From 04e8e3a312ed1f2d9dc40354ae5847ef9cc11360 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期六, 29 八月 2026 16:22:35 +0800
Subject: [PATCH] fix: 新疆-绿能 1.报工等待时长倒计时 + 报工数量可选 2.报工自动生成产品入库单 + 勾选入库 + 不合格仓库兜底

---
 src/views/wls/productreceipt/data.ts                          |   25 ++
 src/views/mes/pro/process/data.ts                             |   23 ++
 src/api/mes/pro/route/process/index.ts                        |    2 
 src/api/mes/wm/productreceipt/index.ts                        |   32 ++
 src/api/mes/pro/task/index.ts                                 |    8 
 src/views/mes/process-design/process/modules/form.vue         |    7 
 src/views/wls/productreceipt/modules/form.vue                 |    6 
 src/views/mes/pro/feedback/modules/form.vue                   |   73 +++++-
 src/views/mes/process-design/process/data.ts                  |   23 ++
 src/views/wls/productreceipt/index.vue                        |    5 
 src/api/mes/pro/process/index.ts                              |    2 
 src/views/mes/workbench/components/tabs/FeedbackTab.vue       |   65 +++++
 src/views/mes/pro/feedback/data.ts                            |   39 +++
 src/views/mes/pro/workorder-center/modules/quick-feedback.vue |   50 ++++
 src/views/wls/productreceipt/modules/line-list.vue            |   88 +++++++
 src/api/mes/wm/productreceipt/line/index.ts                   |    2 
 src/views/mes/pro/feedback/param-state.ts                     |   14 +
 src/views/mes/pro/process/modules/form.vue                    |    7 
 src/views/mes/process-design/route/data.ts                    |   12 
 src/views/mes/process-design/route/modules/process-form.vue   |   17 +
 src/views/mes/pro/feedback/modules/wait-countdown.vue         |  137 ++++++++++++
 21 files changed, 594 insertions(+), 43 deletions(-)

diff --git a/src/api/mes/pro/process/index.ts b/src/api/mes/pro/process/index.ts
index a4ff1ea..a8d922f 100644
--- a/src/api/mes/pro/process/index.ts
+++ b/src/api/mes/pro/process/index.ts
@@ -12,6 +12,8 @@
     status?: number;
     remark?: string;
     createTime?: Date;
+    waitDuration?: number; // 绛夊緟鏃堕暱锛堝皬鏃讹級锛屾姤宸ュ墠闇�鍊掕鏃剁瓑寰�
+    needReportQuantity?: boolean; // 鏄惁闇�瑕佸~鍐欐姤宸ユ暟閲�
   }
 }
 
diff --git a/src/api/mes/pro/route/process/index.ts b/src/api/mes/pro/route/process/index.ts
index a08056f..ba1e191 100644
--- a/src/api/mes/pro/route/process/index.ts
+++ b/src/api/mes/pro/route/process/index.ts
@@ -36,6 +36,8 @@
     remark?: string;
     bomItems?: ProcessBomItem[];
     createTime?: Date;
+    waitDuration?: number; // 绛夊緟鏃堕暱锛堝皬鏃讹級锛�>0 琛ㄧず鎶ュ伐鍓嶉渶鍊掕鏃剁瓑寰�
+    needReportQuantity?: boolean; // 鏄惁闇�瑕佸~鍐欐姤宸ユ暟閲忥紝false 琛ㄧず鎶ュ伐鏁伴噺鍏佽涓虹┖
   }
 }
 
diff --git a/src/api/mes/pro/task/index.ts b/src/api/mes/pro/task/index.ts
index 13fb333..b2edd00 100644
--- a/src/api/mes/pro/task/index.ts
+++ b/src/api/mes/pro/task/index.ts
@@ -59,6 +59,7 @@
     reportableQuantity?: number; // 鍙姤宸ユ暟閲�
     assigneeUserIds?: number[]; // 鎶ュ伐浜�
     feedbackApprove?: boolean; // 鏄惁鎶ュ伐瀹℃壒锛堟淳鐢熻嚜宸ュ崟锛�
+    waitStartTime?: string; // 鎶ュ伐鍊掕鏃跺紑濮嬫椂闂达紙yyyy-MM-dd HH:mm:ss锛�
   }
 
   /** MES 鐢熶骇浠诲姟鍒嗛〉鏌ヨ鍙傛暟 */
@@ -209,3 +210,10 @@
 export function resumeTask(data: { id: number }) {
   return requestClient.put('/mes/pro/task/resume', data);
 }
+
+/** 寮�濮嬫姤宸ュ�掕鏃讹紝杩斿洖鍊掕鏃跺紑濮嬫椂闂达紙yyyy-MM-dd HH:mm:ss锛� */
+export function startWaitCountdown(taskId: number) {
+  return requestClient.post<string>('/mes/pro/task/start-wait-countdown', {
+    taskId,
+  });
+}
diff --git a/src/api/mes/wm/productreceipt/index.ts b/src/api/mes/wm/productreceipt/index.ts
index 7a49b56..090f57f 100644
--- a/src/api/mes/wm/productreceipt/index.ts
+++ b/src/api/mes/wm/productreceipt/index.ts
@@ -18,7 +18,29 @@
     receiptDate?: number; // 鍏ュ簱鏃ユ湡
     status?: number; // 鍗曟嵁鐘舵��
     remark?: string; // 澶囨敞
+    feedbackId?: number; // 鏉ユ簮鎶ュ伐缂栧彿锛堥潪绌鸿〃绀虹敱鎶ュ伐鑷姩鐢熸垚锛�
     createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+
+  /** 涓婃灦鍒嗛厤鏄庣粏 */
+  export interface StockDetail {
+    warehouseId: number; // 浠撳簱缂栧彿
+    locationId: number; // 搴撳尯缂栧彿
+    areaId: number; // 搴撲綅缂栧彿
+    quantity: number; // 鍒嗛厤鏁伴噺
+  }
+
+  /** 涓婃灦琛� */
+  export interface StockRow {
+    lineId: number; // 鍏ュ簱鍗曡缂栧彿
+    isStock: boolean; // 鏄惁鍏ュ簱
+    details?: StockDetail[]; // isStock=true 鏃剁殑浠撳簱/搴撳尯/搴撲綅鍒嗛厤
+  }
+
+  /** 鎵归噺涓婃灦璇锋眰 */
+  export interface StockRequest {
+    receiptId: number; // 浜у搧鍏ュ簱鍗曠紪鍙�
+    rows: StockRow[]; // 琛屼笂鏋跺垪琛�
   }
 }
 
@@ -83,6 +105,16 @@
   );
 }
 
