Преглед изворни кода

fix(s0): switch process flow card list to missed print records

将「工序流转卡」页面数据源从 S0ProcessFlowCard(0 行)切换为
legacy MissedPrint 表(202 行);页面只读化,移除新增/编辑/删除/启停。

- 新增 Entity:AdoS0MissedPrintRecord(9 字段只读投影 + [IgnoreTable])
- 追加 Entity:AdoGeneralizedCodeRecord(用于 LeftJoin 翻译 BarcodeStatus)
- 新增 DTO:AdoS0FlowCardPrintRecordQueryDto / S0MfgFlowCardPrintRecordRow
- 重写 Controller GetPagedAsync 走 MissedPrint + LeftJoin GeneralizedCode(.MergeTable())
- 删除 Controller 5 个写入/详情 Action(页面只读)
- 前端 API 类型重写、s0MfgProcessFlowCardsApi 仅保留 list
- ProcessFlowCardList.vue 改为只读列表(9 列 + 6 个查询字段)
- 菜单 Title 保持「工序流转卡」、Remark 注明数据源 = legacy MissedPrint
- bump server 1.0.132 → 1.0.133 / Web 2.4.170 → 2.4.171
YY968XX пре 2 дана
родитељ
комит
29d2dda1f8

+ 1 - 1
Web/package.json

