Browse Source

feat(s5): add iqc inspbill approval flow

- 新增 S5_IQC_INSPBILL ApprovalFlow 定义(start→N1_INSPECT→N2_SUP_REVIEW→end)code SeedData(幂等,per-org)
- 新增 ROLE_S5_IQC_INSPECTOR / ROLE_S5_IQC_SUPERVISOR seed + superAdmin.NET UAT 双角色绑定
- 新增 api/S5IqcInspBillFlow:state / submit-result / supervisor-approve / supervisor-reject(正常鉴权)
- 新增 IqcInspBillFlowBizHandler(no-op,不写 qms/库存);drawer 流程操作区 + 检验员表单 + 主管通过/退回
- submit-result 仅写 pd/dhsl/bhgsl/clfs;不写 FBILLSTATUS/审核人/审核时间/leader;退回用 ReturnToPrev 回 N1
- 不做 SQE、不做仓储通知、不写库存、不入库、不新增 SQL、不改菜单/功能码、不改 ApprovalFlow 核心
- 冒烟:路径A通过(CLOSED)/路径B退回(回N1)/7项校验幂等全400无500;qms 仅A/B两行4字段变、其它不变;ApprovalFlow 表按预期增长
- 版本 server 1.0.237 / Web 2.4.235
YY968XX 1 ngày trước cách đây
mục cha
commit
a371379ca1

+ 1 - 1
Web/package.json

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

+ 47 - 0
Web/src/views/aidop/s5/api/iqcInspBillFlow.ts

@@ -0,0 +1,47 @@
+import service from '/@/utils/request';
+import { withAidopTenantParams } from '../../api/aidopTenant';
+
+// ── S5 来料检验单 检验员+主管最小审批流(写接口,正常鉴权)──
+// 后端 api/S5IqcInspBillFlow。BizType=S5_IQC_INSPBILL。不做 SQE/仓储/入库/导出。
+export interface IqcFlowState {
+	bizType?: string;
+	bizId?: number;
+	instanceId?: number | null;
+	flowStatus?: string;
+	currentNodeCode?: string | null;
+	currentNodeName?: string | null;
+	currentAssigneeRole?: string | null;
+	canSubmitResult?: boolean;
+	canSupervisorApprove?: boolean;
+	canSupervisorReject?: boolean;
+	isCompleted?: boolean;
+	updatedAt?: string | null;
+	message?: string | null;
+}
+
+export interface IqcSubmitResultPayload {
+	id: number;
+	pd: number;
+	dhsl: number;
+	bhgsl: number;
+	clfs?: number | null;
+	comment?: string;
+}
+
+export function fetchIqcInspBillFlowState(id: number) {
+	return service
+		.get<IqcFlowState>('/api/S5IqcInspBillFlow/state', { params: withAidopTenantParams({ id }) })
+		.then((r) => r.data);
+}
+
+export function submitIqcInspBillResult(payload: IqcSubmitResultPayload) {
+	return service.post<IqcFlowState>('/api/S5IqcInspBillFlow/submit-result', payload).then((r) => r.data);
+}
+
+export function approveIqcInspBill(id: number, comment?: string) {
+	return service.post<IqcFlowState>('/api/S5IqcInspBillFlow/supervisor-approve', { id, comment }).then((r) => r.data);
+}
+
+export function rejectIqcInspBill(id: number, comment: string) {
+	return service.post<IqcFlowState>('/api/S5IqcInspBillFlow/supervisor-reject', { id, comment }).then((r) => r.data);
+}

+ 130 - 4
Web/src/views/aidop/s5/iqc/components/IqcInspBillDetailDrawer.vue

@@ -69,12 +69,53 @@
 					<el-descriptions-item label="审核时间">{{ fmtDateTime(detail.auditDate) }}</el-descriptions-item>
 					<el-descriptions-item label="审核时间">{{ fmtDateTime(detail.auditDate) }}</el-descriptions-item>
 				</el-descriptions>
 				</el-descriptions>
 
 
-				<el-descriptions title="流程占位" :column="2" border size="small" class="mt12">
+				<el-descriptions title="流程信息" :column="2" border size="small" class="mt12" v-loading="flowLoading">
 					<el-descriptions-item label="BizType">{{ dash(detail.bizType) }}</el-descriptions-item>
 					<el-descriptions-item label="BizType">{{ dash(detail.bizType) }}</el-descriptions-item>
-					<el-descriptions-item label="BizId">{{ dash(detail.bizId) }}</el-descriptions-item>
 					<el-descriptions-item label="BizCode">{{ dash(detail.bizCode) }}</el-descriptions-item>
 					<el-descriptions-item label="BizCode">{{ dash(detail.bizCode) }}</el-descriptions-item>
-					<el-descriptions-item label="提示">{{ dash(detail.flowHint) }}</el-descriptions-item>
+					<el-descriptions-item label="流程状态">{{ dash(flowState?.flowStatus) }}</el-descriptions-item>
+					<el-descriptions-item label="当前节点">{{ dash(flowState?.currentNodeName) }}</el-descriptions-item>
+					<el-descriptions-item label="当前角色">{{ dash(flowState?.currentAssigneeRole) }}</el-descriptions-item>
+					<el-descriptions-item label="说明">{{ dash(flowState?.message) }}</el-descriptions-item>
 				</el-descriptions>
 				</el-descriptions>