+/** 鎵归噺涓婃灦锛堝嬀閫夋槸鍚﹀叆搴� + 鍒嗛厤浠撳簱/搴撳尯/搴撲綅鏁伴噺锛� */
+export function submitProductReceiptStock(
+  data: MesWmProductReceiptApi.StockRequest,
+) {
+  return requestClient.post<boolean>(
+    '/mes/wm/product-receipt/submit-stock',
+    data,
+  );
+}
+
 /** 瀵煎嚭浜у搧鍏ュ簱鍗� */
 export function exportProductReceipt(params: any) {
   return requestClient.download('/mes/wm/product-receipt/export-excel', {
diff --git a/src/api/mes/wm/productreceipt/line/index.ts b/src/api/mes/wm/productreceipt/line/index.ts
index 04e113b..973954f 100644
--- a/src/api/mes/wm/productreceipt/line/index.ts
+++ b/src/api/mes/wm/productreceipt/line/index.ts
@@ -16,6 +16,8 @@
     quantity?: number; // 鍏ュ簱鏁伴噺
     batchId?: number; // 鎵规缂栧彿
     batchCode?: string; // 鎵规鍙�
+    qualityStatus?: number; // 璐ㄩ噺鐘舵�侊紙瀛楀吀 MES_WM_QUALITY_STATUS 鍚堟牸/涓嶅悎鏍硷級
+    isStock?: boolean; // 鏄惁鍏ュ簱锛坒alse/绌�=鎵ц鍏ュ簱鏃堕粯璁よ繘涓嶅悎鏍间粨搴擄級
     remark?: string; // 澶囨敞
   }
 }
diff --git a/src/views/mes/pro/feedback/data.ts b/src/views/mes/pro/feedback/data.ts
index 32056e7..fbe99f0 100644
--- a/src/views/mes/pro/feedback/data.ts
+++ b/src/views/mes/pro/feedback/data.ts
@@ -19,7 +19,13 @@
 import { generateAutoCode } from '#/api/mes/md/autocode/record';
 import { getRouteProcessByRouteAndProcess } from '#/api/mes/pro/route/process';
 import { getSimpleUserList } from '#/api/system/user';
-import { loadParamDetails } from './param-state';
+import {
+  loadParamDetails,
+  resetWaitConfig,
+  waitDuration,
+  waitStartTime,
+  waitTaskId,
+} from './param-state';
 import { getRangePickerDefaultProps } from '#/utils';
 import { MdItemSelect } from '#/views/mes/md/item/components';
 import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
@@ -195,6 +201,12 @@
       defaultValue: true,
     },
     {
+      fieldName: 'needReportQuantity',
+      component: 'Input',
+      dependencies: { triggerFields: [''], show: () => false },
+      defaultValue: true,
+    },
+    {
       fieldName: 'routeId',
       component: 'Input',
       dependencies: { triggerFields: [''], show: () => false },
@@ -255,8 +267,10 @@
         status: MesProWorkOrderStatusEnum.CONFIRMED,
         // 璁㈠崟鍙樻洿锛氭竻绌轰换鍔″強浠诲姟甯﹀嚭鐨勪骇鍝佷俊鎭�佹暟閲忓尯鍩熸帶鍒朵綅
         onChange: async () => {
+          resetWaitConfig();
           await formApi?.setValues({
             checkFlag: true,
+            needReportQuantity: true,
             itemCode: undefined,
             itemId: undefined,
             itemName: undefined,
@@ -291,6 +305,8 @@
         onChange: async (task?: MesProTaskApi.Task) => {
           await loadParamDetails(task?.id);
           if (!task) {
+            resetWaitConfig();
+            await formApi?.setFieldValue('needReportQuantity', true);
             return;
           }
           await formApi?.setValues({
@@ -304,7 +320,9 @@
             workstationId: task.workstationId,
             reportableQuantity: task.reportableQuantity,
           });
-          // 宸ヨ壓璺嚎宸ュ簭鐨� checkFlag 鍐冲畾鏁伴噺鍖哄煙灞曠ず
+          waitTaskId.value = task.id;
+          waitStartTime.value = task.waitStartTime;
+          // 宸ヨ壓璺嚎宸ュ簭鐨� checkFlag / 绛夊緟鏃堕暱 / 鎶ュ伐鏁伴噺瑙勫垯鍐冲畾灞曠ず
           if (task.routeId && task.processId) {
             try {
               const routeProcess = await getRouteProcessByRouteAndProcess(
@@ -315,9 +333,19 @@
                 'checkFlag',
                 routeProcess?.checkFlag ?? false,
               );
+              await formApi?.setFieldValue(
+                'needReportQuantity',
+                routeProcess?.needReportQuantity ?? true,
+              );
+              waitDuration.value = routeProcess?.waitDuration;
             } catch {
               await formApi?.setFieldValue('checkFlag', true);
+              await formApi?.setFieldValue('needReportQuantity', true);
+              waitDuration.value = undefined;
             }
+          } else {
+            waitDuration.value = undefined;
+            await formApi?.setFieldValue('needReportQuantity', true);
           }
         },
       },
@@ -405,7 +433,12 @@
         precision: 2,
         placeholder: '璇疯緭鍏ユ姤宸ユ暟閲�',
       },
-      rules: 'required',
+      dependencies: {
+        triggerFields: ['needReportQuantity'],
+        show: (values) => values.needReportQuantity !== false,
+        rules: (values) =>
+          values.needReportQuantity === false ? null : 'required',
+      },
     },
     {
       fieldName: 'feedbackUserId',
diff --git a/src/views/mes/pro/feedback/modules/form.vue b/src/views/mes/pro/feedback/modules/form.vue
index 9e5a9a3..baf93c8 100644
--- a/src/views/mes/pro/feedback/modules/form.vue
+++ b/src/views/mes/pro/feedback/modules/form.vue
@@ -25,14 +25,24 @@
   updateFeedback,
 } from '#/api/mes/pro/feedback';
 import { getRouteProcessByRouteAndProcess } from '#/api/mes/pro/route/process';
+import { getTask } from '#/api/mes/pro/task';
 import { getWorkOrder } from '#/api/mes/pro/workorder';
 import { $t } from '#/locales';
 
 import { useFormSchema } from '../data';
-import { findMissingRequiredParams, loadParamDetails, paramDetails } from '../param-state';
+import {
+  findMissingRequiredParams,
+  loadParamDetails,
+  paramDetails,
+  resetWaitConfig,
+  waitDuration,
+  waitStartTime,
+  waitTaskId,
+} from '../param-state';
 import BatchInputDialog from './batch-input-dialog.vue';
 import ItemConsumeList from './item-consume-list.vue';
 import ProductProduceList from './product-produce-list.vue';
+import WaitCountdown from './wait-countdown.vue';
 
 const emit = defineEmits(['success']);
 const formType = ref<FormType>('create');
@@ -88,7 +98,13 @@
 });
 
 /** 鎻愪氦鍓嶅榻愭暟閲忥細鎶ュ伐鍙~鍐欐姤宸ユ暟閲忥紝鍚堟牸/涓嶈壇/搴熷搧鏁伴噺鐢辫川妫�纭畾锛屾姤宸ョ幆鑺備笉閲囬泦 */