@@ -1,7 +1,7 @@
 {
 	"name": "admin.net",
 	"type": "module",
-	"version": "2.4.170",
+	"version": "2.4.171",
 	"packageManager": "pnpm@10.32.1",
 	"lastBuildTime": "2026.03.15",
 	"description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",

+ 29 - 37
Web/src/views/aidop/s0/api/s0ManufacturingApi.ts

@@ -390,45 +390,30 @@ export const s0MfgStandardOperationsApi = {
 	delete: (id: number) => service.delete(`${stdOpBase}/${id}`).then(unwrap),
 };
 
-export interface S0ProcessFlowCardRow {
+/** 流转卡打印记录(legacy MissedPrint 只读投影)。 */
+export interface S0MfgFlowCardPrintRecordRow {
 	id: number;
-	companyRefId?: string;
-	factoryRefId?: string;
-	domainCode?: string | null;
-	workOrderNo: string;
-	workOrderBatchNo?: string | null;
-	flowCardNo: string;
-	nature?: string | null;
-	itemNum: string;
-	itemName?: string | null;
-	drawingNo?: string | null;
-	bomVersion?: string | null;
-	routingCode?: string | null;
-	remarks?: string | null;
-	isActive: boolean;
-	createUser?: string | null;
-	createTime?: string;
-	updateUser?: string | null;
-	updateTime?: string | null;
+	workOrd?: string | null;
+	op?: number | null;
+	itemNum?: string | null;
+	descr?: string | null;
+	labelFormat?: string | null;
+	labelColor?: string | null;
+	status?: string | null;
+	statusDesc?: string | null;
+	createTime?: string | null;
 }
 
-export interface S0ProcessFlowCardUpsert {
-	companyRefId?: string;
-	factoryRefId?: string;
-	domainCode?: string;
-	workOrderNo: string;
-	workOrderBatchNo?: string;
-	flowCardNo: string;
-	nature?: string;
-	itemNum: string;
-	itemName?: string;
-	drawingNo?: string;
-	bomVersion?: string;
-	routingCode?: string;
-	remarks?: string;
-	isActive: boolean;
-	createUser?: string;
-	updateUser?: string;
+export interface S0MfgFlowCardPrintRecordQuery {
+	page?: number;
+	pageSize?: number;
+	keyword?: string;
+	workOrd?: string;
+	itemNum?: string;
+	labelColor?: string;
+	status?: string;
+	createTimeFrom?: string;
+	createTimeTo?: string;
 }
 
 export interface S0OrderScheduleCycleRow {
@@ -501,7 +486,14 @@ export const s0MfgPersonSkillsApi = {
 };
 export const s0MfgWorkCentersApi = crud(`${mfg}/work-centers`);
 export const s0MfgLineMaterialsApi = crud(`${mfg}/line-materials`);
-export const s0MfgProcessFlowCardsApi = crud(`${mfg}/process-flow-cards`);
+/**
+ * 流转卡打印记录 API(数据源:legacy MissedPrint 只读投影)。
+ * 路由保持 process-flow-cards 不变,降低菜单/权限/前端调用迁移成本;仅暴露 list(页面只读)。
+ */
+export const s0MfgProcessFlowCardsApi = {
+	list: (params: S0MfgFlowCardPrintRecordQuery) =>
+		service.get<Paged<S0MfgFlowCardPrintRecordRow>>(`${mfg}/process-flow-cards`, { params }).then(unwrap),
+};
 export const s0MfgOrderScheduleCyclesApi = crud(`${mfg}/order-schedule-cycles`);
 export const s0MfgWorkOrderControlsApi = crud(`${mfg}/work-order-controls`);
 export const s0MfgMaterialProcessElementsApi = crud(`${mfg}/material-process-elements`);

+ 67 - 236
Web/src/views/aidop/s0/manufacturing/ProcessFlowCardList.vue

@@ -1,64 +1,52 @@
 <template>
 	<AidopDemoShell :title="pageTitle" subtitle="S0 / Manufacturing / 工序流转卡">
 		<el-form :inline="true" :model="query" class="mb12" @submit.prevent>
-			<el-form-item label="公司">
-				<el-select v-model="query.companyRefId" clearable filterable placeholder="全部" style="width: 160px">
-					<el-option v-for="item in companyOptions" :key="item.id" :label="item.name || item.code || `${item.id}`" :value="item.id" />
-				</el-select>
-			</el-form-item>
-			<el-form-item label="工厂">
-				<el-select v-model="query.factoryRefId" clearable filterable placeholder="全部" style="width: 160px">
-					<el-option v-for="item in filteredFactoryOptions" :key="item.id" :label="item.name || item.code || `${item.id}`" :value="item.id" />
-				</el-select>
+			<el-form-item label="关键字">
+				<el-input v-model="query.keyword" clearable placeholder="工单号 / 物料编码 / 物料描述" style="width: 220px" />
 			</el-form-item>
-			<el-form-item label="工单">
-				<el-input v-model="query.workOrderNo" clearable style="width: 140px" />
-			</el-form-item>
-			<el-form-item label="流转卡号">
-				<el-input v-model="query.flowCardNo" clearable style="width: 140px" />
+			<el-form-item label="工单号">
+				<el-input v-model="query.workOrd" clearable placeholder="工单号模糊" style="width: 160px" />
 			</el-form-item>
 			<el-form-item label="物料编码">
-				<el-input v-model="query.itemNum" clearable style="width: 140px" />
+				<el-input v-model="query.itemNum" clearable placeholder="物料编码模糊" style="width: 160px" />
 			</el-form-item>
-			<el-form-item label="启用">
-				<el-select v-model="query.isActive" clearable placeholder="全部" style="width: 100px">
-					<el-option label="是" :value="true" />
-					<el-option label="否" :value="false" />
+			<el-form-item label="标签颜色">
+				<el-select v-model="query.labelColor" clearable placeholder="全部" style="width: 120px">
+					<el-option label="LZK" value="LZK" />
 				</el-select>
 			</el-form-item>
+			<el-form-item label="状态">
+				<el-input v-model="query.status" clearable placeholder="状态码" style="width: 100px" />
+			</el-form-item>
+			<el-form-item label="创建时间">
+				<el-date-picker
+					v-model="query.createTimeRange"
+					type="datetimerange"
+					value-format="YYYY-MM-DDTHH:mm:ss"
+					range-separator="-"
+					start-placeholder="开始"
+					end-placeholder="结束"
+					style="width: 360px"
+				/>
+			</el-form-item>
 			<el-form-item>
 				<el-button type="primary" @click="loadList">查询</el-button>
 				<el-button @click="resetQuery">重置</el-button>
-				<el-button type="success" @click="openCreate">新增</el-button>
 			</el-form-item>
 		</el-form>
 
 		<el-table :data="rows" v-loading="loading" border stripe style="width: 100%" max-height="calc(100vh - 260px)">
-			<el-table-column prop="flowCardNo" label="流转卡号" width="140" show-overflow-tooltip />
-			<el-table-column prop="workOrderNo" label="工单" width="120" show-overflow-tooltip />
-			<el-table-column prop="workOrderBatchNo" label="工单批号" width="120" show-overflow-tooltip />
-			<el-table-column prop="nature" label="性质" width="100" show-overflow-tooltip />
-			<el-table-column prop="itemNum" label="物料编码" width="120" show-overflow-tooltip />
-			<el-table-column prop="itemName" label="物料名称" min-width="140" show-overflow-tooltip />
-			<el-table-column prop="drawingNo" label="图号" width="100" show-overflow-tooltip />
-			<el-table-column prop="bomVersion" label="BOM版本" width="100" show-overflow-tooltip />
-			<el-table-column prop="routingCode" label="工艺路线" width="110" show-overflow-tooltip />
-			<el-table-column prop="domainCode" label="工厂域" width="100" show-overflow-tooltip />
-			<el-table-column label="启用" width="72" align="center">
-				<template #default="{ row }">
-					<el-tag :type="row.isActive ? 'success' : 'info'" size="small">{{ row.isActive ? '是' : '否' }}</el-tag>
-				</template>
-			</el-table-column>
-			<el-table-column prop="remarks" label="备注" min-width="120" show-overflow-tooltip />
-			<el-table-column label="操作" width="220" fixed="right" align="center">
-				<template #default="{ row }">
-					<el-button link type="primary" @click="openEdit(row)">编辑</el-button>
-					<el-button link :type="row.isActive ? 'warning' : 'success'" @click="toggleActive(row)">
-						{{ row.isActive ? '停用' : '启用' }}
-					</el-button>
-					<el-button link type="danger" @click="onDelete(row)">删除</el-button>
-				</template>
+			<el-table-column prop="id" label="记录ID" width="100" show-overflow-tooltip />
+			<el-table-column prop="workOrd" label="工单号" width="140" show-overflow-tooltip />
+			<el-table-column prop="op" label="工序" width="80" align="right" />
+			<el-table-column prop="itemNum" label="物料编码" width="140" show-overflow-tooltip />
+			<el-table-column prop="descr" label="物料描述" min-width="180" show-overflow-tooltip />
+			<el-table-column prop="labelFormat" label="标签格式" width="110" show-overflow-tooltip />
+			<el-table-column prop="labelColor" label="标签颜色" width="90" />
+			<el-table-column label="状态" width="110" show-overflow-tooltip>
+				<template #default="{ row }">{{ row.statusDesc || row.status || '' }}</template>
 			</el-table-column>
+			<el-table-column prop="createTime" label="创建时间" width="170" />
 		</el-table>
 
 		<div class="pager">
@@ -72,140 +60,50 @@
 				@size-change="loadList"
 			/>
 		</div>
-
-		<el-dialog v-model="dialogVisible" :title="dialogTitle" width="820px" destroy-on-close @closed="resetForm">
-			<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
-				<el-row :gutter="16">
-					<el-col :span="12"><el-form-item label="公司" prop="companyRefId">
-						<el-select v-model="form.companyRefId" filterable style="width: 100%">
-							<el-option v-for="item in companyOptions" :key="item.id" :label="item.name || item.code || `${item.id}`" :value="item.id" />
-						</el-select>
-					</el-form-item></el-col>
-					<el-col :span="12"><el-form-item label="工厂" prop="factoryRefId">
-						<el-select v-model="form.factoryRefId" filterable style="width: 100%" @change="syncDomainCode">
-							<el-option v-for="item in formFactoryOptions" :key="item.id" :label="item.name || item.code || `${item.id}`" :value="item.id" />
-						</el-select>
-					</el-form-item></el-col>
-					<el-col :span="12"><el-form-item label="工厂域"><el-input v-model="form.domainCode" /></el-form-item></el-col>
-					<el-col :span="12"><el-form-item label="流转卡号" prop="flowCardNo"><el-input v-model="form.flowCardNo" /></el-form-item></el-col>
-					<el-col :span="12"><el-form-item label="工单" prop="workOrderNo"><el-input v-model="form.workOrderNo" /></el-form-item></el-col>
-					<el-col :span="12"><el-form-item label="工单批号"><el-input v-model="form.workOrderBatchNo" /></el-form-item></el-col>
-					<el-col :span="12"><el-form-item label="性质"><el-input v-model="form.nature" /></el-form-item></el-col>
-					<el-col :span="12"><el-form-item label="物料编码" prop="itemNum"><el-input v-model="form.itemNum" /></el-form-item></el-col>
-					<el-col :span="12"><el-form-item label="物料名称"><el-input v-model="form.itemName" /></el-form-item></el-col>
-					<el-col :span="12"><el-form-item label="图号"><el-input v-model="form.drawingNo" /></el-form-item></el-col>
-					<el-col :span="12"><el-form-item label="BOM版本"><el-input v-model="form.bomVersion" /></el-form-item></el-col>
-					<el-col :span="12"><el-form-item label="工艺路线"><el-input v-model="form.routingCode" /></el-form-item></el-col>
-					<el-col :span="12"><el-form-item label="启用"><el-switch v-model="form.isActive" /></el-form-item></el-col>
-					<el-col :span="24"><el-form-item label="备注"><el-input v-model="form.remarks" type="textarea" rows="2" /></el-form-item></el-col>
-				</el-row>
-			</el-form>
-			<template #footer>
-				<el-button @click="dialogVisible = false">取消</el-button>
-				<el-button type="primary" :loading="saving" @click="submitForm">保存</el-button>
-			</template>
-		</el-dialog>
 	</AidopDemoShell>
 </template>
 
 <script setup lang="ts" name="aidopS0MfgProcessFlowCard">
-import { computed, onMounted, reactive, ref, watch } from 'vue';
+import { computed, onMounted, reactive, ref } from 'vue';
 import { useRoute } from 'vue-router';
-import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus';
 import AidopDemoShell from '../../components/AidopDemoShell.vue';
-import { loadOrgList, s0MfgProcessFlowCardsApi, type OrgOption, type S0ProcessFlowCardRow, type S0ProcessFlowCardUpsert } from '../api/s0ManufacturingApi';
+import {
+	s0MfgProcessFlowCardsApi,
+	type S0MfgFlowCardPrintRecordRow,
+} from '../api/s0ManufacturingApi';
 
 const route = useRoute();
 const pageTitle = computed(() => (route.meta?.title as string) || '工序流转卡');
 
 const query = reactive({
-	companyRefId: undefined as string | undefined,
-	factoryRefId: undefined as string | undefined,
-	workOrderNo: '',
-	flowCardNo: '',
+	keyword: '',
+	workOrd: '',
 	itemNum: '',
-	isActive: undefined as boolean | undefined,
+	labelColor: '',
+	status: '',
+	createTimeRange: [] as string[],
 	page: 1,
 	pageSize: 20,
 });
 
 const loading = ref(false);
-const rows = ref<S0ProcessFlowCardRow[]>([]);
+const rows = ref<S0MfgFlowCardPrintRecordRow[]>([]);
 const total = ref(0);
-const dialogVisible = ref(false);
-const dialogTitle = ref('新增');
-const editingId = ref<number | null>(null);
-const saving = ref(false);
-const formRef = ref<FormInstance>();
-const companyOptions = ref<OrgOption[]>([]);
-const factoryOptions = ref<OrgOption[]>([]);
-
-const filteredFactoryOptions = computed(() => {
-	if (!query.companyRefId) return factoryOptions.value;
-	return factoryOptions.value.filter((item) => item.pid === query.companyRefId);
-});
-const formFactoryOptions = computed(() => {
-	if (!form.companyRefId) return factoryOptions.value;
-	return factoryOptions.value.filter((item) => item.pid === form.companyRefId);
-});
-
-const form = reactive<S0ProcessFlowCardUpsert>({
-	companyRefId: undefined,
-	factoryRefId: undefined,
-	domainCode: '',
-	workOrderNo: '',
-	workOrderBatchNo: '',
-	flowCardNo: '',
-	nature: '',
-	itemNum: '',
-	itemName: '',
-	drawingNo: '',
-	bomVersion: '',
-	routingCode: '',
-	remarks: '',
-	isActive: true,
-	createUser: '',
-	updateUser: '',
-});
-
-const rules: FormRules = {
-	companyRefId: [{ required: true, message: '请选择公司', trigger: 'change' }],
-	factoryRefId: [{ required: true, message: '请选择工厂', trigger: 'change' }],
-	flowCardNo: [{ required: true, message: '请填写流转卡号', trigger: 'blur' }],
-	workOrderNo: [{ required: true, message: '请填写工单', trigger: 'blur' }],
-	itemNum: [{ required: true, message: '请填写物料编码', trigger: 'blur' }],
-};
-
-watch(() => query.companyRefId, () => {
-	if (!filteredFactoryOptions.value.some((item) => item.id === query.factoryRefId)) query.factoryRefId = undefined;
-});
-watch(() => form.companyRefId, () => {
-	if (!formFactoryOptions.value.some((item) => item.id === form.factoryRefId)) form.factoryRefId = undefined;
-});
-
-function syncDomainCode() {
-	const f = factoryOptions.value.find((item) => item.id === form.factoryRefId);
-	form.domainCode = f?.code ?? '';
-}
-
-async function loadOptions() {
-	const [companies, factories] = await Promise.all([loadOrgList('201'), loadOrgList('501')]);
-	companyOptions.value = companies;
-	factoryOptions.value = factories;
-}
 
 async function loadList() {
 	loading.value = true;
 	try {
+		const [from, to] = query.createTimeRange || [];
 		const data = await s0MfgProcessFlowCardsApi.list({
 			page: query.page,
 			pageSize: query.pageSize,
-			companyRefId: query.companyRefId,
-			factoryRefId: query.factoryRefId,
-			workOrderNo: query.workOrderNo || undefined,
-			flowCardNo: query.flowCardNo || undefined,
+			keyword: query.keyword || undefined,
+			workOrd: query.workOrd || undefined,
 			itemNum: query.itemNum || undefined,
-			isActive: query.isActive,
+			labelColor: query.labelColor || undefined,
+			status: query.status || undefined,
+			createTimeFrom: from || undefined,
+			createTimeTo: to || undefined,
 		});
 		rows.value = data.list;
 		total.value = data.total;
@@ -218,96 +116,29 @@ async function loadList() {
 }
 
 function resetQuery() {
-	Object.assign(query, {
-		companyRefId: undefined,
-		factoryRefId: undefined,
-		workOrderNo: '',
-		flowCardNo: '',
-		itemNum: '',
-		isActive: undefined,
-		page: 1,
-	});
+	query.keyword = '';
+	query.workOrd = '';
+	query.itemNum = '';
+	query.labelColor = '';
+	query.status = '';
+	query.createTimeRange = [];
+	query.page = 1;
 	void loadList();
 }
 
-function resetForm() {
-	editingId.value = null;
-	Object.assign(form, {
-		companyRefId: undefined, factoryRefId: undefined, domainCode: '', workOrderNo: '', workOrderBatchNo: '',
-		flowCardNo: '', nature: '', itemNum: '', itemName: '', drawingNo: '', bomVersion: '',
-		routingCode: '', remarks: '', isActive: true, createUser: '', updateUser: '',
-	});
-	formRef.value?.clearValidate();
-}
-
-function openCreate() {
-	resetForm();
-	dialogTitle.value = '新增工序流转卡';
-	dialogVisible.value = true;
-}
-
-function openEdit(row: S0ProcessFlowCardRow) {
-	resetForm();
-	editingId.value = row.id;
-	dialogTitle.value = `编辑 ${row.flowCardNo}`;
-	Object.assign(form, {
-		companyRefId: row.companyRefId, factoryRefId: row.factoryRefId, domainCode: row.domainCode ?? '',
-		workOrderNo: row.workOrderNo, workOrderBatchNo: row.workOrderBatchNo ?? '', flowCardNo: row.flowCardNo,
-		nature: row.nature ?? '', itemNum: row.itemNum, itemName: row.itemName ?? '', drawingNo: row.drawingNo ?? '',
-		bomVersion: row.bomVersion ?? '', routingCode: row.routingCode ?? '', remarks: row.remarks ?? '', isActive: row.isActive,
-		createUser: row.createUser ?? '', updateUser: row.updateUser ?? '',
-	});
-	dialogVisible.value = true;
-}
-
-async function submitForm() {
-	await formRef.value?.validate();
-	saving.value = true;
-	try {
-		const payload: S0ProcessFlowCardUpsert = { ...form };
-		if (editingId.value) {
-			await s0MfgProcessFlowCardsApi.update(editingId.value, payload);
-			ElMessage.success('已保存');
-		} else {
-			await s0MfgProcessFlowCardsApi.create(payload);
-			ElMessage.success('已创建');
-		}
-		dialogVisible.value = false;
-		await loadList();
-	} finally {
-		saving.value = false;
-	}
-}
-
-function onDelete(row: S0ProcessFlowCardRow) {
-	ElMessageBox.confirm(`确定删除流转卡「${row.flowCardNo}」?`, '确认', { type: 'warning' })
-		.then(async () => {
-			await s0MfgProcessFlowCardsApi.delete(row.id);
-			ElMessage.success('已删除');
-			await loadList();
-		})
-		.catch(() => {});
-}
-
-function toggleActive(row: S0ProcessFlowCardRow) {
-	const next = !row.isActive;
-	ElMessageBox.confirm(`确定${next ? '启用' : '停用'}流转卡「${row.flowCardNo}」?`, '确认', { type: 'warning' })
-		.then(async () => {
-			await s0MfgProcessFlowCardsApi.toggleEnabled(row.id, { isEnabled: next });
-			ElMessage.success('操作成功');
-			await loadList();
-		})
-		.catch(() => {});
-}
-
-onMounted(async () => {
-	await loadOptions();
-	await loadList();
+onMounted(() => {
+	void loadList();
 });
 </script>
 
 <style scoped lang="scss">
 @import '/@/views/aidop/styles/aidop-demo.scss';
-.mb12 { margin-bottom: 12px; }
-.pager { margin-top: 12px; display: flex; justify-content: flex-end; }
+.mb12 {
+	margin-bottom: 12px;
+}
+.pager {
+	margin-top: 12px;
+	display: flex;
+	justify-content: flex-end;
+}
 </style>

+ 3 - 3
server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj

@@ -11,9 +11,9 @@
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <Copyright>Admin.NET</Copyright>
     <Description>Admin.NET 通用权限开发平台</Description>
-    <AssemblyVersion>1.0.132</AssemblyVersion>
-    <FileVersion>1.0.132</FileVersion>
-    <Version>1.0.132</Version>
+    <AssemblyVersion>1.0.133</AssemblyVersion>
+    <FileVersion>1.0.133</FileVersion>
+    <Version>1.0.133</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 48 - 108
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Manufacturing/AdoS0MfgProcessFlowCardsController.cs

@@ -1,134 +1,74 @@
 using Admin.NET.Plugin.AiDOP.Dto.S0.Manufacturing;
+using Admin.NET.Plugin.AiDOP.Entity;
 using Admin.NET.Plugin.AiDOP.Entity.S0.Manufacturing;
 using Admin.NET.Plugin.AiDOP.Infrastructure;
 
 namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Manufacturing;
 
+/// <summary>
+/// 流转卡打印记录(legacy MissedPrint 只读投影)。
+/// 路由保留 /api/s0/manufacturing/process-flow-cards 以降低前端 / 菜单 / 权限迁移成本。
+/// 数据源由 S0ProcessFlowCard(当前 0 行)切换为 MissedPrint(200+ 行)。
+/// 写入路径仍由 S3 SupplierShipment / SupplierDelivery 服务负责,本 Controller 仅提供只读分页查询。
+/// </summary>
 [ApiController]
 [Route("api/s0/manufacturing/process-flow-cards")]
 [AllowAnonymous]
 [NonUnify]
 public class AdoS0MfgProcessFlowCardsController : ControllerBase
 {
-    private readonly SqlSugarRepository<AdoS0ProcessFlowCard> _rep;
+    private readonly SqlSugarRepository<AdoS0MissedPrintRecord> _rep;
 
-    public AdoS0MfgProcessFlowCardsController(SqlSugarRepository<AdoS0ProcessFlowCard> rep) => _rep = rep;
+    public AdoS0MfgProcessFlowCardsController(SqlSugarRepository<AdoS0MissedPrintRecord> rep) => _rep = rep;
 
     [HttpGet]
-    public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0ProcessFlowCardQueryDto q)
+    public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0FlowCardPrintRecordQueryDto q)
     {
         var page = q.EffectivePage;
         var pageSize = q.PageSize;
         (page, pageSize) = PagingGuard.Normalize(page, pageSize);
 
-        var query = _rep.AsQueryable()
-            .WhereIF(q.CompanyRefId.HasValue, x => x.CompanyRefId == q.CompanyRefId!.Value)
-            .WhereIF(q.FactoryRefId.HasValue, x => x.FactoryRefId == q.FactoryRefId!.Value)
-            .WhereIF(!string.IsNullOrWhiteSpace(q.DomainCode), x => x.DomainCode == q.DomainCode)
-            .WhereIF(!string.IsNullOrWhiteSpace(q.WorkOrderNo), x => x.WorkOrderNo.Contains(q.WorkOrderNo!))
-            .WhereIF(!string.IsNullOrWhiteSpace(q.FlowCardNo), x => x.FlowCardNo.Contains(q.FlowCardNo!))
-            .WhereIF(!string.IsNullOrWhiteSpace(q.ItemNum), x => x.ItemNum.Contains(q.ItemNum!))
-            .WhereIF(q.IsActive.HasValue, x => x.IsActive == q.IsActive.Value)
+        var query = _rep.Context.Queryable<AdoS0MissedPrintRecord, AdoGeneralizedCodeRecord>(
+                (m, g) => new JoinQueryInfos(
+                    JoinType.Left, g.FldName == "BarcodeStatus" && g.Val == m.Status))
             .WhereIF(!string.IsNullOrWhiteSpace(q.Keyword),
-                x => x.WorkOrderNo.Contains(q.Keyword!)
-                    || x.FlowCardNo.Contains(q.Keyword!)
-                    || x.ItemNum.Contains(q.Keyword!)
-                    || (x.ItemName != null && x.ItemName.Contains(q.Keyword!))
-                    || (x.DrawingNo != null && x.DrawingNo.Contains(q.Keyword!)));
+                (m, g) => (m.WorkOrd != null && m.WorkOrd.Contains(q.Keyword!))
+                       || (m.ItemNum != null && m.ItemNum.Contains(q.Keyword!))
+                       || (m.Descr != null && m.Descr.Contains(q.Keyword!)))
+            .WhereIF(!string.IsNullOrWhiteSpace(q.WorkOrd),
+                (m, g) => m.WorkOrd != null && m.WorkOrd.Contains(q.WorkOrd!))
+            .WhereIF(!string.IsNullOrWhiteSpace(q.ItemNum),
+                (m, g) => m.ItemNum != null && m.ItemNum.Contains(q.ItemNum!))
+            .WhereIF(!string.IsNullOrWhiteSpace(q.LabelColor),
+                (m, g) => m.LabelColor == q.LabelColor)
+            .WhereIF(!string.IsNullOrWhiteSpace(q.Status),
+                (m, g) => m.Status == q.Status)
+            .WhereIF(q.CreateTimeFrom.HasValue,
+                (m, g) => m.CreateTime >= q.CreateTimeFrom!.Value)
+            .WhereIF(q.CreateTimeTo.HasValue,
+                (m, g) => m.CreateTime <= q.CreateTimeTo!.Value);
 
         var total = await query.CountAsync();
-        var list = await query.OrderByDescending(x => x.CreateTime).Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();
-        return Ok(new { total, page, pageSize, list });
-    }
-
-    [HttpGet("{id:long}")]
-    public async Task<IActionResult> GetAsync(long id)
-    {
-        var item = await _rep.GetByIdAsync(id);
-        return item == null ? NotFound() : Ok(item);
-    }
-
-    [HttpPost]
-    public async Task<IActionResult> CreateAsync([FromBody] AdoS0ProcessFlowCardUpsertDto dto)
-    {
-        var flowCardNo = dto.FlowCardNo.Trim();
-        if (await _rep.IsAnyAsync(x => x.FactoryRefId == dto.FactoryRefId && x.FlowCardNo == flowCardNo))
-            return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DuplicateCode, "同工厂下流转卡号已存在");
-
-        var now = DateTime.Now;
-        var entity = new AdoS0ProcessFlowCard
-        {
-            CompanyRefId = dto.CompanyRefId,
-            FactoryRefId = dto.FactoryRefId,
-            DomainCode = dto.DomainCode,
-            WorkOrderNo = dto.WorkOrderNo.Trim(),
-            WorkOrderBatchNo = dto.WorkOrderBatchNo?.Trim(),
-            FlowCardNo = flowCardNo,
-            Nature = dto.Nature?.Trim(),
-            ItemNum = dto.ItemNum.Trim(),
-            ItemName = dto.ItemName?.Trim(),
-            DrawingNo = dto.DrawingNo?.Trim(),
-            BomVersion = dto.BomVersion?.Trim(),
-            RoutingCode = dto.RoutingCode?.Trim(),
-            Remarks = dto.Remarks?.Trim(),
-            IsActive = dto.IsActive,
-            CreateUser = dto.CreateUser,
-            CreateTime = now,
-            UpdateUser = dto.UpdateUser,
-            UpdateTime = null
-        };
-        await _rep.AsInsertable(entity).ExecuteReturnEntityAsync();
-        return Ok(entity);
-    }
-
-    [HttpPut("{id:long}")]
-    public async Task<IActionResult> UpdateAsync(long id, [FromBody] AdoS0ProcessFlowCardUpsertDto dto)
-    {
-        var entity = await _rep.GetByIdAsync(id);
-        if (entity == null) return NotFound();
-
-        var flowCardNo = dto.FlowCardNo.Trim();
-        if (await _rep.IsAnyAsync(x => x.Id != id && x.FactoryRefId == dto.FactoryRefId && x.FlowCardNo == flowCardNo))
-            return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DuplicateCode, "同工厂下流转卡号已存在");
-
-        entity.CompanyRefId = dto.CompanyRefId;
-        entity.FactoryRefId = dto.FactoryRefId;
-        entity.DomainCode = dto.DomainCode;
-        entity.WorkOrderNo = dto.WorkOrderNo.Trim();
-        entity.WorkOrderBatchNo = dto.WorkOrderBatchNo?.Trim();
-        entity.FlowCardNo = flowCardNo;
-        entity.Nature = dto.Nature?.Trim();
-        entity.ItemNum = dto.ItemNum.Trim();
-        entity.ItemName = dto.ItemName?.Trim();
-        entity.DrawingNo = dto.DrawingNo?.Trim();
-        entity.BomVersion = dto.BomVersion?.Trim();
-        entity.RoutingCode = dto.RoutingCode?.Trim();
-        entity.Remarks = dto.Remarks?.Trim();
-        entity.IsActive = dto.IsActive;
-        entity.UpdateUser = dto.UpdateUser;
-        entity.UpdateTime = DateTime.Now;
-
-        await _rep.AsUpdateable(entity).ExecuteCommandAsync();
-        return Ok(entity);
-    }
+        var list = await query
+            .Select((m, g) => new S0MfgFlowCardPrintRecordRow
+            {
+                Id = m.Id,
+                WorkOrd = m.WorkOrd,
+                Op = m.Op,
+                ItemNum = m.ItemNum,
+                Descr = m.Descr,
+                LabelFormat = m.LabelFormat,
+                LabelColor = m.LabelColor,
+                Status = m.Status,
+                StatusDesc = g.Comments ?? m.Status,
+                CreateTime = m.CreateTime
+            })
+            .MergeTable()
+            .OrderByDescending(r => r.CreateTime)
+            .OrderByDescending(r => r.Id)
+            .Skip((page - 1) * pageSize).Take(pageSize)
+            .ToListAsync();
 
-    [HttpPatch("{id:long}/toggle-enabled")]
-    public async Task<IActionResult> ToggleActiveAsync(long id, [FromBody] AdoS0ProcessFlowCardToggleActiveDto dto)
-    {
-        var entity = await _rep.GetByIdAsync(id);
-        if (entity == null) return NotFound();
-        entity.IsActive = dto.IsEnabled;
-        entity.UpdateTime = DateTime.Now;
-        await _rep.AsUpdateable(entity).ExecuteCommandAsync();
-        return Ok(entity);
-    }
-
-    [HttpDelete("{id:long}")]
-    public async Task<IActionResult> DeleteAsync(long id)
-    {
-        var item = await _rep.GetByIdAsync(id);
-        if (item == null) return NotFound();
-        await _rep.DeleteAsync(item);
-        return Ok(new { message = "删除成功" });
+        return Ok(new { total, page, pageSize, list });
     }
 }

+ 28 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Dto/S0/Manufacturing/AdoS0ManufacturingDtos.cs

@@ -991,4 +991,32 @@ public class AdoS0OrderScheduleCycleToggleActiveDto
     public bool IsEnabled { get; set; }
 }
 
+// =========================
+// 流转卡打印记录(legacy MissedPrint 只读投影)
+// =========================
+
+public class AdoS0FlowCardPrintRecordQueryDto : AdoS0MfgPagedQueryBase
+{
+    public string? WorkOrd { get; set; }
+    public string? ItemNum { get; set; }
+    public string? LabelColor { get; set; }
+    public string? Status { get; set; }
+    public DateTime? CreateTimeFrom { get; set; }
+    public DateTime? CreateTimeTo { get; set; }
+}
+
+public class S0MfgFlowCardPrintRecordRow
+{
+    public long Id { get; set; }
+    public string? WorkOrd { get; set; }
+    public int? Op { get; set; }
+    public string? ItemNum { get; set; }
+    public string? Descr { get; set; }
+    public string? LabelFormat { get; set; }
+    public string? LabelColor { get; set; }
+    public string? Status { get; set; }
+    public string? StatusDesc { get; set; }
+    public DateTime? CreateTime { get; set; }
+}
+
 #endregion

+ 18 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/AdoLegacyEntities.cs

@@ -41,3 +41,21 @@ public class AdoPlan
     [SugarColumn(IsNullable = true, Length = 200)]
     public string? ProductName { get; set; }
 }
+
+/// <summary>通用代码主数据(legacy GeneralizedCodeMaster 只读投影,用于 BarcodeStatus 等状态码翻译)。</summary>
+[IgnoreTable]
+[SugarTable("GeneralizedCodeMaster")]
+public class AdoGeneralizedCodeRecord
+{
+    [SugarColumn(ColumnName = "RecID", IsPrimaryKey = true)]
+    public long Id { get; set; }
+
+    [SugarColumn(ColumnName = "FldName", Length = 100, IsNullable = true)]
+    public string? FldName { get; set; }
+
+    [SugarColumn(ColumnName = "Val", Length = 50)]
+    public string Val { get; set; } = "";
+
+    [SugarColumn(ColumnName = "Comments", Length = 50, IsNullable = true)]
+    public string? Comments { get; set; }
+}

+ 38 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Manufacturing/AdoS0MissedPrintRecord.cs

@@ -0,0 +1,38 @@
+namespace Admin.NET.Plugin.AiDOP.Entity.S0.Manufacturing;
+
+/// <summary>
+/// 工序流转卡打印记录(legacy MissedPrint 只读投影)。
+/// 列表展示仅取 9 个字段;MissedPrint 80+ 余字段由 S3 SupplierShipment / SupplierDelivery 写入路径负责,本 Entity 不投影。
+/// 标 IgnoreTable 避免 SqlSugar CodeFirst 介入 legacy 表 schema。
+/// </summary>
+[IgnoreTable]
+[SugarTable("MissedPrint", "工序流转卡打印记录(legacy MissedPrint 只读投影)")]
+public class AdoS0MissedPrintRecord
+{
+    [SugarColumn(ColumnName = "RecID", IsPrimaryKey = true, ColumnDataType = "bigint")]
+    public long Id { get; set; }
+
+    [SugarColumn(ColumnName = "WorkOrd", Length = 100, IsNullable = true)]
+    public string? WorkOrd { get; set; }
+
+    [SugarColumn(ColumnName = "Op", IsNullable = true)]
+    public int? Op { get; set; }
+
+    [SugarColumn(ColumnName = "ItemNum", Length = 100, IsNullable = true)]
+    public string? ItemNum { get; set; }
+
+    [SugarColumn(ColumnName = "Descr", Length = 255, IsNullable = true)]
+    public string? Descr { get; set; }
+
+    [SugarColumn(ColumnName = "LabelFormat", Length = 50, IsNullable = true)]
+    public string? LabelFormat { get; set; }
+
+    [SugarColumn(ColumnName = "LabelColor", Length = 20, IsNullable = true)]
+    public string? LabelColor { get; set; }
+
+    [SugarColumn(ColumnName = "Status", Length = 10, IsNullable = true)]
+    public string? Status { get; set; }
+
+    [SugarColumn(ColumnName = "CreateTime", IsNullable = true)]
+    public DateTime? CreateTime { get; set; }
+}

+ 1 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/SeedData/SysMenuSeedData.cs

@@ -722,7 +722,7 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
             Type = MenuTypeEnum.Menu,
             CreateTime = ct,
             OrderNo = 180,
-            Remark = "S0 工序流转卡主数据"
+            Remark = "S0 工序流转卡(数据源:legacy MissedPrint 只读视图)"
         };
 
         yield return new SysMenu