+
+				<!-- 检验员录入结果(仅 canSubmitResult 显示)-->
+				<div v-if="flowState?.canSubmitResult" class="flow-op mt12">
+					<div class="flow-op-title">检验员录入检验结果</div>
+					<el-form :model="resultForm" label-width="96px" size="small">
+						<el-form-item label="判定">
+							<el-select v-model="resultForm.pd" style="width: 180px" @change="onPdChange">
+								<el-option :value="0" label="合格" />
+								<el-option :value="1" label="不合格" />
+							</el-select>
+						</el-form-item>
+						<el-form-item label="合格数量">
+							<el-input-number v-model="resultForm.dhsl" :min="0" :controls="false" style="width: 180px" />
+						</el-form-item>
+						<el-form-item label="不合格数量">
+							<el-input-number v-model="resultForm.bhgsl" :min="0" :controls="false" :disabled="resultForm.pd === 0" style="width: 180px" />
+						</el-form-item>
+						<el-form-item label="处理方式" v-if="resultForm.pd === 1">
+							<el-select v-model="resultForm.clfs" style="width: 180px" clearable>
+								<el-option :value="0" label="让步接收" />
+								<el-option :value="1" label="挑选" />
+								<el-option :value="2" label="报废" />
+							</el-select>
+						</el-form-item>
+						<el-form-item label="备注">
+							<el-input v-model="resultForm.comment" type="textarea" :rows="2" style="width: 320px" />
+						</el-form-item>
+						<el-form-item>
+							<el-button type="primary" :loading="submitting" @click="onSubmitResult">提交检验结果</el-button>
+						</el-form-item>
+					</el-form>
+				</div>
+
+				<!-- 检验主管审核(仅 canSupervisorApprove/Reject 显示)-->
+				<div v-if="flowState?.canSupervisorApprove || flowState?.canSupervisorReject" class="flow-op mt12">
+					<div class="flow-op-title">检验主管审核</div>
+					<el-button type="success" :loading="submitting" @click="onApprove">通过</el-button>
+					<el-button type="warning" :loading="submitting" @click="onReject">退回</el-button>
+				</div>
 			</template>
 			</template>
 			<el-empty v-else-if="!loading" description="暂无详情数据" />
 			<el-empty v-else-if="!loading" description="暂无详情数据" />
 		</div>
 		</div>
@@ -82,17 +123,25 @@
 </template>
 </template>
 
 
 <script setup lang="ts" name="IqcInspBillDetailDrawer">
 <script setup lang="ts" name="IqcInspBillDetailDrawer">
-import { computed } from 'vue';
+import { computed, reactive, watch } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
 import type { IqcInspBillDetail } from '../../api/iqcInspBill';
 import type { IqcInspBillDetail } from '../../api/iqcInspBill';