-function alignQuantity(data: MesProFeedbackApi.Feedback) {
+function alignQuantity(
+  data: MesProFeedbackApi.Feedback,
+  needReportQuantity: boolean,
+) {
+  if (!needReportQuantity) {
+    data.feedbackQuantity = 0;
+  }
   data.uncheckQuantity = data.checkFlag ? data.feedbackQuantity : 0;
   delete data.qualifiedQuantity;
   delete data.unqualifiedQuantity;
@@ -99,9 +115,12 @@
 
 /** 璇诲彇骞舵寜鎻愪氦瑙勫垯瀵归綈鍚庣殑琛ㄥ崟鏁版嵁锛岀敤浜庡揩鐓у拰淇濆瓨 */
 async function getAlignedData(): Promise<MesProFeedbackApi.Feedback> {
-  const data = (await formApi.getValues()) as MesProFeedbackApi.Feedback;
+  const data = (await formApi.getValues()) as MesProFeedbackApi.Feedback & {
+    needReportQuantity?: boolean;
+  };
   syncParamValues(data);
-  alignQuantity(data);
+  alignQuantity(data, data.needReportQuantity !== false);
+  delete data.needReportQuantity;
   return data;
 }
 
@@ -292,19 +311,24 @@
   }
 }
 
-/** 鍔犺浇宸ュ簭鐨� checkFlag 鐢ㄤ簬鍥炴樉鏁伴噺鍖哄煙 */
-async function resolveCheckFlag(routeId?: number, processId?: number) {
+/** 鍔犺浇宸ュ簭閰嶇疆锛坈heckFlag / 绛夊緟鏃堕暱 / 鎶ュ伐鏁伴噺瑙勫垯锛夌敤浜庡洖鏄� */
+async function resolveRouteProcessConfig(routeId?: number, processId?: number) {
   if (!routeId || !processId) {
-    return true;
+    return { checkFlag: true, needReportQuantity: true };
   }
   try {
     const routeProcess = await getRouteProcessByRouteAndProcess(
       routeId,
       processId,
     );
-    return routeProcess?.checkFlag ?? false;
+    waitDuration.value = routeProcess?.waitDuration;
+    return {
+      checkFlag: routeProcess?.checkFlag ?? false,
+      needReportQuantity: routeProcess?.needReportQuantity ?? true,
+    };
   } catch {
-    return true;
+    waitDuration.value = undefined;
+    return { checkFlag: true, needReportQuantity: true };
   }
 }
 
@@ -322,6 +346,7 @@
       subTabsName.value = 'itemConsume';
       originalSnapshot.value = '';
       paramDetails.value = [];
+      resetWaitConfig();
       return;
     }
     // 鍔犺浇鏁版嵁
