ソースを参照

feat(s7): FQC检验任务列表改造为检验员工作台(认领→生成→录入),参考IQC,DDL-free

- 新增 FqcTaskEntryService:list(活源 qms_fqcbj + JOIN 检验单拿 inspBillId) / claim / assign / priority(正常·紧急)
- 生成检验单加认领门(非认领人拒绝),复用 FqcApplyService.GenerateInspection 检规快照,生成后置检验中
- 任务列表 fqcInspectionTaskList 重构为双 tab:待检任务(多选+认领/检验员调配/优先级调整,去掉检验完成;行内 fixed-right 条件按钮 生成检验单↔录入检验) + 检验单(复用结果列表+详情抽屉+流程)
- 成品报检 fqcApplyList 行只留查看(生成/录入移至任务列表)
- 优先级与报检检验优先级同域(正常/紧急,非IQC数字,避免 yxj 混型);版本 server 1.0.369 / Web 2.4.303
YY968XX 2 日 前
コミット
eee233c903

+ 1 - 1
Web/package.json

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

+ 76 - 0
Web/src/views/aidop/s7/api/fqcTaskEntry.ts

@@ -0,0 +1,76 @@
+import service from '/@/utils/request';
+import type { FqcPaged } from './fqcInspBill';
+
+// ── S7 FQC 检验任务(报检)操作:认领 / 检验员调配 / 优先级调整 / 生成检验单(认领后)──
+export interface FqcTaskEntryRow {
+	/** 报检 id(qms_fqcbj.id) */
+	id: number;
+	/** 报检单号 FBILLNO */
+	applyBillNo?: string;
+	/** 生产指令单 sczld */
+	productionOrderNo?: string;
+	/** 申请时间 FAPPLYTIME */
+	applyTime?: string;
+	/** 物料编码 wlbm */
+	materialCode?: string;
+	/** 产品名称 cpmc */
+	productName?: string;
+	/** 产品型号 cpxh */
+	productModel?: string;
+	/** 生产批号 scph */
+	productionBatchNo?: string;
+	/** 指令单数量 sczldsl */
+	orderQty?: number;
+	/** 报检数量 sl */
+	applyQty?: number;
+	/** 申请人 FAPPLYUSER */
+	applicant?: string;
+	/** 备注 FCOMMENT */
+	remark?: string;
+	/** 检验优先级 yxj(正常/紧急) */
+	priority?: string;
+	/** 检验负责人 id jyfzr(用户 id 字符串) */
+	ownerId?: string;
+	/** 检验负责人姓名(水合) */
+	ownerName?: string;
+	/** 检验开始时间 jykssj */
+	inspectStartTime?: string;
+	/** 检验完成时间 jywcsj */
+	inspectFinishTime?: string;
+	/** 检验进度 FINSPECTSTATUS */
+	inspectProgress?: string;
+	/** 检验单 id(LEFT JOIN qms_qcpp_inspbill by lydjbh+hid;无则未生成) */
+	inspBillId?: number | null;
+	/** 检验单号 FBILLNO */
+	inspBillNo?: string;
+}
+
+/** 待检任务列表(活源 qms_fqcbj,带 inspBillId 联动) */
+export function fetchFqcTaskEntryList(params: any) {
+	return service.get<FqcPaged<FqcTaskEntryRow>>('/api/S7FqcTaskEntry/list', { params }).then((r) => r.data);
+}
+
+/** 认领:把选中报检负责人置为当前用户 */
+export function claimFqcTaskEntries(ids: number[]) {
+	return service.post('/api/S7FqcTaskEntry/claim', { ids }).then((r) => r.data);
+}
+
+/** 检验员调配:把选中报检负责人置为指定用户 */
+export function assignFqcTaskEntries(ids: number[], inspectorUserId: number) {
+	return service.post('/api/S7FqcTaskEntry/assign', { ids, inspectorUserId }).then((r) => r.data);
+}
+
+/** 优先级调整:正常/紧急 */
+export function adjustFqcTaskPriority(ids: number[], priority: string) {
+	return service.post('/api/S7FqcTaskEntry/priority', { ids, priority }).then((r) => r.data);
+}
+
+export interface FqcTaskGenerateOutput {
+	inspBillId: number;
+	inspBillNo: string;
+}
+
+/** 生成检验单(认领后;非认领人拒绝) */
+export function generateFqcInspBillByTask(entryId: number) {
+	return service.post<FqcTaskGenerateOutput>('/api/S7FqcTaskEntry/generate-inspbill', { entryId }).then((r) => r.data);
+}

+ 3 - 28
Web/src/views/aidop/s7/fqc/fqcApplyList.vue

@@ -21,11 +21,9 @@
 			<el-table-column prop="qty" label="报检数量" min-width="90" align="right" />
 			<el-table-column prop="inspectStatus" label="检验进度" min-width="90" />
 			<el-table-column prop="applicant" label="申请人" min-width="100" show-overflow-tooltip />
-			<el-table-column label="操作" width="230" fixed="right">
+			<el-table-column label="操作" width="90" fixed="right">
 				<template #default="{ row }">
 					<el-button type="primary" link @click="openDetail(row)">查看</el-button>
-					<el-button v-if="!row.inspBillId" type="success" link :loading="genId === row.id" @click="onGenerate(row)">生成检验单</el-button>
-					<el-button v-else type="warning" link @click="openInspection(row)">录入检验</el-button>
 				</template>
 			</el-table-column>
 			<template #empty><el-empty description="暂无报检单,点「新增报检」引用完工工单创建" /></template>
@@ -116,27 +114,19 @@
 
 <script setup lang="ts" name="aidopS7FqcApplyList">
 import { computed, onMounted, reactive, ref } from 'vue';
-import { useRoute, useRouter } from 'vue-router';
+import { useRoute } from 'vue-router';
 import { ElMessage } from 'element-plus';
 import AidopDemoShell from '/@/views/aidop/components/AidopDemoShell.vue';
-import { fetchFqcWorkOrders, fetchFqcApplyPage, fetchFqcApplyDetail, createFqcApply, generateFqcInspection, type FqcWorkOrderRow, type FqcApplyRow } from '../api/fqcApply';
+import { fetchFqcWorkOrders, fetchFqcApplyPage, fetchFqcApplyDetail, createFqcApply, type FqcWorkOrderRow, type FqcApplyRow } from '../api/fqcApply';
 import { createFqcApplyForm, fillFqcApplyFormFromWorkOrder, FQC_PRIORITY_OPTIONS } from './fqcApplyForm';
 
 const route = useRoute();