+import type { IqcFlowState, IqcSubmitResultPayload } from '../../api/iqcInspBillFlow';
 
 
 const props = defineProps<{
 const props = defineProps<{
 	modelValue: boolean;
 	modelValue: boolean;
 	detail: IqcInspBillDetail | null;
 	detail: IqcInspBillDetail | null;
 	loading?: boolean;
 	loading?: boolean;
+	flowState?: IqcFlowState | null;
+	flowLoading?: boolean;
+	submitting?: boolean;
 }>();
 }>();
 const emit = defineEmits<{
 const emit = defineEmits<{
 	(e: 'update:modelValue', v: boolean): void;
 	(e: 'update:modelValue', v: boolean): void;
 	(e: 'closed'): void;
 	(e: 'closed'): void;
+	(e: 'submit-result', payload: IqcSubmitResultPayload): void;
+	(e: 'approve'): void;
+	(e: 'reject', comment: string): void;
 }>();
 }>();
 
 
 const innerVisible = computed({
 const innerVisible = computed({
@@ -100,6 +149,73 @@ const innerVisible = computed({
 	set: (v: boolean) => emit('update:modelValue', v),
 	set: (v: boolean) => emit('update:modelValue', v),
 });
 });
 
 
+// 检验员录入表单(只读详情批不含,本批新增,仅 canSubmitResult 时可见)
+const resultForm = reactive<{ pd: number; dhsl: number; bhgsl: number; clfs: number | null; comment: string }>({
+	pd: 0,
+	dhsl: 0,
+	bhgsl: 0,
+	clfs: null,
+	comment: '',
+});
+
+// 每次打开 drawer 重置录入表单
+watch(
+	() => props.modelValue,
+	(v) => {
+		if (v) {
+			resultForm.pd = 0;
+			resultForm.dhsl = 0;
+			resultForm.bhgsl = 0;
+			resultForm.clfs = null;
+			resultForm.comment = '';
+		}
+	}
+);
+
+function onPdChange() {
+	if (resultForm.pd === 0) {
+		resultForm.bhgsl = 0;
+		resultForm.clfs = null;
+	}
+}
+
+function onSubmitResult() {
+	if (resultForm.pd !== 0 && resultForm.pd !== 1) return ElMessage.error('请选择判定');
+	if (resultForm.dhsl < 0) return ElMessage.error('合格数量不能为负');
+	if (resultForm.bhgsl < 0) return ElMessage.error('不合格数量不能为负');
+	if (resultForm.pd === 0 && resultForm.bhgsl !== 0) return ElMessage.error('判定合格时不合格数量必须为 0');
+	if (resultForm.pd === 1 && resultForm.bhgsl <= 0) return ElMessage.error('判定不合格时不合格数量必须大于 0');
+	if (resultForm.pd === 1 && (resultForm.clfs === null || resultForm.clfs === undefined)) return ElMessage.error('判定不合格时请选择处理方式');
+	const id = props.detail?.id;
+	if (!id) return ElMessage.error('检验单 id 缺失');
+	emit('submit-result', {
+		id,
+		pd: resultForm.pd,
+		dhsl: resultForm.dhsl,
+		bhgsl: resultForm.bhgsl,
+		clfs: resultForm.pd === 1 ? resultForm.clfs : null,
+		comment: resultForm.comment,
+	});
+}
+
+function onApprove() {
+	emit('approve');
+}
+
+async function onReject() {
+	try {
+		const { value } = await ElMessageBox.prompt('请填写退回意见', '退回', {
+			confirmButtonText: '确定退回',
+			cancelButtonText: '取消',
+			inputType: 'textarea',
+			inputValidator: (v: string) => (v && v.trim() ? true : '退回意见不能为空'),
+		});
+		emit('reject', value.trim());
+	} catch {
+		/* 用户取消 */
+	}
+}
+
 function dash(v: unknown) {
 function dash(v: unknown) {
 	if (v === null || v === undefined || v === '') return '-';
 	if (v === null || v === undefined || v === '') return '-';
 	return String(v);
 	return String(v);
@@ -141,4 +257,14 @@ function onClosed() {
 	white-space: pre-wrap;
 	white-space: pre-wrap;
 	word-break: break-word;
 	word-break: break-word;
 }
 }
+.flow-op {
+	padding: 12px;
+	border: 1px solid var(--el-border-color-lighter);
+	border-radius: 4px;
+	background: var(--el-fill-color-light);
+}
+.flow-op-title {
+	font-weight: bold;
+	margin-bottom: 10px;
+}
 </style>
 </style>

+ 88 - 3
Web/src/views/aidop/s5/iqc/iqcTaskList.vue

@@ -71,7 +71,17 @@
 			</template>
 			</template>
 		</el-table>
 		</el-table>
 
 
-		<IqcInspBillDetailDrawer v-model="detailVisible" :detail="currentDetail" :loading="detailLoading" />
+		<IqcInspBillDetailDrawer
+			v-model="detailVisible"
+			:detail="currentDetail"
+			:loading="detailLoading"
+			:flow-state="currentFlowState"
+			:flow-loading="flowLoading"
+			:submitting="flowSubmitting"
+			@submit-result="onSubmitResult"
+			@approve="onApprove"
+			@reject="onReject"
+		/>
 
 
 		<div class="pager">
 		<div class="pager">
 			<el-pagination
 			<el-pagination
@@ -93,6 +103,14 @@ import { useRoute } from 'vue-router';
 import { ElMessage } from 'element-plus';
 import { ElMessage } from 'element-plus';
 import AidopDemoShell from '/@/views/aidop/components/AidopDemoShell.vue';
 import AidopDemoShell from '/@/views/aidop/components/AidopDemoShell.vue';
 import { fetchIqcInspBillList, fetchIqcInspBillDetail, type IqcInspBillRow, type IqcInspBillDetail } from '../api/iqcInspBill';
 import { fetchIqcInspBillList, fetchIqcInspBillDetail, type IqcInspBillRow, type IqcInspBillDetail } from '../api/iqcInspBill';
+import {
+	fetchIqcInspBillFlowState,
+	submitIqcInspBillResult,
+	approveIqcInspBill,
+	rejectIqcInspBill,
+	type IqcFlowState,
+	type IqcSubmitResultPayload,
+} from '../api/iqcInspBillFlow';
 import IqcInspBillDetailDrawer from './components/IqcInspBillDetailDrawer.vue';
 import IqcInspBillDetailDrawer from './components/IqcInspBillDetailDrawer.vue';
 
 
 const route = useRoute();
 const route = useRoute();
@@ -123,26 +141,52 @@ const loading = ref(false);
 const rows = ref<IqcInspBillRow[]>([]);
 const rows = ref<IqcInspBillRow[]>([]);
 const total = ref(0);
 const total = ref(0);
 
 
-// 只读详情 drawer(S5-IQC-INSPBILL-DETAIL-READONLY-1):仅查看,无任何写/流程/通知/入库动作
+// 详情 drawer + 检验员/主管最小流程(S5-IQC-INSPBILL-INSPECTOR-SUPERVISOR-FLOW-1)
 const detailVisible = ref(false);
 const detailVisible = ref(false);
 const detailLoading = ref(false);
 const detailLoading = ref(false);
 const currentDetail = ref<IqcInspBillDetail | null>(null);
 const currentDetail = ref<IqcInspBillDetail | null>(null);
+const currentFlowState = ref<IqcFlowState | null>(null);
+const flowLoading = ref(false);
+const flowSubmitting = ref(false);
+const currentId = ref<number | null>(null);
 
 
 function fmtDate(v?: string | null) {
 function fmtDate(v?: string | null) {
 	if (!v) return '';
 	if (!v) return '';
 	return String(v).slice(0, 10);
 	return String(v).slice(0, 10);
 }
 }
 
 
+async function loadFlowState(id: number) {
+	flowLoading.value = true;
+	try {
+		currentFlowState.value = await fetchIqcInspBillFlowState(id);
+	} catch (e: any) {
+		currentFlowState.value = null;
+		ElMessage.error(e?.message || '加载流程状态失败');
+	} finally {
+		flowLoading.value = false;
+	}
+}
+
+async function refreshAfterAction() {
+	if (currentId.value) {
+		const [d] = await Promise.all([fetchIqcInspBillDetail(currentId.value), loadFlowState(currentId.value)]);
+		if (d) currentDetail.value = d;
+	}
+	loadList();
+}
+
 async function openDetail(row: IqcInspBillRow) {
 async function openDetail(row: IqcInspBillRow) {
 	if (!row?.id) {
 	if (!row?.id) {
 		ElMessage.error('检验单 id 缺失,无法查看详情');
 		ElMessage.error('检验单 id 缺失,无法查看详情');
 		return;
 		return;
 	}
 	}
+	currentId.value = row.id;
 	detailLoading.value = true;
 	detailLoading.value = true;
 	detailVisible.value = true;
 	detailVisible.value = true;
 	currentDetail.value = null;
 	currentDetail.value = null;
+	currentFlowState.value = null;
 	try {
 	try {
-		const data = await fetchIqcInspBillDetail(row.id);
+		const [data] = await Promise.all([fetchIqcInspBillDetail(row.id), loadFlowState(row.id)]);
 		if (!data) {
 		if (!data) {
 			ElMessage.warning('未找到检验单详情');
 			ElMessage.warning('未找到检验单详情');
 			currentDetail.value = null;
 			currentDetail.value = null;
@@ -157,6 +201,47 @@ async function openDetail(row: IqcInspBillRow) {
 	}
 	}
 }
 }
 
 
+async function onSubmitResult(payload: IqcSubmitResultPayload) {
+	flowSubmitting.value = true;
+	try {
+		await submitIqcInspBillResult(payload);
+		ElMessage.success('检验结果已提交,转检验主管审核');
+		await refreshAfterAction();
+	} catch (e: any) {
+		ElMessage.error(e?.message || '提交检验结果失败');
+	} finally {
+		flowSubmitting.value = false;
+	}
+}
+
+async function onApprove() {
+	if (!currentId.value) return;
+	flowSubmitting.value = true;
+	try {
+		await approveIqcInspBill(currentId.value);
+		ElMessage.success('已通过,流程完成');
+		await refreshAfterAction();
+	} catch (e: any) {
+		ElMessage.error(e?.message || '审核通过失败');
+	} finally {
+		flowSubmitting.value = false;
+	}
+}
+
+async function onReject(comment: string) {
+	if (!currentId.value) return;
+	flowSubmitting.value = true;
+	try {
+		await rejectIqcInspBill(currentId.value, comment);
+		ElMessage.success('已退回检验员');
+		await refreshAfterAction();
+	} catch (e: any) {
+		ElMessage.error(e?.message || '退回失败');
+	} finally {
+		flowSubmitting.value = false;
+	}
+}
+
 async function loadList() {
 async function loadList() {
 	loading.value = true;
 	loading.value = true;
 	try {
 	try {

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

@@ -11,9 +11,9 @@
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <Copyright>Admin.NET</Copyright>
     <Copyright>Admin.NET</Copyright>
     <Description>Admin.NET ͨ��Ȩ�޿���ƽ̨</Description>
     <Description>Admin.NET ͨ��Ȩ�޿���ƽ̨</Description>
-    <AssemblyVersion>1.0.236</AssemblyVersion>
-    <FileVersion>1.0.236</FileVersion>
-    <Version>1.0.236</Version>
+    <AssemblyVersion>1.0.237</AssemblyVersion>
+    <FileVersion>1.0.237</FileVersion>
+    <Version>1.0.237</Version>
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>

+ 242 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/IqcInspBillFlowSeed.cs

@@ -0,0 +1,242 @@
+using System.Diagnostics;
+using System.Text.Json;
+using Admin.NET.Plugin.AiDOP.MaterialWarehouse;
+using Admin.NET.Plugin.ApprovalFlow;
+using Admin.NET.Plugin.ApprovalFlow.Service;
+using Yitter.IdGenerator;
+using ApprovalFlowEntity = Admin.NET.Plugin.ApprovalFlow.ApprovalFlow;
+
+namespace Admin.NET.Plugin.AiDOP.Infrastructure;
+
+/// <summary>
+/// S5 来料检验单 检验员+主管流程种子(幂等)。
+///
+/// 顺序:① 角色 ROLE_S5_IQC_INSPECTOR / ROLE_S5_IQC_SUPERVISOR ② UAT 成员绑定(superAdmin.NET 同绑两角色)
+/// ③ ApprovalBizType(S5_IQC_INSPBILL) ④ ApprovalFlow 已发布定义(start→N1_INSPECT→N2_SUP_REVIEW→end,节点绑角色)。
+/// 全部幂等:存在则跳过,不覆盖、不删除既有角色/流程。superAdmin.NET 不存在时仅告警不崩溃。
+/// </summary>
+public static class IqcInspBillFlowSeed
+{
+    public static void EnsureSeed(ISqlSugarClient db)
+    {
+        try
+        {
+            db.CodeFirst.InitTables(
+                typeof(ApprovalBizType),
+                typeof(ApprovalFlowEntity),
+                typeof(ApprovalFlowVersion));
+            EnsureRoles(db);
+            EnsureUatMembers(db);
+            EnsureBizType(db);
+            EnsurePublishedFlow(db);
+        }
+        catch (Exception ex)
+        {
+            Trace.TraceWarning("S5 IQC inspbill flow seed: " + ex);
+        }
+    }
+
+    private static void EnsureRoles(ISqlSugarClient db)
+    {
+        EnsureRole(db, IqcInspBillFlowConst.RoleInspectorId, IqcInspBillFlowConst.RoleInspectorCode, "来料检验员", 810);
+        EnsureRole(db, IqcInspBillFlowConst.RoleSupervisorId, IqcInspBillFlowConst.RoleSupervisorCode, "来料检验主管", 811);
+    }
+
+    private static void EnsureRole(ISqlSugarClient db, long id, string code, string name, int orderNo)
+    {
+        var exists = db.Queryable<SysRole>().ClearFilter().Any(x => x.Code == code);
+        if (exists) return;
+        db.Insertable(new SysRole
+        {
+            Id = id,
+            TenantId = SqlSugarConst.DefaultTenantId,
+            Name = name,
+            Code = code,
+            OrderNo = orderNo,
+            DataScope = DataScopeEnum.Self,
+            Status = StatusEnum.Enable,
+            Remark = "S5 来料检验流程角色",
+            CreateTime = DateTime.Now,
+        }).ExecuteCommand();
+    }
+
+    private static void EnsureUatMembers(ISqlSugarClient db)
+    {
+        var user = db.Queryable<SysUser>().ClearFilter()
+            .Where(x => x.Account == IqcInspBillFlowConst.UatAccount)
+            .First();
+        if (user == null)
+        {
+            Trace.TraceWarning($"S5 IQC flow seed: UAT 账号 {IqcInspBillFlowConst.UatAccount} 不存在,跳过成员绑定(阶段2 冒烟前须指定成员)");
+            return;
+        }
+        EnsureUserRole(db, user.Id, IqcInspBillFlowConst.RoleInspectorId);
+        EnsureUserRole(db, user.Id, IqcInspBillFlowConst.RoleSupervisorId);
+    }
+
+    private static void EnsureUserRole(ISqlSugarClient db, long userId, long roleId)
+    {
+        var exists = db.Queryable<SysUserRole>().ClearFilter().Any(x => x.UserId == userId && x.RoleId == roleId);
+        if (exists) return;
+        db.Insertable(new SysUserRole
+        {
+            Id = YitIdHelper.NextId(),
+            UserId = userId,
+            RoleId = roleId,
+        }).ExecuteCommand();
+    }
+
+    private static void EnsureBizType(ISqlSugarClient db)
+    {
+        var exists = db.Queryable<ApprovalBizType>().Any(x => x.Code == IqcInspBillFlowConst.BizType);
+        if (exists) return;
+        db.Insertable(new ApprovalBizType
+        {
+            Id = YitIdHelper.NextId(),
+            Code = IqcInspBillFlowConst.BizType,
+            Name = "来料检验单审批",
+            Remark = "S5 来料检验单 检验员→主管 审批流",
+            IsEnabled = true,
+            CreateTime = DateTime.Now,
+            UpdateTime = DateTime.Now,
+        }).ExecuteCommand();
+    }
+
+    private static void EnsurePublishedFlow(ISqlSugarClient db)
+    {
+        var orgIds = db.Queryable<SysOrg>()
+            .Select(x => x.Id)
+            .ToList()
+            .Where(x => x > 0)
+            .Distinct()
+            .ToList();
+        if (orgIds.Count == 0) orgIds.Add(0);
+
+        var now = DateTime.Now;
+        var flowJson = BuildFlowJson();
+        foreach (var orgId in orgIds)
+        {
+            var existing = db.Queryable<ApprovalFlowEntity>()
+                .Where(x => x.BizType == IqcInspBillFlowConst.BizType && x.OrgId == orgId && x.IsPublished && !x.IsDelete)
+                .OrderByDescending(x => x.Version)
+                .First();
+            if (existing != null) continue;
+
+            var flow = new ApprovalFlowEntity
+            {
+                Id = YitIdHelper.NextId(),
+                Code = $"S5IQC_{orgId}",
+                Name = "来料检验单审批",
+                BizType = IqcInspBillFlowConst.BizType,
+                FormJson = "{}",
+                FlowJson = flowJson,
+                Status = 1,
+                Remark = "系统默认流程:开始-检验员检验-检验主管审核-结束",
+                Version = 1,
+                IsPublished = true,
+                OrgId = orgId,
+                CreateTime = now,
+                UpdateTime = now,
+            };
+            db.Insertable(flow).ExecuteCommand();
+            db.Insertable(new ApprovalFlowVersion
+            {
+                Id = YitIdHelper.NextId(),
+                FlowId = flow.Id,
+                Version = flow.Version,
+                FlowJson = flow.FlowJson,
+                FormJson = flow.FormJson,
+                PublishTime = now,
+                PublisherName = "系统初始化",
+                CreateTime = now,
+                UpdateTime = now,
+            }).ExecuteCommand();
+        }
+    }
+
+    private static string BuildFlowJson()
+    {
+        var item = new ApprovalFlowItem
+        {
+            Nodes = new List<ApprovalFlowNodeItem>
+            {
+                new()
+                {
+                    Id = "start",
+                    Type = "bpmn:startEvent",
+                    X = 100,
+                    Y = 120,
+                    Properties = new FlowProperties { NodeName = "开始" },
+                    Text = new FlowTextItem { X = 100, Y = 154, Value = "开始" },
+                },
+                new()
+                {
+                    Id = IqcInspBillFlowConst.NodeInspect,
+                    Type = "bpmn:userTask",
+                    X = 300,
+                    Y = 120,
+                    Properties = new FlowProperties
+                    {
+                        NodeName = "检验员检验",
+                        ApproverType = nameof(ApproverTypeEnum.Role),
+                        ApproverIds = IqcInspBillFlowConst.RoleInspectorId.ToString(),
+                        ApproverNames = "来料检验员",
+                        MultiApproveMode = "Any",
+                    },
+                    Text = new FlowTextItem { X = 300, Y = 154, Value = "检验员检验" },
+                },
+                new()
+                {
+                    Id = IqcInspBillFlowConst.NodeSupervisor,
+                    Type = "bpmn:userTask",
+                    X = 500,
+                    Y = 120,
+                    Properties = new FlowProperties
+                    {
+                        NodeName = "检验主管审核",
+                        ApproverType = nameof(ApproverTypeEnum.Role),
+                        ApproverIds = IqcInspBillFlowConst.RoleSupervisorId.ToString(),
+                        ApproverNames = "来料检验主管",
+                        MultiApproveMode = "Any",
+                    },
+                    Text = new FlowTextItem { X = 500, Y = 154, Value = "检验主管审核" },
+                },
+                new()
+                {
+                    Id = "end",
+                    Type = "bpmn:endEvent",
+                    X = 700,
+                    Y = 120,
+                    Properties = new FlowProperties { NodeName = "结束" },
+                    Text = new FlowTextItem { X = 700, Y = 154, Value = "结束" },
+                },
+            },
+            Edges = new List<ApprovalFlowEdgeItem>
+            {
+                BuildEdge("edge-start-n1", "start", IqcInspBillFlowConst.NodeInspect, 140, 120, 260, 120),
+                BuildEdge("edge-n1-n2", IqcInspBillFlowConst.NodeInspect, IqcInspBillFlowConst.NodeSupervisor, 340, 120, 460, 120),
+                BuildEdge("edge-n2-end", IqcInspBillFlowConst.NodeSupervisor, "end", 540, 120, 660, 120),
+            }
+        };
+        return JsonSerializer.Serialize(item);
+    }
+
+    private static ApprovalFlowEdgeItem BuildEdge(string id, string source, string target, float sx, float sy, float ex, float ey)
+    {
+        return new ApprovalFlowEdgeItem
+        {
+            Id = id,
+            Type = "bpmn:sequenceFlow",
+            SourceNodeId = source,
+            TargetNodeId = target,
+            StartPoint = new FlowEdgePointItem { X = sx, Y = sy },
+            EndPoint = new FlowEdgePointItem { X = ex, Y = ey },
+            Properties = new FlowProperties(),
+            PointsList = new List<FlowEdgePointItem>
+            {
+                new() { X = sx, Y = sy },
+                new() { X = ex, Y = ey },
+            },
+        };
+    }
+}

+ 90 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/MaterialWarehouse/Dto/IqcInspBillFlowDto.cs

@@ -0,0 +1,90 @@
+namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse.Dto;
+
+/// <summary>
+/// 检验员提交检验结果入参。写 qms_qcp_inspbill.pd/dhsl/bhgsl/clfs,并推进 ApprovalFlow N1→N2。
+/// </summary>
+public class IqcSubmitResultInput
+{
+    /// <summary>检验单 id(qms_qcp_inspbill.id,BizId)</summary>
+    public long Id { get; set; }
+
+    /// <summary>判定:0=合格 1=不合格</summary>
+    public int Pd { get; set; }
+
+    /// <summary>合格数量</summary>
+    public decimal Dhsl { get; set; }
+
+    /// <summary>不合格数量</summary>
+    public decimal Bhgsl { get; set; }
+
+    /// <summary>处理方式:0=让步接收 1=挑选 2=报废(pd=1 时必填)</summary>
+    public int? Clfs { get; set; }
+
+    /// <summary>提交说明(可选)</summary>
+    public string? Comment { get; set; }
+}
+
+/// <summary>检验主管通过入参。</summary>
+public class IqcSupervisorApproveInput
+{
+    /// <summary>检验单 id</summary>
+    public long Id { get; set; }
+
+    /// <summary>审批意见(可选)</summary>
+    public string? Comment { get; set; }
+}
+
+/// <summary>检验主管退回入参(回 N1,意见必填)。</summary>
+public class IqcSupervisorRejectInput
+{
+    /// <summary>检验单 id</summary>
+    public long Id { get; set; }
+
+    /// <summary>退回意见(必填)</summary>
+    public string? Comment { get; set; }
+}
+
+/// <summary>
+/// 检验单流程状态输出(只读,供前端按钮控制)。
+/// </summary>
+public class IqcFlowStateOutput
+{
+    /// <summary>BizType(S5_IQC_INSPBILL)</summary>
+    public string BizType { get; set; } = IqcInspBillFlowConst.BizType;
+
+    /// <summary>BizId(=检验单 id)</summary>
+    public long BizId { get; set; }
+
+    /// <summary>流程实例 id(未启动为 null)</summary>
+    public long? InstanceId { get; set; }
+
+    /// <summary>流程状态(NotStarted / Running / Approved / Rejected / Cancelled / Terminated)</summary>
+    public string FlowStatus { get; set; } = "NotStarted";
+
+    /// <summary>当前节点码(N1_INSPECT / N2_SUP_REVIEW,未启动/已结束为 null)</summary>
+    public string? CurrentNodeCode { get; set; }
+
+    /// <summary>当前节点名</summary>
+    public string? CurrentNodeName { get; set; }
+
+    /// <summary>当前节点角色</summary>
+    public string? CurrentAssigneeRole { get; set; }
+
+    /// <summary>当前用户是否可提交检验结果</summary>
+    public bool CanSubmitResult { get; set; }
+
+    /// <summary>当前用户是否可主管通过</summary>
+    public bool CanSupervisorApprove { get; set; }
+
+    /// <summary>当前用户是否可主管退回</summary>
+    public bool CanSupervisorReject { get; set; }
+
+    /// <summary>流程是否已完成/终态</summary>
+    public bool IsCompleted { get; set; }
+
+    /// <summary>更新时间</summary>
+    public DateTime? UpdatedAt { get; set; }
+
+    /// <summary>提示信息</summary>
+    public string? Message { get; set; }
+}

+ 91 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/MaterialWarehouse/IqcInspBillFlowBizHandler.cs

@@ -0,0 +1,91 @@
+using Admin.NET.Plugin.ApprovalFlow;
+using Admin.NET.Plugin.ApprovalFlow.Service;
+
+namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse;
+
+/// <summary>
+/// S5 来料检验单流程常量(BizType / 节点码 / 角色码 / 角色固定 Id / 字典)。集中避免散落字符串。
+/// </summary>
+public static class IqcInspBillFlowConst
+{
+    /// <summary>流程业务类型编码</summary>
+    public const string BizType = "S5_IQC_INSPBILL";
+
+    /// <summary>N1 检验员检验节点</summary>
+    public const string NodeInspect = "N1_INSPECT";
+
+    /// <summary>N2 检验主管审核节点</summary>
+    public const string NodeSupervisor = "N2_SUP_REVIEW";
+
+    /// <summary>检验员角色编码</summary>
+    public const string RoleInspectorCode = "ROLE_S5_IQC_INSPECTOR";
+
+    /// <summary>检验主管角色编码</summary>
+    public const string RoleSupervisorCode = "ROLE_S5_IQC_SUPERVISOR";
+
+    /// <summary>检验员角色固定 Id(种子与流程定义共用)</summary>
+    public const long RoleInspectorId = 1329915010001L;
+
+    /// <summary>检验主管角色固定 Id</summary>
+    public const long RoleSupervisorId = 1329915010002L;
+
+    /// <summary>UAT 冒烟绑定账号(同绑两角色,仅 UAT 自审)</summary>
+    public const string UatAccount = "superAdmin.NET";
+
+    // pd 判定:0=合格 1=不合格 | clfs 处理方式:0=让步接收 1=挑选 2=报废
+}
+
+/// <summary>
+/// S5 来料检验单审批流回调处理器(BizType=S5_IQC_INSPBILL)。
+///
+/// 本批(检验员+主管最小闭环)Handler 全部 no-op:不写 qms、不写库存、不发额外通知、不转 SQE。
+/// 检验结果字段 pd/dhsl/bhgsl/clfs 由 IqcInspBillFlowService.submit-result 在事务内写入;
+/// 流程状态由 ApprovalFlowInstance 承载,不回写 qms_qcp_inspbill。GetBizData 仅返回基础信息供流程日志/条件(本批线性流程无网关)。
+/// </summary>
+public class IqcInspBillFlowBizHandler : IFlowBizHandler, ITransient
+{
+    public string BizType => IqcInspBillFlowConst.BizType;
+
+    private readonly ISqlSugarClient _db;
+
+    public IqcInspBillFlowBizHandler(ISqlSugarClient db)
+    {
+        _db = db;
+    }
+
+    /// <summary>流程发起后回调:本批 no-op(不回写 qms)。</summary>
+    public Task OnFlowStarted(long bizId, long instanceId) => Task.CompletedTask;
+
+    /// <summary>单节点完成回调:本批 no-op。</summary>
+    public Task OnNodeCompleted(long bizId, long instanceId, string nodeId, string nodeName, long? approverUserId) => Task.CompletedTask;
+
+    /// <summary>流程结束回调:本批 no-op(不写 qms/FBILLSTATUS/FAUDITORID/库存)。审核结果由 ApprovalFlowInstance 状态承载。</summary>
+    public Task OnFlowCompleted(long bizId, long instanceId, FlowInstanceStatusEnum finalStatus, long? lastApproverId) => Task.CompletedTask;
+
+    /// <summary>返回检验单基础信息(本批线性流程无网关条件,仅供日志/展示)。</summary>
+    public async Task<Dictionary<string, object>> GetBizData(long bizId)
+    {
+        var rows = await _db.Ado.SqlQueryAsync<IqcInspBillFlowBizData>(
+            "SELECT FBILLNO AS BillNo, FMATERIALCFG AS MaterialCode, wlmc AS MaterialName, pd AS Pd, clfs AS Clfs FROM qms_qcp_inspbill WHERE id=@id LIMIT 1",
+            new List<SugarParameter> { new("@id", bizId) });
+        var r = rows.FirstOrDefault();
+        if (r == null) return new Dictionary<string, object>();
+        return new Dictionary<string, object>
+        {
+            ["billNo"] = r.BillNo ?? "",
+            ["materialCode"] = r.MaterialCode ?? "",
+            ["materialName"] = r.MaterialName ?? "",
+            ["pd"] = r.Pd ?? -1,
+            ["clfs"] = r.Clfs ?? -1,
+        };
+    }
+
+    private class IqcInspBillFlowBizData
+    {
+        public string? BillNo { get; set; }
+        public string? MaterialCode { get; set; }
+        public string? MaterialName { get; set; }
+        public long? Pd { get; set; }
+        public long? Clfs { get; set; }
+    }
+}

+ 264 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/MaterialWarehouse/IqcInspBillFlowService.cs

@@ -0,0 +1,264 @@
+using Admin.NET.Plugin.AiDOP.MaterialWarehouse.Dto;
+using Admin.NET.Plugin.ApprovalFlow;
+using Admin.NET.Plugin.ApprovalFlow.Service;
+
+namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse;
+
+/// <summary>
+/// S5 来料检验单 检验员+主管最小审批流服务(S5-IQC-INSPBILL-INSPECTOR-SUPERVISOR-FLOW-1)。
+///
+/// N1_INSPECT 检验员录入 pd/dhsl/bhgsl/clfs → 提交 → N2_SUP_REVIEW 检验主管审核(通过=CLOSED / 退回=回 N1)。
+/// 复用 ApprovalFlow 引擎扩展点(StartFlow/Approve/ReturnToPrev + IFlowBizHandler),不改 ApprovalFlow 核心。
+/// BizType=S5_IQC_INSPBILL,BizId=qms_qcp_inspbill.id,BizCode=FBILLNO。反查用 ApprovalFlowInstance(BizType,BizId),inspbill 不加列。
+/// submit-result 仅写 qms_qcp_inspbill.pd/dhsl/bhgsl/clfs;approve/reject 不写 qms。不写库存、不入库、不做 SQE/仓储通知。
+/// 正常鉴权(非 AllowAnonymous);权限以当前用户 pending task 归属为准。
+/// </summary>
+[ApiDescriptionSettings(Order = 310, Description = "来料检验单流程")]
+[Route("api/S5IqcInspBillFlow")]
+[NonUnify]
+public class IqcInspBillFlowService : IDynamicApiController, ITransient
+{
+    private readonly ISqlSugarClient _db;
+    private readonly FlowEngineService _flowEngine;
+    private readonly UserManager _userManager;
+
+    public IqcInspBillFlowService(ISqlSugarClient db, FlowEngineService flowEngine, UserManager userManager)
+    {
+        _db = db;
+        _flowEngine = flowEngine;
+        _userManager = userManager;
+    }
+
+    /// <summary>
+    /// 检验单流程状态(只读,供前端按钮控制)。
+    /// </summary>
+    [DisplayName("来料检验单流程状态")]
+    [HttpGet("state")]
+    public async Task<IqcFlowStateOutput> GetState([FromQuery] long id)
+    {
+        await EnsureBillExistsAsync(id);
+        return await BuildStateAsync(id);
+    }
+
+    /// <summary>
+    /// 检验员提交检验结果(写 pd/dhsl/bhgsl/clfs + 推进 N1→N2)。
+    /// </summary>
+    [DisplayName("检验员提交检验结果")]
+    [HttpPost("submit-result")]
+    public async Task<IqcFlowStateOutput> SubmitResult([FromBody] IqcSubmitResultInput input)
+    {
+        var billNo = await EnsureBillExistsAsync(input.Id);
+
+        // 参数校验
+        if (input.Pd != 0 && input.Pd != 1) throw Oops.Oh("判定 pd 只能为 0(合格) 或 1(不合格)");
+        if (input.Dhsl < 0) throw Oops.Oh("合格数量不能为负");
+        if (input.Bhgsl < 0) throw Oops.Oh("不合格数量不能为负");
+        if (input.Pd == 0)
+        {
+            if (input.Bhgsl != 0) throw Oops.Oh("判定合格时不合格数量必须为 0");
+        }
+        else // pd == 1
+        {
+            if (input.Bhgsl <= 0) throw Oops.Oh("判定不合格时不合格数量必须大于 0");
+            if (input.Clfs is not (0 or 1 or 2)) throw Oops.Oh("判定不合格时处理方式 clfs 必填且只能为 0(让步接收)/1(挑选)/2(报废)");
+        }
+
+        var inst = await GetLatestInstanceAsync(input.Id);
+        if (inst != null && inst.Status != FlowInstanceStatusEnum.Running)
+            throw Oops.Oh("该检验单流程已结束,无法再提交检验结果");
+
+        var tran = await _db.AsTenant().UseTranAsync(async () =>
+        {
+            // 无运行中实例 → 起流程(自动生成 N1 任务给检验员角色成员)
+            long instanceId;
+            if (inst == null)
+            {
+                instanceId = await _flowEngine.StartFlow(new StartFlowInput
+                {
+                    BizType = IqcInspBillFlowConst.BizType,
+                    BizId = input.Id,
+                    BizNo = billNo,
+                    Title = $"来料检验单 {billNo}",
+                    Comment = input.Comment,
+                });
+            }
+            else
+            {
+                instanceId = inst.Id;
+            }
+
+            var myTask = await GetMyPendingTaskAsync(instanceId);
+            if (myTask == null)
+                throw Oops.Oh("当前用户没有待处理的检验任务(需检验员角色成员)");
+            if (myTask.NodeId != IqcInspBillFlowConst.NodeInspect)
+                throw Oops.Oh("当前不在检验录入节点,无法提交检验结果");
+
+            // 写检验结果(仅 pd/dhsl/bhgsl/clfs)
+            await _db.Ado.ExecuteCommandAsync(
+                "UPDATE qms_qcp_inspbill SET pd=@pd, dhsl=@dhsl, bhgsl=@bhgsl, clfs=@clfs WHERE id=@id",
+                new List<SugarParameter>
+                {
+                    new("@pd", input.Pd),
+                    new("@dhsl", input.Dhsl),
+                    new("@bhgsl", input.Bhgsl),
+                    new("@clfs", input.Pd == 1 ? input.Clfs : null),
+                    new("@id", input.Id),
+                });
+
+            // 推进 N1 → N2
+            await _flowEngine.Approve(myTask.Id, input.Comment);
+        });
+        if (!tran.IsSuccess) throw tran.ErrorException;
+
+        return await BuildStateAsync(input.Id);
+    }
+
+    /// <summary>
+    /// 检验主管通过(N2 → 完成)。
+    /// </summary>
+    [DisplayName("检验主管通过")]
+    [HttpPost("supervisor-approve")]
+    public async Task<IqcFlowStateOutput> SupervisorApprove([FromBody] IqcSupervisorApproveInput input)
+    {
+        await EnsureBillExistsAsync(input.Id);
+        var (inst, myTask) = await RequireSupervisorTaskAsync(input.Id);
+
+        var tran = await _db.AsTenant().UseTranAsync(async () =>
+        {
+            await _flowEngine.Approve(myTask.Id, input.Comment);
+        });
+        if (!tran.IsSuccess) throw tran.ErrorException;
+
+        return await BuildStateAsync(input.Id);
+    }
+
+    /// <summary>
+    /// 检验主管退回(N2 → 回 N1,意见必填)。
+    /// </summary>
+    [DisplayName("检验主管退回")]
+    [HttpPost("supervisor-reject")]
+    public async Task<IqcFlowStateOutput> SupervisorReject([FromBody] IqcSupervisorRejectInput input)
+    {
+        if (string.IsNullOrWhiteSpace(input.Comment))
+            throw Oops.Oh("退回必须填写意见");
+
+        await EnsureBillExistsAsync(input.Id);
+        var (inst, myTask) = await RequireSupervisorTaskAsync(input.Id);
+
+        var tran = await _db.AsTenant().UseTranAsync(async () =>
+        {
+            await _flowEngine.ReturnToPrev(myTask.Id, input.Comment);
+        });
+        if (!tran.IsSuccess) throw tran.ErrorException;
+
+        return await BuildStateAsync(input.Id);
+    }
+
+    // ───────────────────────── helpers ─────────────────────────
+
+    /// <summary>校验检验单存在并返回 FBILLNO。</summary>
+    private async Task<string> EnsureBillExistsAsync(long id)
+    {
+        if (id <= 0) throw Oops.Oh("检验单 id 非法");
+        var rows = await _db.Ado.SqlQueryAsync<string>(
+            "SELECT FBILLNO FROM qms_qcp_inspbill WHERE id=@id LIMIT 1",
+            new List<SugarParameter> { new("@id", id) });
+        if (rows.Count == 0) throw Oops.Oh("检验单不存在");
+        return rows[0] ?? id.ToString();
+    }
+
+    /// <summary>取该检验单最新流程实例(BizType+BizId,跨数据范围)。</summary>
+    private async Task<ApprovalFlowInstance?> GetLatestInstanceAsync(long bizId)
+    {
+        return await _db.Queryable<ApprovalFlowInstance>()
+            .ClearFilter()
+            .Where(x => x.BizType == IqcInspBillFlowConst.BizType && x.BizId == bizId)
+            .OrderByDescending(x => x.Id)
+            .FirstAsync();
+    }
+
+    /// <summary>取当前用户在指定实例下的待办任务。</summary>
+    private async Task<ApprovalFlowTask?> GetMyPendingTaskAsync(long instanceId)
+    {
+        var userId = _userManager.UserId;
+        return await _db.Queryable<ApprovalFlowTask>()
+            .ClearFilter()
+            .Where(x => x.InstanceId == instanceId && x.AssigneeId == userId && x.Status == FlowTaskStatusEnum.Pending)
+            .FirstAsync();
+    }
+
+    /// <summary>校验并返回主管 N2 待办任务。</summary>
+    private async Task<(ApprovalFlowInstance inst, ApprovalFlowTask task)> RequireSupervisorTaskAsync(long bizId)
+    {
+        var inst = await GetLatestInstanceAsync(bizId);
+        if (inst == null) throw Oops.Oh("该检验单尚未发起流程");
+        if (inst.Status != FlowInstanceStatusEnum.Running) throw Oops.Oh("该检验单流程已结束");
+        var myTask = await GetMyPendingTaskAsync(inst.Id);
+        if (myTask == null) throw Oops.Oh("当前用户没有待处理的审核任务(需检验主管角色成员)");
+        if (myTask.NodeId != IqcInspBillFlowConst.NodeSupervisor) throw Oops.Oh("当前不在主管审核节点");
+        return (inst, myTask);
+    }
+
+    /// <summary>当前用户是否具备指定角色(跨数据范围)。</summary>
+    private async Task<bool> HasRoleAsync(long roleId)
+    {
+        var userId = _userManager.UserId;
+        return await _db.Queryable<SysUserRole>()
+            .ClearFilter()
+            .AnyAsync(x => x.UserId == userId && x.RoleId == roleId);
+    }
+
+    /// <summary>组装流程状态输出。</summary>
+    private async Task<IqcFlowStateOutput> BuildStateAsync(long bizId)
+    {
+        var output = new IqcFlowStateOutput { BizId = bizId };
+        var inst = await GetLatestInstanceAsync(bizId);
+
+        if (inst == null)
+        {
+            output.FlowStatus = "NotStarted";
+            output.CanSubmitResult = await HasRoleAsync(IqcInspBillFlowConst.RoleInspectorId);
+            output.Message = output.CanSubmitResult ? "待检验员录入结果" : "尚未发起流程";
+            return output;
+        }
+
+        output.InstanceId = inst.Id;
+        output.FlowStatus = inst.Status.ToString();
+        output.UpdatedAt = inst.EndTime ?? inst.StartTime;
+
+        if (inst.Status != FlowInstanceStatusEnum.Running)
+        {
+            output.IsCompleted = true;
+            output.Message = inst.Status == FlowInstanceStatusEnum.Approved ? "流程已完成(主管审核通过)" : $"流程已结束({inst.Status})";
+            return output;
+        }
+
+        output.CurrentNodeCode = inst.CurrentNodeId;
+        var myTask = await GetMyPendingTaskAsync(inst.Id);
+        if (myTask != null)
+        {
+            output.CurrentNodeName = myTask.NodeName;
+            if (myTask.NodeId == IqcInspBillFlowConst.NodeInspect)
+            {
+                output.CurrentAssigneeRole = IqcInspBillFlowConst.RoleInspectorCode;
+                output.CanSubmitResult = true;
+                output.Message = "待当前用户录入检验结果";
+            }
+            else if (myTask.NodeId == IqcInspBillFlowConst.NodeSupervisor)
+            {
+                output.CurrentAssigneeRole = IqcInspBillFlowConst.RoleSupervisorCode;
+                output.CanSupervisorApprove = true;
+                output.CanSupervisorReject = true;
+                output.Message = "待当前用户主管审核";
+            }
+        }
+        else
+        {
+            output.CurrentAssigneeRole = inst.CurrentNodeId == IqcInspBillFlowConst.NodeInspect
+                ? IqcInspBillFlowConst.RoleInspectorCode
+                : IqcInspBillFlowConst.RoleSupervisorCode;
+            output.Message = "流程进行中(当前用户无待办)";
+        }
+        return output;
+    }
+}

+ 1 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Startup.cs

@@ -78,6 +78,7 @@ public class Startup : AppStartup
             foreach (var tenantId in kpiTenantIds.Where(x => x > 0))
             foreach (var tenantId in kpiTenantIds.Where(x => x > 0))
                 AidopKpiMasterSeed.EnsureSeed(dbS4, tenantId!.Value);
                 AidopKpiMasterSeed.EnsureSeed(dbS4, tenantId!.Value);
             AidopImprovementApprovalSeed.EnsureSeed(dbS4);
             AidopImprovementApprovalSeed.EnsureSeed(dbS4);
+            Admin.NET.Plugin.AiDOP.Infrastructure.IqcInspBillFlowSeed.EnsureSeed(dbS4);
         }
         }
         catch (Exception ex)
         catch (Exception ex)
         {
         {