@@ -357,12 +382,26 @@
     try {
       formData.value = await getFeedback(data.id);
       await loadParamDetails(formData.value.taskId);
-      const checkFlag = await resolveCheckFlag(
+      const config = await resolveRouteProcessConfig(
         formData.value.routeId,
         formData.value.processId,
       );
+      waitTaskId.value = formData.value.taskId;
+      waitStartTime.value = undefined;
+      if (formData.value.taskId) {
+        try {
+          const task = await getTask(formData.value.taskId);
+          waitStartTime.value = task?.waitStartTime;
+        } catch {
+          // 浠诲姟涓嶅瓨鍦ㄦ椂蹇界暐鍊掕鏃跺洖鏄�
+        }
+      }
       // 璁剧疆鍒� values
-      await formApi.setValues({ ...formData.value, checkFlag });
+      await formApi.setValues({
+        ...formData.value,
+        checkFlag: config.checkFlag,
+        needReportQuantity: config.needReportQuantity,
+      });
       // 璁板綍鍒濆蹇収锛堝惈鏁伴噺瀵归綈缁撴灉锛夛紝submit 鏃剁敤浜庤烦杩囨棤鍙樻洿鐨勪繚瀛樿姹�
       originalSnapshot.value = JSON.stringify(await getAlignedData());
     } finally {
@@ -375,6 +414,18 @@
 <template>
   <Modal :title="getTitle" class="w-3/5">
     <Form class="mx-4" />
+    <div
+      v-if="waitDuration"
+      class="mx-4 mt-4 rounded border border-gray-200 p-4"
+    >
+      <div class="mb-2 font-medium">鎶ュ伐鍊掕鏃�</div>
+      <WaitCountdown
+        :task-id="waitTaskId"
+        :wait-duration="waitDuration"
+        :wait-start-time="waitStartTime"
+        :disabled="!isEditable"
+      />
+    </div>
     <div v-if="paramDetails.length" class="mx-4 mt-4 rounded border border-gray-200 p-4">
       <div class="mb-3 font-medium">宸ヨ壓鍙傛暟</div>
       <div class="grid grid-cols-2 gap-3">
diff --git a/src/views/mes/pro/feedback/modules/wait-countdown.vue b/src/views/mes/pro/feedback/modules/wait-countdown.vue
new file mode 100644
index 0000000..760b8de
--- /dev/null
+++ b/src/views/mes/pro/feedback/modules/wait-countdown.vue
@@ -0,0 +1,137 @@
+<script lang="ts" setup>
+import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
+
+import dayjs from 'dayjs';
+
+import { Button, message } from 'ant-design-vue';
+
+import { startWaitCountdown } from '#/api/mes/pro/task';
+
+defineOptions({ name: 'WaitCountdown' });
+
+interface Props {
+  taskId?: number;
+  /** 绛夊緟鏃堕暱锛堝皬鏃讹級锛�>0 鎵嶅睍绀哄�掕鏃� */
+  waitDuration?: number;
+  /** 鍊掕鏃跺紑濮嬫椂闂达紙yyyy-MM-dd HH:mm:ss锛� */
+  waitStartTime?: string;
+  /** 鍙妯″紡锛堣鎯�/瀹℃壒锛変笅涓嶅睍绀哄紑濮嬫寜閽� */
+  disabled?: boolean;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  taskId: undefined,
+  waitDuration: undefined,
+  waitStartTime: undefined,
+  disabled: false,
+});
+
+const starting = ref(false);
+const startTime = ref<string | undefined>(props.waitStartTime);
+const now = ref(Date.now());
+let timer: ReturnType<typeof setInterval> | undefined;
+
+const showCountdown = computed(
+  () => !!props.waitDuration && props.waitDuration > 0,
+);
+
+const endTime = computed(() => {
+  if (!startTime.value || !showCountdown.value) {
+    return undefined;
+  }
+  return dayjs(startTime.value, 'YYYY-MM-DD HH:mm:ss').valueOf() +
+    props.waitDuration! * 3600 * 1000;
+});
+
+const remainingMs = computed(() => {
+  if (!endTime.value) {
+    return 0;
+  }
+  return Math.max(0, endTime.value - now.value);
+});
+
+const counting = computed(() => remainingMs.value > 0);
+
+const remainingText = computed(() => {
+  const totalSeconds = Math.ceil(remainingMs.value / 1000);
+  const hours = Math.floor(totalSeconds / 3600);
+  const minutes = Math.floor((totalSeconds % 3600) / 60);
+  const seconds = totalSeconds % 60;
+  const pad = (n: number) => String(n).padStart(2, '0');
+  if (hours > 0) {
+    return `鍓╀綑 ${hours} 灏忔椂 ${pad(minutes)} 鍒� ${pad(seconds)} 绉抈;
+  }
+  return `鍓╀綑 ${pad(minutes)} 鍒� ${pad(seconds)} 绉抈;
+});
+
+function startTimer() {
+  if (timer) {
+    clearInterval(timer);
+  }
+  timer = setInterval(() => {
+    now.value = Date.now();
+    if (remainingMs.value <= 0) {
+      clearInterval(timer);
+      timer = undefined;
+    }
+  }, 1000);
+}
+
+watch(
+  () => props.waitStartTime,
+  (value) => {
+    startTime.value = value;
+    if (value) {
+      now.value = Date.now();
+      startTimer();
+    }
+  },
+);
+
+onMounted(() => {
+  if (startTime.value) {
+    startTimer();
+  }
+});
+
+onBeforeUnmount(() => {
+  if (timer) {
+    clearInterval(timer);
+    timer = undefined;
+  }
+});
+
+async function handleStart() {
+  if (!props.taskId) {
+    message.warning('璇峰厛閫夋嫨浠诲姟');
+    return;
+  }
+  starting.value = true;
+  try {
+    const time = await startWaitCountdown(props.taskId);
+    startTime.value = time;
+    now.value = Date.now();
+    startTimer();
+    message.success('鍊掕鏃跺凡寮�濮�');
+  } finally {
+    starting.value = false;
+  }
+}
+</script>
+
+<template>
+  <div v-if="showCountdown" class="flex items-center gap-2">
+    <Button
+      v-if="!startTime && !disabled"
+      type="primary"
+      :loading="starting"
+      @click="handleStart"
+    >
+      寮�濮嬪�掕鏃�
+    </Button>
+    <span v-if="startTime" class="text-sm">
+      <span v-if="counting" class="text-orange-500">{{ remainingText }}</span>
+      <span v-else class="text-green-600">鍊掕鏃跺凡缁撴潫锛屽彲鎶ュ伐</span>
+    </span>
+  </div>
+</template>
diff --git a/src/views/mes/pro/feedback/param-state.ts b/src/views/mes/pro/feedback/param-state.ts
index 2b3599a..ebd9559 100644
--- a/src/views/mes/pro/feedback/param-state.ts
+++ b/src/views/mes/pro/feedback/param-state.ts
@@ -45,3 +45,17 @@
     .filter((detail) => detail.required && !detail.paramValue?.trim())
     .map((detail) => detail.paramName || detail.paramCode || '');
 }
+
+/** 褰撳墠鎶ュ伐浠诲姟缂栧彿锛堢敤浜庡�掕鏃舵帴鍙o級 */
+export const waitTaskId = ref<number>();
+/** 褰撳墠浠诲姟宸ュ簭鐨勭瓑寰呮椂闀匡紙灏忔椂锛夛紝>0 琛ㄧず鎶ュ伐鍓嶉渶鍊掕鏃� */
+export const waitDuration = ref<number>();
+/** 褰撳墠浠诲姟鎶ュ伐鍊掕鏃跺紑濮嬫椂闂达紙yyyy-MM-dd HH:mm:ss锛� */
+export const waitStartTime = ref<string>();
+
+/** 閲嶇疆鎶ュ伐鍊掕鏃剁浉鍏崇姸鎬� */
+export function resetWaitConfig() {
+  waitTaskId.value = undefined;
+  waitDuration.value = undefined;
+  waitStartTime.value = undefined;
+}
diff --git a/src/views/mes/pro/process/data.ts b/src/views/mes/pro/process/data.ts
index 1cf6134..a7b51ac 100644
--- a/src/views/mes/pro/process/data.ts
+++ b/src/views/mes/pro/process/data.ts
@@ -77,6 +77,29 @@
       rules: z.number().default(CommonStatusEnum.ENABLE),
     },
     {
+      fieldName: 'waitDuration',
+      label: '绛夊緟鏃堕暱(灏忔椂)',
+      component: 'InputNumber',
+      formItemClass: 'col-span-2',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        precision: 2,
+        placeholder: '鐣欑┖鎴� 0 琛ㄧず鏃犻渶鍊掕鏃�',
+      },
+    },
+    {
+      fieldName: 'needReportQuantity',
+      label: '闇�濉啓鎶ュ伐鏁伴噺',
+      component: 'Switch',
+      formItemClass: 'col-span-1',
+      defaultValue: true,
+      componentProps: {
+        checkedChildren: '鏄�',
+        unCheckedChildren: '鍚�',
+      },
+    },
+    {
       fieldName: 'attention',
       label: '宸ュ簭璇存槑',
       component: 'Textarea',
diff --git a/src/views/mes/pro/process/modules/form.vue b/src/views/mes/pro/process/modules/form.vue
index 540c0be..643c235 100644
--- a/src/views/mes/pro/process/modules/form.vue
+++ b/src/views/mes/pro/process/modules/form.vue
@@ -88,8 +88,11 @@
     modalApi.lock();
     try {
       formData.value = await getProcess(data.id);
-      // 璁剧疆鍒� values
-      await formApi.setValues(formData.value);
+      // 璁剧疆鍒� values锛堟棫鏁版嵁鏈厤缃姤宸ユ暟閲忓紑鍏虫椂鎸夐粯璁ら渶瑕佸~鍐欙級
+      await formApi.setValues({
+        ...formData.value,
+        needReportQuantity: formData.value.needReportQuantity ?? true,
+      });
     } finally {
       modalApi.unlock();
     }
diff --git a/src/views/mes/pro/workorder-center/modules/quick-feedback.vue b/src/views/mes/pro/workorder-center/modules/quick-feedback.vue
index 9df47ce..fc91d88 100644
--- a/src/views/mes/pro/workorder-center/modules/quick-feedback.vue
+++ b/src/views/mes/pro/workorder-center/modules/quick-feedback.vue
@@ -9,10 +9,13 @@
 import { message } from 'ant-design-vue';
 
 import { quickCreateFeedback } from '#/api/mes/pro/feedback';
+import { getRouteProcessByRouteAndProcess } from '#/api/mes/pro/route/process';
+import { getTask } from '#/api/mes/pro/task';
 import {
   loadParamDetails,
   paramDetails,
 } from '#/views/mes/pro/feedback/param-state';
+import WaitCountdown from '#/views/mes/pro/feedback/modules/wait-countdown.vue';
 import ProTaskSelect from '#/views/mes/pro/task/components/select.vue';
 
 defineOptions({ name: 'QuickFeedbackForm' });
@@ -41,9 +44,15 @@
         feedbackQuantity: undefined,
         remark: '',
       };