-const router = useRouter();
 const pageTitle = computed(() => (route.meta?.title as string) || '成品报检');
 
-// 进入检验录入(Phase 1E,独立隐藏路由 tab)
-function openInspection(row: FqcApplyRow & { inspBillId?: number }) {
-	if (!row.inspBillId) return;
-	router.push({ path: '/aidop/s7/fqc/insp-detail', query: { id: String(row.inspBillId) } });
-}
-
 const query = reactive({ billNo: '', materialCode: '', productionBatchNo: '', page: 1, pageSize: 10 });
 const rows = ref<FqcApplyRow[]>([]);
 const total = ref(0);
 const loading = ref(false);
-const genId = ref<number | null>(null); // 生成中的行 id(前端防双击,非幂等防线——真正幂等在 Service+DB UNIQUE)
 
 async function loadList() {
 	loading.value = true;
@@ -220,21 +210,6 @@ function pickWo(row: FqcWorkOrderRow) {
 	woVisible.value = false;
 }
 
-// 生成检验单(Phase 1D,仅生成+提示,不做录入 1E)
-async function onGenerate(row: FqcApplyRow) {
-	if (!row?.id || genId.value) return;
-	genId.value = row.id;
-	try {
-		const res = await generateFqcInspection({ applyId: row.id });
-		if (res.created) ElMessage.success(`已生成检验单 ${res.billNo}`);
-		else ElMessage.info(`该报检已生成检验单 ${res.billNo}`);
-		loadList();
-	} catch (e: any) {
-		ElMessage.error(e?.message || '生成检验单失败');
-	} finally {
-		genId.value = null;
-	}
-}
 
 // 详情
 const detailVisible = ref(false);

+ 415 - 144
Web/src/views/aidop/s7/fqc/fqcInspectionTaskList.vue

@@ -1,113 +1,202 @@
 <template>
-	<AidopDemoShell :title="pageTitle" subtitle="只读列表">
-		<el-form :inline="true" :model="query" class="mb12" @submit.prevent>
-			<el-form-item label="生产指令单">
-				<el-input v-model="query.productionOrderNo" clearable style="width: 200px" />
-			</el-form-item>
-			<el-form-item label="生产批号">
-				<el-input v-model="query.productionBatchNo" clearable style="width: 200px" />
-			</el-form-item>
-			<el-form-item label="物料编码">
-				<el-input v-model="query.materialCode" clearable style="width: 200px" />
-			</el-form-item>
-			<el-form-item label="申请时间">
-				<el-date-picker
-					v-model="applyTimeRange"
-					type="daterange"
-					value-format="YYYY-MM-DD"
-					range-separator="-"
-					start-placeholder="开始日期"
-					end-placeholder="结束日期"
-					unlink-panels
-					style="width: 260px"
-				/>
-			</el-form-item>
-			<el-form-item label="检验进度">
-				<el-select v-model="query.inspectProgress" placeholder="全部" clearable style="width: 160px">
-					<el-option label="未检验" value="未检验" />
-					<el-option label="检验中" value="检验中" />
-					<el-option label="检验完成" value="检验完成" />
-				</el-select>
-			</el-form-item>
-			<el-form-item label="检验优先级">
-				<el-input v-model="query.priority" clearable style="width: 160px" />
-			</el-form-item>
-			<el-form-item label="检验负责人">
-				<el-input v-model="query.inspector" clearable style="width: 160px" />
-			</el-form-item>
-			<el-form-item>
-				<el-button type="primary" @click="doSearch">查询</el-button>
-				<el-button @click="resetQuery">重置</el-button>
-			</el-form-item>
-		</el-form>
-
-		<!-- 本批只读:动作按钮保留占位但禁用,不接真实写动作 -->
-		<div class="mb12 actions">
-			<el-tooltip content="本批为只读列表,暂不开放" placement="top">
-				<span><el-button disabled>检验完成</el-button></span>
-			</el-tooltip>
-			<el-tooltip content="本批为只读列表,暂不开放" placement="top">
-				<span><el-button disabled>认领</el-button></span>
-			</el-tooltip>
-			<el-tooltip content="本批为只读列表,暂不开放" placement="top">
-				<span><el-button disabled>检验员调配</el-button></span>
-			</el-tooltip>
-			<el-tooltip content="本批为只读列表,暂不开放" placement="top">
-				<span><el-button disabled>优先级调整</el-button></span>
-			</el-tooltip>
-		</div>
-
-		<el-table :data="rows" row-key="id" v-loading="loading" border stripe>
-			<el-table-column prop="productionOrderNo" label="生产指令单" min-width="150" show-overflow-tooltip resizable />
-			<el-table-column prop="applyTime" label="申请时间" min-width="170" resizable>
-				<template #default="{ row }">{{ fmtDateTime(row.applyTime) }}</template>
-			</el-table-column>
-			<el-table-column prop="materialCode" label="物料编码" min-width="140" show-overflow-tooltip resizable />
-			<el-table-column prop="productName" label="产品名称" min-width="160" show-overflow-tooltip resizable />
-			<el-table-column prop="productModel" label="产品型号" min-width="140" show-overflow-tooltip resizable />
-			<el-table-column prop="productionBatchNo" label="生产批号" min-width="140" show-overflow-tooltip resizable />
-			<el-table-column prop="orderQty" label="指令单数量" min-width="120" resizable />
-			<el-table-column prop="applicant" label="申请人" min-width="120" show-overflow-tooltip resizable />
-			<el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip resizable />
-			<el-table-column prop="priority" label="检验优先级" min-width="110" resizable />
-			<el-table-column prop="inspectStartTime" label="检验开始时间" min-width="170" resizable>
-				<template #default="{ row }">{{ fmtDateTime(row.inspectStartTime) }}</template>
-			</el-table-column>
-			<el-table-column prop="inspectFinishTime" label="检验完成时间" min-width="170" resizable>
-				<template #default="{ row }">{{ fmtDateTime(row.inspectFinishTime) }}</template>
-			</el-table-column>
-			<el-table-column prop="inspector" label="检验负责人" min-width="120" show-overflow-tooltip resizable />
-			<el-table-column prop="inspectProgress" label="检验进度" min-width="110" resizable />
-			<template #empty>
-				<el-empty description="暂无数据" />
+	<AidopDemoShell :title="pageTitle" subtitle="认领 → 生成检验单 → 录入检验(提交转主管审核)">
+		<el-tabs v-model="activeTab" @tab-change="onTabChange">
+			<!-- ===== 待检任务 ===== -->
+			<el-tab-pane label="待检任务" name="entry">
+				<el-form :inline="true" :model="entryQuery" class="mb12" @submit.prevent>
+					<el-form-item label="生产指令单"><el-input v-model="entryQuery.productionOrderNo" clearable style="width: 180px" /></el-form-item>
+					<el-form-item label="生产批号"><el-input v-model="entryQuery.productionBatchNo" clearable style="width: 160px" /></el-form-item>
+					<el-form-item label="物料编码"><el-input v-model="entryQuery.materialCode" clearable style="width: 160px" /></el-form-item>
+					<el-form-item label="申请时间">
+						<el-date-picker v-model="applyTimeRange" type="daterange" value-format="YYYY-MM-DD" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" unlink-panels style="width: 260px" />
+					</el-form-item>
+					<el-form-item label="检验进度">
+						<el-select v-model="entryQuery.inspectProgress" placeholder="全部" clearable style="width: 140px">
+							<el-option label="未检验" value="未检验" />
+							<el-option label="检验中" value="检验中" />
+							<el-option label="检验完成" value="检验完成" />
+						</el-select>
+					</el-form-item>
+					<el-form-item label="检验优先级">
+						<el-select v-model="entryQuery.priority" placeholder="全部" clearable style="width: 120px">
+							<el-option label="正常" value="正常" />
+							<el-option label="紧急" value="紧急" />
+						</el-select>
+					</el-form-item>
+					<el-form-item label="检验负责人"><el-input v-model="entryQuery.inspector" clearable style="width: 140px" /></el-form-item>
+					<el-form-item>
+						<el-button type="primary" @click="doEntrySearch">查询</el-button>
+						<el-button @click="resetEntryQuery">重置</el-button>
+					</el-form-item>
+				</el-form>
+
+				<div class="mb12 actions">
+					<el-button type="primary" :disabled="!selectedEntryIds.length" @click="onClaim">认领</el-button>
+					<el-button :disabled="!selectedEntryIds.length" @click="openAssign">检验员调配</el-button>
+					<el-button :disabled="!selectedEntryIds.length" @click="openPriority">优先级调整</el-button>
+				</div>
+
+				<el-table :data="entryRows" row-key="id" v-loading="entryLoading" border stripe @selection-change="onEntrySelection">
+					<el-table-column type="selection" width="48" />
+					<el-table-column prop="applyBillNo" label="报检单号" min-width="160" show-overflow-tooltip resizable />
+					<el-table-column prop="productionOrderNo" label="生产指令单" min-width="130" show-overflow-tooltip resizable />
+					<el-table-column prop="materialCode" label="物料编码" min-width="120" show-overflow-tooltip resizable />
+					<el-table-column prop="productName" label="产品名称" min-width="150" show-overflow-tooltip resizable />
+					<el-table-column prop="productModel" label="产品型号" min-width="120" show-overflow-tooltip resizable />
+					<el-table-column prop="productionBatchNo" label="生产批号" min-width="120" show-overflow-tooltip resizable />
+					<el-table-column prop="orderQty" label="指令单数量" min-width="100" align="right" resizable />
+					<el-table-column prop="applyQty" label="报检数量" min-width="90" align="right" resizable />
+					<el-table-column prop="priority" label="检验优先级" min-width="100" resizable>
+						<template #default="{ row }">
+							<el-tag v-if="row.priority === '紧急'" type="danger" size="small">紧急</el-tag>
+							<el-tag v-else-if="row.priority === '正常'" type="info" size="small">正常</el-tag>
+							<span v-else>{{ row.priority || '' }}</span>
+						</template>
+					</el-table-column>
+					<el-table-column prop="ownerName" label="检验负责人" min-width="110" show-overflow-tooltip resizable>
+						<template #default="{ row }">{{ row.ownerName || '' }}</template>
+					</el-table-column>
+					<el-table-column prop="applicant" label="申请人" min-width="100" show-overflow-tooltip resizable />
+					<el-table-column prop="applyTime" label="申请时间" min-width="160" resizable>
+						<template #default="{ row }">{{ fmtDateTime(row.applyTime) }}</template>
+					</el-table-column>
+					<el-table-column prop="inspectStartTime" label="检验开始时间" min-width="160" resizable>
+						<template #default="{ row }">{{ fmtDateTime(row.inspectStartTime) }}</template>
+					</el-table-column>
+					<el-table-column prop="inspectProgress" label="检验进度" min-width="100" resizable />
+					<el-table-column prop="inspBillNo" label="检验单号" min-width="150" show-overflow-tooltip resizable />
+					<el-table-column label="操作" width="130" fixed="right">
+						<template #default="{ row }">
+							<el-button v-if="!row.inspBillId" type="success" link :loading="generatingId === row.id" @click="onGenerate(row)">生成检验单</el-button>
+							<el-button v-else type="warning" link @click="openInspection(row)">录入检验</el-button>
+						</template>
+					</el-table-column>
+					<template #empty><el-empty description="暂无待检任务" /></template>
+				</el-table>
+
+				<div class="pager">
+					<el-pagination v-model:current-page="entryQuery.page" v-model:page-size="entryQuery.pageSize" :total="entryTotal" :page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next" @current-change="loadEntryList" @size-change="loadEntryList" />
+				</div>
+			</el-tab-pane>
+
+			<!-- ===== 检验单 ===== -->
+			<el-tab-pane label="检验单" name="bill">
+				<el-form :inline="true" :model="billQuery" class="mb12" @submit.prevent>
+					<el-form-item label="单据编号"><el-input v-model="billQuery.billNo" clearable style="width: 180px" /></el-form-item>
+					<el-form-item label="生产批号"><el-input v-model="billQuery.productionBatchNo" clearable style="width: 160px" /></el-form-item>
+					<el-form-item label="生产指令单"><el-input v-model="billQuery.productionOrderNo" clearable style="width: 160px" /></el-form-item>
+					<el-form-item label="物料编码"><el-input v-model="billQuery.materialCode" clearable style="width: 160px" /></el-form-item>
+					<el-form-item>
+						<el-button type="primary" @click="doBillSearch">查询</el-button>
+						<el-button @click="resetBillQuery">重置</el-button>
+					</el-form-item>
+				</el-form>
+
+				<el-table :data="billRows" row-key="id" v-loading="billLoading" border stripe>
+					<el-table-column prop="billNo" label="单据编号" min-width="150" show-overflow-tooltip resizable />
+					<el-table-column prop="inspectTime" label="检验时间" min-width="160" resizable>
+						<template #default="{ row }">{{ fmtDateTime(row.inspectTime) }}</template>
+					</el-table-column>
+					<el-table-column prop="productionBatchNo" label="生产批号" min-width="130" show-overflow-tooltip resizable />
+					<el-table-column prop="materialCode" label="物料编码" min-width="130" show-overflow-tooltip resizable />
+					<el-table-column prop="materialName" label="物料名称" min-width="150" show-overflow-tooltip resizable />
+					<el-table-column prop="spec" label="规格型号" min-width="130" show-overflow-tooltip resizable />
+					<el-table-column prop="sourceBillNo" label="来源单据编号" min-width="150" show-overflow-tooltip resizable />
+					<el-table-column prop="productionOrderNo" label="生产指令单" min-width="130" show-overflow-tooltip resizable />
+					<el-table-column prop="judgment" label="判定" min-width="90" resizable />
+					<el-table-column prop="qualifiedQty" label="合格数量" min-width="100" align="right" resizable />
+					<el-table-column prop="unqualifiedQty" label="不合格数量" min-width="110" align="right" resizable />
+					<el-table-column label="操作" width="90" fixed="right">
+						<template #default="{ row }"><el-button link type="primary" @click="openDetail(row)">查看</el-button></template>
+					</el-table-column>
+					<template #empty><el-empty description="暂无检验单" /></template>
+				</el-table>
+
+				<div class="pager">
+					<el-pagination v-model:current-page="billQuery.page" v-model:page-size="billQuery.pageSize" :total="billTotal" :page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next" @current-change="loadBillList" @size-change="loadBillList" />
+				</div>
+			</el-tab-pane>
+		</el-tabs>
+
+		<FqcInspBillDetailDrawer
+			v-model="detailVisible"
+			:detail="detail"
+			:loading="detailLoading"
+			:flow-state="flowState"
+			:submitting="flowSubmitting"
+			@submit-result="onFlowSubmit"
+			@approve="onFlowApprove"
+			@reject="onFlowReject"
+			@qe-submit="onFlowQe"
+		/>
+
+		<el-dialog v-model="assignVisible" title="检验员调配" width="420px">
+			<el-form label-width="100px">
+				<el-form-item label="用户 Id"><el-input v-model="assignUserId" placeholder="填写检验员用户 Id" /></el-form-item>
+			</el-form>
+			<template #footer>
+				<el-button @click="assignVisible = false">取消</el-button>
+				<el-button type="primary" @click="onAssign">确定</el-button>
 			</template>
-		</el-table>
-
-		<div class="pager">
-			<el-pagination
-				v-model:current-page="query.page"
-				v-model:page-size="query.pageSize"
-				:total="total"
-				:page-sizes="[10, 20, 50]"
-				layout="total, sizes, prev, pager, next"
-				@current-change="loadList"
-				@size-change="loadList"
-			/>
-		</div>
+		</el-dialog>
+
+		<el-dialog v-model="priorityVisible" title="优先级调整" width="420px">
+			<el-form label-width="100px">
+				<el-form-item label="优先级">
+					<el-select v-model="priorityValue" style="width: 160px">
+						<el-option label="正常" value="正常" />
+						<el-option label="紧急" value="紧急" />
+					</el-select>
+				</el-form-item>
+			</el-form>
+			<template #footer>
+				<el-button @click="priorityVisible = false">取消</el-button>
+				<el-button type="primary" @click="onPriority">确定</el-button>
+			</template>
+		</el-dialog>
 	</AidopDemoShell>
 </template>
 
 <script setup lang="ts" name="aidopS7FqcTaskList">
 import { computed, onActivated, onMounted, reactive, ref } from 'vue';
-import { useRoute } from 'vue-router';
+import { useRoute, useRouter } from 'vue-router';
 import { ElMessage } from 'element-plus';
 import AidopDemoShell from '/@/views/aidop/components/AidopDemoShell.vue';
-import { fetchFqcTaskList, type FqcTaskRow } from '../api/fqcInspBill';
+import FqcInspBillDetailDrawer from './components/FqcInspBillDetailDrawer.vue';
+import { fetchFqcResultList, fetchFqcDetail, type FqcResultRow, type FqcDetail } from '../api/fqcInspBill';
+import {
+	fetchFqcTaskEntryList,
+	claimFqcTaskEntries,
+	assignFqcTaskEntries,
+	adjustFqcTaskPriority,
+	generateFqcInspBillByTask,
+	type FqcTaskEntryRow,
+} from '../api/fqcTaskEntry';
+import {
+	fetchFqcFlowState,
+	submitFqcResult,
+	fqcSupervisorApprove,
+	fqcSupervisorReject,
+	submitFqcQeDisposition,
+	type FqcFlowState,
+	type FqcSubmitResultPayload,
+	type FqcQeDispositionPayload,
+} from '../api/fqcInspBillFlow';
 
 const route = useRoute();
+const router = useRouter();
 const pageTitle = computed(() => (route.meta?.title as string) || 'FQC检验任务列表');
 
-const query = reactive({
+const activeTab = ref('entry');
+
+function fmtDateTime(v?: string | null) {
+	if (!v) return '';
+	const s = String(v);
+	return s.length > 19 ? s.slice(0, 19).replace('T', ' ') : s.replace('T', ' ');
+}
+
+// ── 待检任务 ──
+const entryQuery = reactive({
 	productionOrderNo: '',
 	productionBatchNo: '',
 	materialCode: '',
@@ -119,68 +208,250 @@ const query = reactive({
 	page: 1,
 	pageSize: 10,
 });
-
 const applyTimeRange = ref<[string, string] | null>(null);
+const entryLoading = ref(false);
+const entryRows = ref<FqcTaskEntryRow[]>([]);
+const entryTotal = ref(0);
+const selectedEntryIds = ref<number[]>([]);
+const generatingId = ref<number | null>(null);
 
-const loading = ref(false);
-const rows = ref<FqcTaskRow[]>([]);
-const total = ref(0);
+const assignVisible = ref(false);
+const assignUserId = ref('');
+const priorityVisible = ref(false);
+const priorityValue = ref<'正常' | '紧急'>('正常');
 
-function fmtDateTime(v?: string | null) {
-	if (!v) return '';
-	const s = String(v);
-	return s.length > 19 ? s.slice(0, 19).replace('T', ' ') : s.replace('T', ' ');
+function onEntrySelection(rowsSel: FqcTaskEntryRow[]) {
+	selectedEntryIds.value = rowsSel.map((r) => Number(r.id)).filter((x) => x > 0);
 }
 
-async function loadList() {
-	loading.value = true;
+async function loadEntryList() {
+	entryLoading.value = true;
 	try {
-		query.applyTimeStart = applyTimeRange.value?.[0] || '';
-		query.applyTimeEnd = applyTimeRange.value?.[1] || '';
-		const data = await fetchFqcTaskList({
-			productionOrderNo: query.productionOrderNo,
-			productionBatchNo: query.productionBatchNo,
-			materialCode: query.materialCode,
-			applyTimeStart: query.applyTimeStart,
-			applyTimeEnd: query.applyTimeEnd,
-			inspectProgress: query.inspectProgress,
-			priority: query.priority,
-			inspector: query.inspector,
-			page: query.page,
-			pageSize: query.pageSize,
-		});
-		rows.value = data.list || [];
-		total.value = data.total || 0;
+		entryQuery.applyTimeStart = applyTimeRange.value?.[0] || '';
+		entryQuery.applyTimeEnd = applyTimeRange.value?.[1] || '';
+		const data = await fetchFqcTaskEntryList({ ...entryQuery });
+		entryRows.value = data.list || [];
+		entryTotal.value = data.total || 0;
 	} catch (e: any) {
-		rows.value = [];
-		total.value = 0;
-		ElMessage.error(e?.message || '加载FQC检验任务列表失败');
+		entryRows.value = [];
+		entryTotal.value = 0;
+		ElMessage.error(e?.message || '加载待检任务失败');
 	} finally {
-		loading.value = false;
+		entryLoading.value = false;
 	}
 }
+function doEntrySearch() {
+	entryQuery.page = 1;
+	loadEntryList();
+}
+function resetEntryQuery() {
+	entryQuery.productionOrderNo = '';
+	entryQuery.productionBatchNo = '';
+	entryQuery.materialCode = '';
+	applyTimeRange.value = null;
+	entryQuery.applyTimeStart = '';
+	entryQuery.applyTimeEnd = '';
+	entryQuery.inspectProgress = '';
+	entryQuery.priority = '';
+	entryQuery.inspector = '';
+	entryQuery.page = 1;
+	loadEntryList();
+}
 
-function doSearch() {
-	query.page = 1;
-	loadList();
+async function onClaim() {
+	try {
+		await claimFqcTaskEntries(selectedEntryIds.value);
+		ElMessage.success('认领成功');
+		loadEntryList();
+	} catch (e: any) {
+		ElMessage.error(e?.message || '认领失败');
+	}
+}
+function openAssign() {
+	assignUserId.value = '';
+	assignVisible.value = true;
+}
+async function onAssign() {
+	const uid = Number(assignUserId.value);
+	if (!uid) {
+		ElMessage.warning('请填写有效的用户 Id');
+		return;
+	}
+	try {
+		await assignFqcTaskEntries(selectedEntryIds.value, uid);
+		ElMessage.success('调配成功');
+		assignVisible.value = false;
+		loadEntryList();
+	} catch (e: any) {
+		ElMessage.error(e?.message || '调配失败');
+	}
+}
+function openPriority() {
+	priorityValue.value = '正常';
+	priorityVisible.value = true;
+}
+async function onPriority() {
+	try {
+		await adjustFqcTaskPriority(selectedEntryIds.value, priorityValue.value);
+		ElMessage.success('优先级已更新');
+		priorityVisible.value = false;
+		loadEntryList();
+	} catch (e: any) {
+		ElMessage.error(e?.message || '调整失败');
+	}
 }
 
-function resetQuery() {
-	query.productionOrderNo = '';
-	query.productionBatchNo = '';
-	query.materialCode = '';
-	applyTimeRange.value = null;
-	query.applyTimeStart = '';
-	query.applyTimeEnd = '';
-	query.inspectProgress = '';
-	query.priority = '';
-	query.inspector = '';
-	query.page = 1;
-	loadList();
+async function onGenerate(row: FqcTaskEntryRow) {
+	if (!row?.id || generatingId.value) return;
+	generatingId.value = row.id;
+	try {
+		const res = await generateFqcInspBillByTask(row.id);
+		ElMessage.success(`已生成检验单 ${res.inspBillNo}`);
+		await loadEntryList();
+		if (res.inspBillId) router.push({ path: '/aidop/s7/fqc/insp-detail', query: { id: String(res.inspBillId) } });
+	} catch (e: any) {
+		ElMessage.error(e?.message || '生成检验单失败');
+	} finally {
+		generatingId.value = null;
+	}
+}
+function openInspection(row: FqcTaskEntryRow) {
+	if (!row.inspBillId) return;
+	router.push({ path: '/aidop/s7/fqc/insp-detail', query: { id: String(row.inspBillId) } });
+}
+
+// ── 检验单 ──
+const billQuery = reactive({ billNo: '', productionBatchNo: '', productionOrderNo: '', materialCode: '', page: 1, pageSize: 10 });
+const billLoading = ref(false);
+const billRows = ref<FqcResultRow[]>([]);
+const billTotal = ref(0);
+
+async function loadBillList() {
+	billLoading.value = true;
+	try {
+		const data = await fetchFqcResultList({ ...billQuery });
+		billRows.value = data.list || [];
+		billTotal.value = data.total || 0;
+	} catch (e: any) {
+		billRows.value = [];
+		billTotal.value = 0;
+		ElMessage.error(e?.message || '加载FQC检验单列表失败');
+	} finally {
+		billLoading.value = false;
+	}
+}
+function doBillSearch() {
+	billQuery.page = 1;
+	loadBillList();
+}
+function resetBillQuery() {
+	billQuery.billNo = '';
+	billQuery.productionBatchNo = '';
+	billQuery.productionOrderNo = '';
+	billQuery.materialCode = '';
+	billQuery.page = 1;
+	loadBillList();
 }
 
-onMounted(() => loadList());
-onActivated(() => loadList());
+// ── 检验单详情 + 流程 ──
+const detailVisible = ref(false);
+const detail = ref<FqcDetail | null>(null);
+const detailLoading = ref(false);
+const flowState = ref<FqcFlowState | null>(null);
+const flowSubmitting = ref(false);
+const currentDetailId = ref<number | null>(null);
+
+async function loadDetailAndState(id: number) {
+	detailLoading.value = true;
+	try {
+		const [d, s] = await Promise.all([fetchFqcDetail(id), fetchFqcFlowState(id)]);
+		detail.value = d;
+		flowState.value = s;
+	} catch (e: any) {
+		detail.value = null;
+		flowState.value = null;
+		ElMessage.error(e?.message || '加载FQC检验单详情失败');
+	} finally {
+		detailLoading.value = false;
+	}
+}
+async function openDetail(row: FqcResultRow) {
+	if (!row?.id) return;
+	currentDetailId.value = row.id;
+	detail.value = null;
+	flowState.value = null;
+	detailVisible.value = true;
+	await loadDetailAndState(row.id);
+}
+async function onFlowSubmit(payload: FqcSubmitResultPayload) {
+	flowSubmitting.value = true;
+	try {
+		await submitFqcResult(payload);
+		ElMessage.success('检验结果已提交');
+		if (currentDetailId.value) await loadDetailAndState(currentDetailId.value);
+		loadBillList();
+		loadEntryList();
+	} catch (e: any) {
+		ElMessage.error(e?.message || '提交检验结果失败');
+	} finally {
+		flowSubmitting.value = false;
+	}
+}
+async function onFlowApprove() {
+	if (!currentDetailId.value) return;
+	flowSubmitting.value = true;
+	try {
+		await fqcSupervisorApprove(currentDetailId.value);
+		ElMessage.success('已通过');
+		await loadDetailAndState(currentDetailId.value);
+		loadBillList();
+	} catch (e: any) {
+		ElMessage.error(e?.message || '审核通过失败');
+	} finally {
+		flowSubmitting.value = false;
+	}
+}
+async function onFlowReject(comment: string) {
+	if (!currentDetailId.value) return;
+	flowSubmitting.value = true;
+	try {
+		await fqcSupervisorReject(currentDetailId.value, comment);
+		ElMessage.success('已退回');
+		await loadDetailAndState(currentDetailId.value);
+		loadBillList();
+		loadEntryList();
+	} catch (e: any) {
+		ElMessage.error(e?.message || '退回失败');
+	} finally {
+		flowSubmitting.value = false;
+	}
+}
+async function onFlowQe(payload: FqcQeDispositionPayload) {
+	flowSubmitting.value = true;
+	try {
+		await submitFqcQeDisposition(payload);
+		ElMessage.success('QE 处置已提交');
+		if (currentDetailId.value) await loadDetailAndState(currentDetailId.value);
+		loadBillList();
+	} catch (e: any) {
+		ElMessage.error(e?.message || '提交 QE 处置失败');
+	} finally {
+		flowSubmitting.value = false;
+	}
+}
+
+function onTabChange(name: string | number) {
+	if (name === 'entry') loadEntryList();
+	else loadBillList();
+}
+
+onMounted(() => {
+	loadEntryList();
+	loadBillList();
+});
+onActivated(() => {
+	loadEntryList();
+});
 </script>
 
 <style scoped lang="scss">

+ 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.368</AssemblyVersion>
-    <FileVersion>1.0.368</FileVersion>
-    <Version>1.0.368</Version>
+    <AssemblyVersion>1.0.369</AssemblyVersion>
+    <FileVersion>1.0.369</FileVersion>
+    <Version>1.0.369</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 318 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/FinishedWarehouse/FqcTaskEntryService.cs

@@ -0,0 +1,318 @@
+using System.ComponentModel;
+using Admin.NET.Plugin.AiDOP.Infrastructure;
+
+namespace Admin.NET.Plugin.AiDOP.FinishedWarehouse;
+
+/// <summary>
+/// S7 FQC 成品检验任务(报检)操作:认领 / 检验员调配 / 优先级调整 / 生成检验单(带认领门)。
+/// 对齐 S5 IQC 任务列表范式(IqcTaskEntryService),但 FQC 报检 qms_fqcbj 是头级单(无明细分录),
+/// 认领/优先级/负责人直接落 qms_fqcbj(jyfzr/yxj/FINSPECTSTATUS/jykssj)。DDL-free,字段均已存在。
+/// 只写 qms_fqcbj 的任务归属/优先级/进度;检验单生成复用 FqcApplyService.GenerateInspection(检规快照逻辑不重复)。
+/// </summary>
+[ApiDescriptionSettings(Order = 344, Description = "FQC检验任务操作")]
+[Route("api/S7FqcTaskEntry")]
+[NonUnify]
+public class FqcTaskEntryService : IDynamicApiController, ITransient
+{
+    private readonly ISqlSugarClient _db;
+    private readonly UserManager _userManager;
+    private readonly FqcApplyService _fqcApply;
+
+    public FqcTaskEntryService(ISqlSugarClient db, UserManager userManager, FqcApplyService fqcApply)
+    {
+        _db = db;
+        _userManager = userManager;
+        _fqcApply = fqcApply;
+    }
+
+    private long ResolveTenantOrThrow() => AidopTenantScope.ResolveOrThrow(_userManager);
+
+    /// <summary>待检任务列表:活源 qms_fqcbj + LEFT JOIN 检验单(qms_qcpp_inspbill by lydjbh+hid) 拿 inspBillId。</summary>
+    [DisplayName("FQC检验任务列表(报检)")]
+    [HttpGet("list")]
+    public async Task<object> GetList([FromQuery] FqcTaskEntryListInput input)
+    {
+        var page = input.Page <= 0 ? 1 : input.Page;
+        var pageSize = input.PageSize <= 0 ? 20 : (input.PageSize > 200 ? 200 : input.PageSize);
+        var offset = (page - 1) * pageSize;
+        var tenantId = ResolveTenantOrThrow();
+
+        var where = new List<string> { "q.tenant_id = @TenantId" };
+        var pars = new List<SugarParameter> { new("@TenantId", tenantId) };
+
+        if (!string.IsNullOrWhiteSpace(input.ProductionOrderNo))
+        {
+            where.Add("q.sczld LIKE @ProductionOrderNo");
+            pars.Add(new SugarParameter("@ProductionOrderNo", $"%{input.ProductionOrderNo.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.ProductionBatchNo))
+        {
+            where.Add("q.scph LIKE @ProductionBatchNo");
+            pars.Add(new SugarParameter("@ProductionBatchNo", $"%{input.ProductionBatchNo.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.MaterialCode))
+        {
+            where.Add("q.wlbm LIKE @MaterialCode");
+            pars.Add(new SugarParameter("@MaterialCode", $"%{input.MaterialCode.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.InspectProgress))
+        {
+            where.Add("q.FINSPECTSTATUS = @InspectProgress");
+            pars.Add(new SugarParameter("@InspectProgress", input.InspectProgress.Trim()));
+        }
+        if (!string.IsNullOrWhiteSpace(input.Priority))
+        {
+            where.Add("q.yxj LIKE @Priority");
+            pars.Add(new SugarParameter("@Priority", $"%{input.Priority.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.Inspector))
+        {
+            where.Add("q.jyfzr LIKE @Inspector");
+            pars.Add(new SugarParameter("@Inspector", $"%{input.Inspector.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.ApplyTimeStart))
+        {
+            where.Add("q.FAPPLYTIME >= @ApplyTimeStart");
+            pars.Add(new SugarParameter("@ApplyTimeStart", input.ApplyTimeStart.Trim() + " 00:00:00"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.ApplyTimeEnd))
+        {
+            where.Add("q.FAPPLYTIME <= @ApplyTimeEnd");
+            pars.Add(new SugarParameter("@ApplyTimeEnd", input.ApplyTimeEnd.Trim() + " 23:59:59"));
+        }
+
+        var whereSql = string.Join(" AND ", where);
+        var total = await _db.Ado.GetIntAsync(
+            $"SELECT COUNT(1) FROM qms_fqcbj q WHERE {whereSql}", pars);
+
+        // yxj 优先级排序:数值型转 SIGNED 升序(空/非数值排最后),再按 id 倒序
+        var list = await _db.Ado.SqlQueryAsync<FqcTaskEntryRow>(
+            $"""
+             SELECT
+                 q.id AS Id,
+                 q.FBILLNO AS ApplyBillNo,
+                 q.sczld AS ProductionOrderNo,
+                 q.FAPPLYTIME AS ApplyTime,
+                 q.wlbm AS MaterialCode,
+                 q.cpmc AS ProductName,
+                 q.cpxh AS ProductModel,
+                 q.scph AS ProductionBatchNo,
+                 q.sczldsl AS OrderQty,
+                 q.sl AS ApplyQty,
+                 q.FAPPLYUSER AS Applicant,
+                 q.FCOMMENT AS Remark,
+                 q.yxj AS Priority,
+                 q.jyfzr AS OwnerId,
+                 CAST(NULL AS CHAR) AS OwnerName,
+                 q.jykssj AS InspectStartTime,
+                 q.jywcsj AS InspectFinishTime,
+                 q.FINSPECTSTATUS AS InspectProgress,
+                 b.id AS InspBillId,
+                 b.FBILLNO AS InspBillNo
+             FROM qms_fqcbj q
+             LEFT JOIN qms_qcpp_inspbill b ON b.lydjbh = q.FBILLNO AND b.hid = q.id AND b.tenant_id = q.tenant_id
+             WHERE {whereSql}
+             ORDER BY (q.yxj = '紧急') DESC, (q.yxj = '正常') DESC, q.id DESC
+             LIMIT {pageSize} OFFSET {offset}
+             """,
+            pars);
+
+        await FillOwnerNamesAsync(list);
+        return new { total, page, pageSize, list };
+    }
+
+    private async Task FillOwnerNamesAsync(List<FqcTaskEntryRow> list)
+    {
+        var ids = list
+            .Select(x => x.OwnerId)
+            .Where(x => !string.IsNullOrWhiteSpace(x) && long.TryParse(x, out _))
+            .Select(x => long.Parse(x!))
+            .Distinct()
+            .ToList();
+        if (ids.Count == 0) return;
+
+        var users = await _db.Queryable<SysUser>()
+            .ClearFilter()
+            .Where(u => ids.Contains(u.Id))
+            .Select(u => new { u.Id, u.RealName, u.Account })
+            .ToListAsync();
+        var map = users.ToDictionary(u => u.Id.ToString(), u => u.RealName ?? u.Account ?? u.Id.ToString());
+        foreach (var row in list)
+        {
+            if (!string.IsNullOrWhiteSpace(row.OwnerId) && map.TryGetValue(row.OwnerId!, out var name))
+                row.OwnerName = name;
+        }
+    }
+
+    /// <summary>认领:把选中报检的检验负责人 jyfzr 置为当前用户。</summary>
+    [DisplayName("认领检验任务")]
+    [HttpPost("claim")]
+    public async Task<object> Claim([FromBody] FqcTaskEntryIdsInput input)
+    {
+        var ids = NormalizeIds(input?.Ids);
+        var tenantId = ResolveTenantOrThrow();
+        var owner = _userManager.UserId.ToString();
+        var n = await _db.Ado.ExecuteCommandAsync(
+            $"UPDATE qms_fqcbj SET jyfzr = @Owner WHERE tenant_id = @TenantId AND id IN ({string.Join(",", ids)})",
+            new SugarParameter("@Owner", owner),
+            new SugarParameter("@TenantId", tenantId));
+        return new { updated = n, ownerId = owner };
+    }
+
+    /// <summary>检验员调配:把选中报检的检验负责人 jyfzr 置为指定用户。</summary>
+    [DisplayName("检验员调配")]
+    [HttpPost("assign")]
+    public async Task<object> Assign([FromBody] FqcTaskEntryAssignInput input)
+    {
+        var ids = NormalizeIds(input?.Ids);
+        if (input == null || input.InspectorUserId <= 0) throw Oops.Oh("请指定检验负责人");
+        var tenantId = ResolveTenantOrThrow();
+        var owner = input.InspectorUserId.ToString();
+        var n = await _db.Ado.ExecuteCommandAsync(
+            $"UPDATE qms_fqcbj SET jyfzr = @Owner WHERE tenant_id = @TenantId AND id IN ({string.Join(",", ids)})",
+            new SugarParameter("@Owner", owner),
+            new SugarParameter("@TenantId", tenantId));
+        return new { updated = n, ownerId = owner };
+    }
+
+    /// <summary>优先级调整:写 qms_fqcbj.yxj(与报检检验优先级同域:正常/紧急)。</summary>
+    [DisplayName("优先级调整")]
+    [HttpPost("priority")]
+    public async Task<object> AdjustPriority([FromBody] FqcTaskEntryPriorityInput input)
+    {
+        var ids = NormalizeIds(input?.Ids);
+        var priority = input?.Priority?.Trim();
+        if (priority != "正常" && priority != "紧急") throw Oops.Oh("优先级仅支持 正常/紧急");
+        var tenantId = ResolveTenantOrThrow();
+        var n = await _db.Ado.ExecuteCommandAsync(
+            $"UPDATE qms_fqcbj SET yxj = @Priority WHERE tenant_id = @TenantId AND id IN ({string.Join(",", ids)})",
+            new SugarParameter("@Priority", priority),
+            new SugarParameter("@TenantId", tenantId));
+        return new { updated = n };
+    }
+
+    /// <summary>
+    /// 生成检验单(带认领门):校验当前用户为认领人 → 复用 FqcApplyService.GenerateInspection 生成(检规快照)
+    /// → 置报检 FINSPECTSTATUS=检验中 + jykssj → 回填 inspBillId/inspBillNo。非认领人拒绝。
+    /// </summary>
+    [DisplayName("生成检验单(认领后)")]
+    [HttpPost("generate-inspbill")]
+    public async Task<FqcTaskGenerateOutput> GenerateInspBill([FromBody] FqcTaskGenerateInput input)
+    {
+        if (input == null || input.EntryId <= 0) throw Oops.Oh("报检 id 非法");
+        var tenantId = ResolveTenantOrThrow();
+        var userId = _userManager.UserId.ToString();
+
+        var apply = await _db.Ado.SqlQuerySingleAsync<FqcTaskApplyRow>(
+            "SELECT id AS Id, FBILLNO AS BillNo, jyfzr AS OwnerId FROM qms_fqcbj WHERE id=@Id AND tenant_id=@t LIMIT 1",
+            new SugarParameter("@Id", input.EntryId), new SugarParameter("@t", tenantId));
+        if (apply == null) throw Oops.Oh("报检不存在或不属于当前租户");
+        if (string.IsNullOrWhiteSpace(apply.OwnerId) || !string.Equals(apply.OwnerId!.Trim(), userId, StringComparison.Ordinal))
+            throw Oops.Oh("尚未认领或非认领人操作!");
+
+        // 复用现有生成逻辑(检规解析 + 12 项快照 + 幂等 UNIQUE),不重复实现
+        await _fqcApply.GenerateInspection(new FqcGenerateInput { ApplyId = input.EntryId });
+
+        // 生成后置报检为检验中 + 开始时间(IFNULL 保留既有)
+        await _db.Ado.ExecuteCommandAsync(
+            "UPDATE qms_fqcbj SET FINSPECTSTATUS='检验中', jykssj=IFNULL(jykssj,@now) WHERE id=@Id AND tenant_id=@t",
+            new SugarParameter("@now", DateTime.Now),
+            new SugarParameter("@Id", input.EntryId),
+            new SugarParameter("@t", tenantId));
+
+        // 回读检验单 id/号(幂等:已存在也返回)
+        var bill = await _db.Ado.SqlQuerySingleAsync<FqcTaskBillRow>(
+            "SELECT id AS Id, FBILLNO AS BillNo FROM qms_qcpp_inspbill WHERE lydjbh=@no AND hid=@Id AND tenant_id=@t LIMIT 1",
+            new SugarParameter("@no", apply.BillNo), new SugarParameter("@Id", input.EntryId), new SugarParameter("@t", tenantId));
+        if (bill == null) throw Oops.Oh("检验单生成后未能回读,请刷新重试");
+        return new FqcTaskGenerateOutput { InspBillId = bill.Id, InspBillNo = bill.BillNo ?? "" };
+    }
+
+    private static List<long> NormalizeIds(List<long>? ids)
+    {
+        var list = (ids ?? new List<long>()).Where(x => x > 0).Distinct().ToList();
+        if (list.Count == 0) throw Oops.Oh("请至少选择一条任务");
+        if (list.Count > 200) throw Oops.Oh("单次最多操作 200 条");
+        return list;
+    }
+
+    private sealed class FqcTaskApplyRow
+    {
+        public long Id { get; set; }
+        public string? BillNo { get; set; }
+        public string? OwnerId { get; set; }
+    }
+
+    private sealed class FqcTaskBillRow
+    {
+        public long Id { get; set; }
+        public string? BillNo { get; set; }
+    }
+}
+
+public class FqcTaskEntryListInput
+{
+    public string? ProductionOrderNo { get; set; }
+    public string? ProductionBatchNo { get; set; }
+    public string? MaterialCode { get; set; }
+    public string? InspectProgress { get; set; }
+    public string? Priority { get; set; }
+    public string? Inspector { get; set; }
+    public string? ApplyTimeStart { get; set; }
+    public string? ApplyTimeEnd { get; set; }
+    public int Page { get; set; } = 1;
+    public int PageSize { get; set; } = 20;
+}
+
+public class FqcTaskEntryRow
+{
+    public long Id { get; set; }
+    public string? ApplyBillNo { get; set; }
+    public string? ProductionOrderNo { get; set; }
+    public string? ApplyTime { get; set; }
+    public string? MaterialCode { get; set; }
+    public string? ProductName { get; set; }
+    public string? ProductModel { get; set; }
+    public string? ProductionBatchNo { get; set; }
+    public decimal? OrderQty { get; set; }
+    public decimal? ApplyQty { get; set; }
+    public string? Applicant { get; set; }
+    public string? Remark { get; set; }
+    public string? Priority { get; set; }
+    public string? OwnerId { get; set; }
+    public string? OwnerName { get; set; }
+    public string? InspectStartTime { get; set; }
+    public string? InspectFinishTime { get; set; }
+    public string? InspectProgress { get; set; }
+    public long? InspBillId { get; set; }
+    public string? InspBillNo { get; set; }
+}
+
+public class FqcTaskEntryIdsInput
+{
+    public List<long>? Ids { get; set; }
+}
+
+public class FqcTaskEntryAssignInput
+{
+    public List<long>? Ids { get; set; }
+    public long InspectorUserId { get; set; }
+}
+
+public class FqcTaskEntryPriorityInput
+{
+    public List<long>? Ids { get; set; }
+    /// <summary>优先级:正常 / 紧急(与报检检验优先级同域)</summary>
+    public string? Priority { get; set; }
+}
+
+public class FqcTaskGenerateInput
+{
+    public long EntryId { get; set; }
+}
+
+public class FqcTaskGenerateOutput
+{
+    public long InspBillId { get; set; }
+    public string InspBillNo { get; set; } = "";
+}