-      // 棰勮浠诲姟鏃跺姞杞藉叾宸ヨ壓鍙傛暟锛涘惁鍒欑瓑寰呴�夋嫨浠诲姟鍚庡啀鍔犺浇
+      waitDuration.value = undefined;
+      needReportQuantity.value = true;
+      waitStartTime.value = undefined;
+      // 棰勮浠诲姟鏃跺姞杞藉叾宸ヨ壓鍙傛暟涓庣瓑寰呴厤缃紱鍚﹀垯绛夊緟閫夋嫨浠诲姟鍚庡啀鍔犺浇
       if (props.taskId) {
         loadParamDetails(props.taskId);
+        getTask(props.taskId)
+          .then(applyTaskWaitConfig)
+          .catch(() => {});
       } else {
         paramDetails.value = [];
       }
@@ -59,6 +68,9 @@
   remark: '',
 });
 const submitting = ref(false);
+const waitDuration = ref<number>();
+const needReportQuantity = ref<boolean>(true);
+const waitStartTime = ref<string>();
 
 const hasPredefinedTask = computed(() => !!props.taskId);
 
@@ -77,6 +89,27 @@
   selectedTask.value = task;
   selectedTaskId.value = task?.id;
   loadParamDetails(task?.id);
+  applyTaskWaitConfig(task);
+}
+
+/** 鎸変换鍔″伐搴忓姞杞界瓑寰呮椂闀夸笌鎶ュ伐鏁伴噺瑙勫垯 */
+async function applyTaskWaitConfig(task?: MesProTaskApi.Task) {
+  waitDuration.value = undefined;
+  needReportQuantity.value = true;
+  waitStartTime.value = task?.waitStartTime;
+  if (!task?.routeId || !task?.processId) {
+    return;
+  }
+  try {
+    const routeProcess = await getRouteProcessByRouteAndProcess(
+      task.routeId,
+      task.processId,
+    );
+    waitDuration.value = routeProcess?.waitDuration;
+    needReportQuantity.value = routeProcess?.needReportQuantity ?? true;
+  } catch {
+    // 鍔犺浇澶辫触鎸夐粯璁ゅ鐞嗭紙鏃犻渶鍊掕鏃躲�佹暟閲忓繀濉級
+  }
 }
 
 async function handleSubmit() {
@@ -85,7 +118,7 @@
     message.warning('璇烽�夋嫨浠诲姟');
     return;
   }
-  if (!formData.value.feedbackQuantity) {
+  if (needReportQuantity.value && !formData.value.feedbackQuantity) {
     message.warning('璇疯緭鍏ユ姤宸ユ暟閲�');
     return;
   }
@@ -93,7 +126,9 @@
   try {
     await quickCreateFeedback({
       taskId,
-      feedbackQuantity: formData.value.feedbackQuantity,
+      feedbackQuantity: needReportQuantity.value
+        ? (formData.value.feedbackQuantity ?? 0)
+        : 0,
       paramValues: paramDetails.value.map((detail) => ({
         detailId: detail.id,
         value: detail.paramValue,
@@ -127,7 +162,14 @@
           class="!w-full"
         />
       </a-form-item>
-      <a-form-item label="鏈鎶ュ伐鏁伴噺" required>
+      <a-form-item v-if="waitDuration" label="鎶ュ伐鍊掕鏃�">
+        <WaitCountdown
+          :task-id="selectedTaskId"
+          :wait-duration="waitDuration"
+          :wait-start-time="waitStartTime"
+        />
+      </a-form-item>
+      <a-form-item v-if="needReportQuantity" label="鏈鎶ュ伐鏁伴噺" required>
         <a-input-number
           v-model:value="formData.feedbackQuantity"
           :min="0.01"
diff --git a/src/views/mes/process-design/process/data.ts b/src/views/mes/process-design/process/data.ts
index 1cf6134..a7b51ac 100644
--- a/src/views/mes/process-design/process/data.ts
+++ b/src/views/mes/process-design/process/data.ts
@@ -77,6 +77,29 @@
       rules: z.number().default(CommonStatusEnum.ENABLE),
     },
     {
+      fieldName: 'waitDuration',
+      label: '绛夊緟鏃堕暱(灏忔椂)',
+      component: 'InputNumber',
+      formItemClass: 'col-span-2',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        precision: 2,
+        placeholder: '鐣欑┖鎴� 0 琛ㄧず鏃犻渶鍊掕鏃�',
+      },
+    },
+    {
+      fieldName: 'needReportQuantity',
+      label: '闇�濉啓鎶ュ伐鏁伴噺',
+      component: 'Switch',
+      formItemClass: 'col-span-1',
+      defaultValue: true,
+      componentProps: {
+        checkedChildren: '鏄�',
+        unCheckedChildren: '鍚�',
+      },
+    },
+    {
       fieldName: 'attention',
       label: '宸ュ簭璇存槑',
       component: 'Textarea',
diff --git a/src/views/mes/process-design/process/modules/form.vue b/src/views/mes/process-design/process/modules/form.vue
index 540c0be..643c235 100644
--- a/src/views/mes/process-design/process/modules/form.vue
+++ b/src/views/mes/process-design/process/modules/form.vue
@@ -88,8 +88,11 @@
     modalApi.lock();
     try {
       formData.value = await getProcess(data.id);
-      // 璁剧疆鍒� values
-      await formApi.setValues(formData.value);
+      // 璁剧疆鍒� values锛堟棫鏁版嵁鏈厤缃姤宸ユ暟閲忓紑鍏虫椂鎸夐粯璁ら渶瑕佸~鍐欙級
+      await formApi.setValues({
+        ...formData.value,
+        needReportQuantity: formData.value.needReportQuantity ?? true,
+      });
     } finally {
       modalApi.unlock();
     }
diff --git a/src/views/mes/process-design/route/data.ts b/src/views/mes/process-design/route/data.ts
index 7990b1e..5c310bb 100644
--- a/src/views/mes/process-design/route/data.ts
+++ b/src/views/mes/process-design/route/data.ts
@@ -166,7 +166,10 @@
 }
 
 /** 宸ヨ壓璺嚎宸ュ簭鏄庣粏琛ㄥ崟 */
-export function useRouteProcessFormSchema(processOptions: Array<{ label: string; value: number }>): VbenFormSchema[] {
+export function useRouteProcessFormSchema(
+  processOptions: Array<{ label: string; value: number }>,
+  onProcessChange?: (processId?: number) => void,
+): VbenFormSchema[] {
   return [
     {
       fieldName: "id",
@@ -198,6 +201,7 @@
         options: processOptions,
         placeholder: "璇烽�夋嫨宸ュ簭",
         showSearch: true,
+        onChange: onProcessChange,
       },
       rules: "selectRequired",
     },
@@ -279,12 +283,12 @@
     },
     {
       fieldName: "waitTime",
-      label: "绛夊緟鏃堕棿(鍒�)",
+      label: "绛夊緟鏃堕暱(灏忔椂)",
       component: "InputNumber",
       componentProps: {
         class: "!w-full",
         min: 0,
-        precision: 0,
+        precision: 2,
       },
       rules: z.number().default(0),
     },
@@ -351,7 +355,7 @@
       width: 160,
     },
     { field: "prepareTime", title: "鍑嗗鏃堕棿(鍒�)", width: 110 },
-    { field: "waitTime", title: "绛夊緟鏃堕棿(鍒�)", width: 110 },
+    { field: "waitTime", title: "绛夊緟鏃堕暱(灏忔椂)", width: 110 },
     {
       field: "colorCode",
       title: "鐢樼壒鍥鹃鑹�",
diff --git a/src/views/mes/process-design/route/modules/process-form.vue b/src/views/mes/process-design/route/modules/process-form.vue
index 6dcab06..2006065 100644
--- a/src/views/mes/process-design/route/modules/process-form.vue
+++ b/src/views/mes/process-design/route/modules/process-form.vue
@@ -9,7 +9,7 @@
 import { message } from 'ant-design-vue';
 
 import { useVbenForm } from '#/adapter/form';
-import { getProcessSimpleList } from '#/api/mes/pro/process';
+import { getProcess, getProcessSimpleList } from '#/api/mes/pro/process';
 import {
   createRouteProcess,
   getRouteProcess,
@@ -39,6 +39,19 @@
   wrapperClass: 'grid-cols-2',
 });
 
+/** 閫夋嫨宸ュ簭鍚庯紝鑷姩甯﹀嚭璇ュ伐搴忛厤缃殑绛夊緟鏃堕暱锛堢洿鎺ュ~灏忔椂鏁帮紝涓嶆崲绠楋級 */
+async function handleProcessChange(processId?: number) {
+  if (!processId) {
+    return;
+  }
+  try {
+    const process = await getProcess(processId);
+    await formApi.setFieldValue('waitTime', process.waitDuration ?? 0);
+  } catch {
+    // 宸ュ簭璇︽儏鑾峰彇澶辫触鏃跺拷鐣ュ弽鏄�
+  }
+}
+
 /** 鍔犺浇宸ュ簭閫夐」鍚庡啀鐢熸垚 schema锛堥伩鍏嶄笅鎷夌┖閫夐」锛� */
 async function loadSchema(): Promise<VbenFormSchema[]> {
   const list = await getProcessSimpleList();
@@ -46,7 +59,7 @@
     label: item.name ?? '',
     value: item.id!,
   }));
-  return useRouteProcessFormSchema(options);
+  return useRouteProcessFormSchema(options, handleProcessChange);
 }
 
 const [Modal, modalApi] = useVbenModal({
diff --git a/src/views/mes/workbench/components/tabs/FeedbackTab.vue b/src/views/mes/workbench/components/tabs/FeedbackTab.vue
index 86b6e25..2166e3f 100644
--- a/src/views/mes/workbench/components/tabs/FeedbackTab.vue
+++ b/src/views/mes/workbench/components/tabs/FeedbackTab.vue
@@ -26,14 +26,20 @@
   updateFeedback,
 } from '#/api/mes/pro/feedback';
 import { getRouteProcessByRouteAndProcess } from '#/api/mes/pro/route/process';
+import { getTask } from '#/api/mes/pro/task';
 import { $t } from '#/locales';
 
 import { useFormSchema } from '../../../pro/feedback/data';
 import {
   loadParamDetails,
   paramDetails,
+  resetWaitConfig,
+  waitDuration,
+  waitStartTime,
+  waitTaskId,
 } from '../../../pro/feedback/param-state';
 import BatchInputDialog from '../../../pro/feedback/modules/batch-input-dialog.vue';
+import WaitCountdown from '../../../pro/feedback/modules/wait-countdown.vue';
 const batchDialogRef = ref<InstanceType<typeof BatchInputDialog>>();
 const batchActionType = ref<'' | 'submit' | 'approve'>('');
 const pendingSubmitMode = ref<'saveAndSubmit' | 'submit'>('submit');
@@ -79,12 +85,16 @@
 
 /** 鎻愪氦鍓嶅榻愭暟閲忥細鎶ュ伐鍙~鍐欐姤宸ユ暟閲忥紝鍚堟牸/涓嶈壇/搴熷搧鏁伴噺鐢辫川妫�纭畾锛屾姤宸ョ幆鑺備笉閲囬泦 */
 function alignQuantity(data: any) {
+  if (data.needReportQuantity === false) {
+    data.feedbackQuantity = 0;
+  }
   data.uncheckQuantity = data.checkFlag ? data.feedbackQuantity : 0;
   delete data.qualifiedQuantity;
   delete data.unqualifiedQuantity;
   delete data.laborScrapQuantity;
   delete data.materialScrapQuantity;
   delete data.otherScrapQuantity;
+  delete data.needReportQuantity;
 }
 
 async function getAlignedData() {
@@ -102,14 +112,21 @@
   }));
 }
 
-/** 鍔犺浇 checkFlag */
-async function resolveCheckFlag(routeId?: number, processId?: number) {
-  if (!routeId || !processId) return true;
+/** 鍔犺浇宸ュ簭閰嶇疆锛坈heckFlag / 绛夊緟鏃堕暱 / 鎶ュ伐鏁伴噺瑙勫垯锛� */
+async function resolveRouteProcessConfig(routeId?: number, processId?: number) {
+  if (!routeId || !processId) {
+    return { checkFlag: true, needReportQuantity: true };
+  }
   try {
     const rp = await getRouteProcessByRouteAndProcess(routeId, processId);
-    return rp?.checkFlag ?? false;
+    waitDuration.value = rp?.waitDuration;
+    return {
+      checkFlag: rp?.checkFlag ?? false,
+      needReportQuantity: rp?.needReportQuantity ?? true,
+    };
   } catch {
-    return true;
+    waitDuration.value = undefined;
+    return { checkFlag: true, needReportQuantity: true };
   }
 }
 
@@ -119,13 +136,17 @@
   feedbackId.value = undefined;
   feedbackStatus.value = undefined;
   originalSnapshot.value = '';
+  resetWaitConfig();
 
   formApi.setState({ schema: useFormSchema(formType.value, formApi) });
 
   const code = await generateAutoCode(MesAutoCodeRuleCode.PRO_FEEDBACK_CODE);
-  const checkFlag = await resolveCheckFlag(task.routeId, task.processId);
+  const config = await resolveRouteProcessConfig(task.routeId, task.processId);
   // 鍔犺浇浠诲姟缁戝畾鐨勫伐鑹哄弬鏁帮紙蹇呭~鍙傛暟鏍¢獙 + 鍙傛暟鍊煎洖鏄撅級
   await loadParamDetails(task.id);
+
+  waitTaskId.value = task.id;
+  waitStartTime.value = task.waitStartTime;
 
   await formApi.setValues({
     code,
@@ -145,7 +166,8 @@
     reportableQuantity: task.reportableQuantity,
     scheduledQuantity: task.quantity,
     feedbackApprove: task.feedbackApprove,
-    checkFlag,
+    checkFlag: config.checkFlag,
+    needReportQuantity: config.needReportQuantity,
   });
   originalSnapshot.value = JSON.stringify(await getAlignedData());
 }
@@ -337,9 +359,23 @@
   feedbackStatus.value = data.status;
   formType.value = 'update';
   formApi.setState({ schema: useFormSchema(formType.value, formApi) });
-  const checkFlag = await resolveCheckFlag(data.routeId, data.processId);
+  const config = await resolveRouteProcessConfig(data.routeId, data.processId);
   await loadParamDetails(data.taskId);
-  await formApi.setValues({ ...data, checkFlag });
+  waitTaskId.value = data.taskId;
+  waitStartTime.value = undefined;
+  if (data.taskId) {
+    try {
+      const task = await getTask(data.taskId);
+      waitStartTime.value = task?.waitStartTime;
+    } catch {
+      // 浠诲姟涓嶅瓨鍦ㄦ椂蹇界暐鍊掕鏃跺洖鏄�
+    }
+  }
+  await formApi.setValues({
+    ...data,
+    checkFlag: config.checkFlag,
+    needReportQuantity: config.needReportQuantity,
+  });
   originalSnapshot.value = JSON.stringify(await getAlignedData());
 }
 
@@ -377,6 +413,17 @@
 
       <Form class="feedback-tab__form" />
 
+      <!-- 鎶ュ伐鍊掕鏃讹紙宸ュ簭閰嶇疆绛夊緟鏃堕暱鏃跺睍绀猴級 -->
+      <div v-if="waitDuration" class="feedback-tab__params">
+        <div class="feedback-tab__params-title">鎶ュ伐鍊掕鏃�</div>
+        <WaitCountdown
+          :task-id="waitTaskId"
+          :wait-duration="waitDuration"
+          :wait-start-time="waitStartTime"
+          :disabled="!isEditable"
+        />
+      </div>
+
       <!-- 宸ヨ壓鍙傛暟锛堜换鍔$粦瀹氬弬鏁版椂灞曠ず锛屽繀濉弬鏁版湭濉細琚悗绔嫤鎴級 -->
       <div v-if="paramDetails.length" class="feedback-tab__params">
         <div class="feedback-tab__params-title">宸ヨ壓鍙傛暟</div>
diff --git a/src/views/wls/productreceipt/data.ts b/src/views/wls/productreceipt/data.ts
index f249a84..c7c6db6 100644
--- a/src/views/wls/productreceipt/data.ts
+++ b/src/views/wls/productreceipt/data.ts
@@ -192,6 +192,12 @@
       },
     },
     {
+      field: 'feedbackId',
+      title: '鏉ユ簮',
+      width: 100,
+      slots: { default: 'source' },
+    },
+    {
       title: '鎿嶄綔',
       width: 240,
       fixed: 'right',
@@ -237,10 +243,29 @@
       width: 100,
     },
     {
+      field: 'qualityStatus',
+      title: '璐ㄩ噺鐘舵��',
+      width: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_QUALITY_STATUS },
+      },
+    },
+    {
       field: 'batchCode',
       title: '鎵规鍙�',
       minWidth: 120,
     },
+    ...(stockable
+      ? [
+          {
+            field: 'isStock',
+            title: '鏄惁鍏ュ簱',
+            width: 100,
+            slots: { default: 'isStock' },
+          } as const,
+        ]
+      : []),
     ...(editable || stockable
       ? [
           {
diff --git a/src/views/wls/productreceipt/index.vue b/src/views/wls/productreceipt/index.vue
index f557590..76bdd54 100644
--- a/src/views/wls/productreceipt/index.vue
+++ b/src/views/wls/productreceipt/index.vue
@@ -6,7 +6,7 @@
 import { MesWmProductReceiptStatusEnum } from '@vben/constants';
 import { downloadFileFromBlobPart } from '@vben/utils';
 
-import { Button, message } from 'ant-design-vue';
+import { Button, message, Tag } from 'ant-design-vue';
 
 import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
 import {
@@ -143,6 +143,9 @@
           {{ row.code }}
         </Button>
       </template>
+      <template #source="{ row }">
+        <Tag v-if="row.feedbackId" color="blue">鑷姩鐢熸垚</Tag>
+      </template>
       <template #actions="{ row }">
         <TableAction
           :actions="[
diff --git a/src/views/wls/productreceipt/modules/form.vue b/src/views/wls/productreceipt/modules/form.vue
index d9fa648..2b3d5a8 100644
--- a/src/views/wls/productreceipt/modules/form.vue
+++ b/src/views/wls/productreceipt/modules/form.vue
@@ -219,7 +219,11 @@
     <template v-if="formData?.id">
       <Divider>鐗╂枡淇℃伅</Divider>
       <div class="mx-4">
-        <LineList :form-type="formType" :receipt-id="formData.id" />
+        <LineList
+          :form-type="formType"
+          :receipt-id="formData.id"
+          :auto-generated="!!formData.feedbackId"
+        />
       </div>
     </template>
     <template #prepend-footer>
diff --git a/src/views/wls/productreceipt/modules/line-list.vue b/src/views/wls/productreceipt/modules/line-list.vue
index 26442fb..e28cdac 100644
--- a/src/views/wls/productreceipt/modules/line-list.vue
+++ b/src/views/wls/productreceipt/modules/line-list.vue
@@ -2,6 +2,7 @@
 import type { FormType } from '../data';
 
 import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmProductReceiptApi } from '#/api/mes/wm/productreceipt';
 import type { MesWmProductReceiptDetailApi } from '#/api/mes/wm/productreceipt/detail';
 import type { MesWmProductReceiptLineApi } from '#/api/mes/wm/productreceipt/line';
 
@@ -10,9 +11,10 @@
 import { useVbenModal } from '@vben/common-ui';
 import { BarcodeBizTypeEnum } from '@vben/constants';
 
-import { message } from 'ant-design-vue';
+import { Button, Checkbox, message } from 'ant-design-vue';
 
 import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { submitProductReceiptStock } from '#/api/mes/wm/productreceipt';
 import { getProductReceiptDetailListByLineId } from '#/api/mes/wm/productreceipt/detail';
 import {
   deleteProductReceiptLine,
@@ -29,16 +31,22 @@
 const props = defineProps<{
   formType: FormType;
   receiptId: number;
+  autoGenerated?: boolean; // 鏄惁鐢辨姤宸ヨ嚜鍔ㄧ敓鎴愶紙鑷姩鐢熸垚鐨勮涓嶅彲鎵嬪伐澧炲垹鏀癸級
 }>();
 
 const isEditable = computed(() =>
   // 鏄惁鍙紪杈戞槑缁嗚
   ['create', 'update'].includes(props.formType),
 );
+const canEditLine = computed(
+  // 鑷姩鐢熸垚鐨勫叆搴撳崟琛屼笉鍙墜宸ュ鍒犳敼
+  () => isEditable.value && !props.autoGenerated,
+);
 const isStock = computed(() => props.formType === 'stock'); // 鏄惁涓轰笂鏋舵ā寮�
 const detailMap = reactive<
   Record<number, MesWmProductReceiptDetailApi.ProductReceiptDetail[]>
 >({}); // 宸插睍寮�琛岀殑涓婃灦鏄庣粏缂撳瓨
+const checkedMap = reactive<Record<number, boolean>>({}); // 琛屾槸鍚﹀叆搴撳嬀閫夌姸鎬佺紦瀛�
 const barcodeDetailRef = ref(); // 鏉$爜璇︽儏寮圭獥瀹炰緥
 
 const [LineFormModal, lineFormModalApi] = useVbenModal({
@@ -55,6 +63,9 @@
 function handleRefresh() {
   for (const id of Object.keys(detailMap)) {
     delete detailMap[Number(id)];
+  }
+  for (const id of Object.keys(checkedMap)) {
+    delete checkedMap[Number(id)];
   }
   gridApi.query();
 }
@@ -99,6 +110,64 @@
     row.itemCode,
     row.itemName,
   );
+}
+
+/** 琛屾槸鍚﹀叆搴撳嬀閫夌姸鎬侊紙鏈墜鍔ㄥ嬀閫夋椂鍥為��鍒拌鑷甫鍊硷級 */
+function isLineStock(row: MesWmProductReceiptLineApi.ProductReceiptLine) {
+  return checkedMap[row.id!] ?? row.isStock ?? false;
+}
+
+/** 鍒囨崲琛屾槸鍚﹀叆搴� */
+function setLineStock(
+  row: MesWmProductReceiptLineApi.ProductReceiptLine,
+  checked: boolean,
+) {
+  checkedMap[row.id!] = checked;
+}
+
+/** 淇濆瓨涓婃灦鍒嗛厤锛氭敹闆嗘墍鏈夎鐨勫嬀閫夌姸鎬佷笌浠撳簱/搴撳尯/搴撲綅鍒嗛厤锛岃皟鐢ㄦ壒閲忎笂鏋舵帴鍙� */
+async function handleSaveStock() {
+  if (!props.receiptId) {
+    return;
+  }
+  const hideLoading = message.loading({
+    content: '姝e湪淇濆瓨涓婃灦鍒嗛厤...',
+    duration: 0,
+  });
+  try {
+    const { list } = await getProductReceiptLinePage({
+      pageNo: 1,
+      pageSize: 10000,
+      receiptId: props.receiptId,
+    });
+    const rows: MesWmProductReceiptApi.StockRow[] = [];
+    for (const line of list) {
+      const isStock = isLineStock(line);
+      const row: MesWmProductReceiptApi.StockRow = {
+        lineId: line.id!,
+        isStock,
+      };
+      if (isStock) {
+        let details = detailMap[line.id!];
+        if (!details) {
+          details = await getProductReceiptDetailListByLineId(line.id!);
+          detailMap[line.id!] = details;
+        }
+        row.details = details.map((detail) => ({
+          warehouseId: detail.warehouseId!,
+          locationId: detail.locationId!,
+          areaId: detail.areaId!,
+          quantity: detail.quantity!,
+        }));
+      }
+      rows.push(row);
+    }
+    await submitProductReceiptStock({ receiptId: props.receiptId, rows });
+    message.success('涓婃灦鍒嗛厤淇濆瓨鎴愬姛');
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
 }
 
 /** 鎵撳紑涓婃灦鏄庣粏琛ㄥ崟 */
@@ -185,7 +254,7 @@
     <LineFormModal @success="handleRefresh" />
     <DetailFormModal @success="handleDetailSuccess" />
     <Grid table-title="鐗╂枡淇℃伅">
-      <template v-if="isEditable" #toolbar-tools>
+      <template v-if="canEditLine" #toolbar-tools>
         <TableAction
           :actions="[
             {
@@ -197,6 +266,9 @@
           ]"
         />
       </template>
+      <template v-if="isStock" #toolbar-tools>
+        <Button type="primary" @click="handleSaveStock">淇濆瓨涓婃灦鍒嗛厤</Button>
+      </template>
       <template #detail="{ row }">
         <DetailList
           :details="getExpandedDetails(row)"
@@ -204,6 +276,14 @@
           @edit="(detailId) => openDetailForm(row.id!, row.itemId, detailId)"
           @refresh="loadLineDetails(row.id!)"
         />
+      </template>
+      <template #isStock="{ row }">
+        <div class="flex justify-center">
+          <Checkbox
+            :checked="isLineStock(row)"
+            @update:checked="(checked: boolean) => setLineStock(row, checked)"
+          />
+        </div>
       </template>
       <template #actions="{ row }">
         <div class="flex items-center justify-center">
@@ -213,7 +293,7 @@
                 label: $t('common.edit'),
                 type: 'link',
                 icon: ACTION_ICON.EDIT,
-                ifShow: isEditable,
+                ifShow: canEditLine,
                 onClick: handleEdit.bind(null, row),
               },
               {
@@ -221,7 +301,7 @@
                 type: 'link',
                 danger: true,
                 icon: ACTION_ICON.DELETE,
-                ifShow: isEditable,
+                ifShow: canEditLine,
                 popConfirm: {
                   title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
                   confirm: handleDelete.bind(null, row),

--
Gitblit v1.9.3