From d2cd585f4d3b015789a5f6c8d75e352605932f85 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 21 八月 2026 13:44:30 +0800
Subject: [PATCH] feat(mes): 新增袋码作废时间和原因字段及破损替换功能

---
 src/views/mes/pro/bagCode/index.vue                |   97 ++++++++++++++++++
 src/views/mes/pro/bagCode/modules/replace-form.vue |  105 +++++++++++++++++++++
 src/api/mes/pro/bagCode/index.ts                   |   48 +++++++++
 src/views/mes/pro/bagCode/data.ts                  |   11 ++
 src/views/mes/pro/components/qr-trace-modal.vue    |   20 ++++
 5 files changed, 277 insertions(+), 4 deletions(-)

diff --git a/src/api/mes/pro/bagCode/index.ts b/src/api/mes/pro/bagCode/index.ts
index 70d332c..5c381e9 100644
--- a/src/api/mes/pro/bagCode/index.ts
+++ b/src/api/mes/pro/bagCode/index.ts
@@ -17,6 +17,8 @@
     palletCode?: string; // 鎵樼洏鐮�
     status?: number; // 鐘舵�侊紙瀛楀吀 mes_pro_bag_code_status锛�
     printTime?: string; // 瀵煎嚭锛堜氦浠樺嵃鍒凤級鏃堕棿
+    cancelTime?: string; // 浣滃簾鏃堕棿
+    cancelReason?: string; // 浣滃簾鍘熷洜
     remark?: string; // 澶囨敞
     createTime?: string; // 鍒涘缓鏃堕棿
   }
@@ -45,6 +47,39 @@
     ids: number[]; // 琚嬬爜缂栧彿鍒楄〃
   }
 
+  /** 鐮存崯琚嬬爜鏇挎崲璇锋眰 */
+  export interface ReplaceParams {
+    batchId: number; // 鐢熶骇鎵规缂栧彿
+    oldIds: number[]; // 寰呮浛鎹㈡棫琚嬬爜缂栧彿鍒楄〃
+    reason: string; // 鏇挎崲鍘熷洜
+  }
+
+  /** 鐮存崯琚嬬爜鏇挎崲鏄庣粏 */
+  export interface ReplaceItem {
+    oldBagCodeId?: number;
+    oldCode?: string;
+    oldBagNo?: number;
+    oldStatus?: number;
+    oldPalletId?: number;
+    oldPalletCode?: string;
+    newBagCodeId?: number;
+    newCode?: string;
+    newBagNo?: number;
+    newPalletId?: number;
+    newPalletCode?: string;
+    reason?: string;
+    replaceTime?: string;
+  }
+
+  /** 鐮存崯琚嬬爜鏇挎崲缁撴灉 */
+  export interface ReplaceResult {
+    replaceUuid?: string;
+    batchId?: number;
+    batchCode?: string;
+    count?: number;
+    items?: ReplaceItem[];
+  }
+
   /** 鎵爜杩芥函淇℃伅 */
   export interface BagCodeTrace {
     id?: number; // 琚嬬爜缂栧彿
@@ -52,6 +87,11 @@
     bagNo?: number; // 琚嬪簭鍙�
     status?: number; // 琚嬬爜鐘舵��
     printTime?: string; // 瀵煎嚭鍗板埛鏃堕棿
+    cancelTime?: string; // 浣滃簾鏃堕棿
+    cancelReason?: string; // 浣滃簾鍘熷洜
+    replacementUuid?: string; // 鏇挎崲缁� UUID
+    replacedByCode?: string; // 鏇挎崲鍚庣殑鏂拌鐮�
+    replacementOfCode?: string; // 琚浛鎹㈢殑鏃ц鐮�
     batchId?: number; // 鎵规缂栧彿
     batchCode?: string; // 鎵规鍙�
     batchStatus?: number; // 鎵规鐘舵��
@@ -89,6 +129,14 @@
   return requestClient.put('/mes/pro/bag-code/unbind-pallet', data);
 }
 
+/** 浣滃簾鐮存崯琚嬬爜骞剁敓鎴愮瓑閲忔浛鎹㈣鐮� */
+export function replaceBagCodes(data: MesProBagCodeApi.ReplaceParams) {
+  return requestClient.post<MesProBagCodeApi.ReplaceResult>(
+    '/mes/pro/bag-code/replace',
+    data,
+  );
+}
+
 /** 鏌ヨ琚嬬爜璇︽儏 */
 export function getBagCode(id: number) {
   return requestClient.get<MesProBagCodeApi.BagCode>(
diff --git a/src/views/mes/pro/bagCode/data.ts b/src/views/mes/pro/bagCode/data.ts
index 09b3d69..a2b312e 100644
--- a/src/views/mes/pro/bagCode/data.ts
+++ b/src/views/mes/pro/bagCode/data.ts
@@ -115,6 +115,17 @@
       formatter: 'formatDateTime',
     },
     {
+      field: 'cancelTime',
+      title: '浣滃簾鏃堕棿',
+      width: 170,
+      formatter: 'formatDateTime',
+    },
+    {
+      field: 'cancelReason',
+      title: '浣滃簾鍘熷洜',
+      minWidth: 160,
+    },
+    {
       field: 'createTime',
       title: '鍒涘缓鏃堕棿',
       width: 170,
diff --git a/src/views/mes/pro/bagCode/index.vue b/src/views/mes/pro/bagCode/index.vue
index f4ffa22..910602c 100644
--- a/src/views/mes/pro/bagCode/index.vue
+++ b/src/views/mes/pro/bagCode/index.vue
@@ -6,7 +6,7 @@
   import { MesProBagCodeStatusEnum } from "@vben/constants";
   import { downloadFileFromBlobPart } from "@vben/utils";
 
-  import { message } from "ant-design-vue";
+  import { message, Modal } from "ant-design-vue";
 
   import { ACTION_ICON, TableAction, useVbenVxeGrid } from "#/adapter/vxe-table";
   import {
@@ -21,6 +21,7 @@
   import { useGridColumns, useGridFormSchema } from "./data";
   import BindPalletModal from "./modules/bind-pallet.vue";
   import GenerateModal from "./modules/generate-form.vue";
+  import ReplaceModal from "./modules/replace-form.vue";
   import QrTraceModal from "../components/qr-trace-modal.vue";
 
   defineOptions({ name: "MesProBagCode" });
@@ -32,6 +33,11 @@
 
   const [BindPalletFormModal, bindPalletFormModalApi] = useVbenModal({
     connectedComponent: BindPalletModal,
+    destroyOnClose: true,
+  });
+
+  const [ReplaceFormModal, replaceFormModalApi] = useVbenModal({
+    connectedComponent: ReplaceModal,
     destroyOnClose: true,
   });
 
@@ -48,6 +54,12 @@
   /** 鐢熸垚鏁版嵁鍖� */
   function handleGenerate() {
     generateFormModalApi.open();
+  }
+
+  /** 鏇挎崲鎴愬姛鍚庡睍绀虹粨鏋滃苟鍒锋柊鍒楄〃 */
+  function handleReplaceSuccess(result: MesProBagCodeApi.ReplaceResult) {
+    showReplaceResult(result);
+    handleRefresh();
   }
 
   /** 鎵爜杩芥函 */
@@ -88,12 +100,82 @@
 
   /** 鑾峰彇鍕鹃�夌殑琚嬬爜璁板綍 */
   function getSelectedRecords(): MesProBagCodeApi.BagCode[] | undefined {
-    const records = gridApi.grid.getCheckboxRecords() as MesProBagCodeApi.BagCode[];
-    if (records.length === 0) {
+    const records = [
+      ...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
+      ...(gridApi.grid.getCheckboxRecords?.() ?? []),
+    ] as MesProBagCodeApi.BagCode[];
+    const uniqueRecords = [
+      ...new Map(records.map((record) => [record.id, record])).values(),
+    ];
+    if (uniqueRecords.length === 0) {
       message.warning("璇疯嚦灏戝嬀閫変竴鏉¤鐮佽褰�");
       return undefined;
     }
-    return records;
+    return uniqueRecords;
+  }
+
+  /** 灞曠ず鏇挎崲缁撴灉 */
+  function showReplaceResult(result: MesProBagCodeApi.ReplaceResult) {
+    const items = result.items ?? [];
+    const content = items.length
+      ? items
+          .map(
+            (item) =>
+              `${item.oldCode ?? "-"} 鈫� ${item.newCode ?? "-"}${
+                item.newPalletCode ? `锛堟墭鐩橈細${item.newPalletCode}锛塦 : ""
+              }`,
+          )
+          .join("\n")
+      : "鏇挎崲鏄庣粏鏆傛棤";
+    Modal.success({
+      title: "鐮存崯琚嬬爜鏇挎崲鎴愬姛",
+      content: `鎵规锛�${result.batchCode ?? "-"}\n鏇挎崲鏁伴噺锛�${
+        result.count ?? items.length
+      }\n${content}`,
+      centered: true,
+    });
+  }
+
+  /** 鎵归噺鏇挎崲鐮存崯琚嬬爜 */
+  async function handleReplace() {
+    const records = getSelectedRecords();
+    if (!records) return;
+    if (records.length > 200) {
+      message.warning("鍗曟鏈�澶氭浛鎹� 200 涓鐮�");
+      return;
+    }
+    const batchIds = new Set(records.map((record) => record.batchId));
+    if (batchIds.size > 1 || !records[0]?.batchId) {
+      message.warning("璇烽�夋嫨鍚屼竴鎵规涓嬬殑琚嬬爜杩涜鏇挎崲");
+      return;
+    }
+    if (
+      records.some(
+        (record) =>
+          record.status !== MesProBagCodeStatusEnum.GENERATED &&
+          record.status !== MesProBagCodeStatusEnum.PRINTED,
+      )
+    ) {
+      message.warning("浠呭凡鐢熸垚鎴栧凡瀵煎嚭鍗板埛鐨勮鐮佸厑璁告浛鎹�");
+      return;
+    }
+    if (records.some((record) => !record.id)) {
+      message.warning("鎵�閫夎鐮佺己灏戠紪鍙凤紝鏃犳硶鏇挎崲");
+      return;
+    }
+    try {
+      await confirm(`纭浣滃簾鎵�閫� ${records.length} 涓鐮佸苟鐢熸垚绛夐噺鏂扮爜鍚楋紵`);
+    } catch {
+      return;
+    }
+    const first = records[0]!;
+    replaceFormModalApi
+      .setData({
+        batchId: first.batchId!,
+        batchCode: first.batchCode,
+        ids: records.map((record) => record.id!),
+      })
+      .open();
   }
 
   /** 鎵归噺缁戝畾鎵樼洏 */
@@ -200,6 +282,7 @@
   <Page auto-content-height>
     <GenerateFormModal @success="handleRefresh" />
     <BindPalletFormModal @success="handleRefresh" />
+    <ReplaceFormModal @success="handleReplaceSuccess" />
     <QrTraceQueryModal />
     <Grid table-title="琚嬬爜鍒楄〃锛堜竴琚嬩竴鐮侊級">
       <template #toolbar-tools>
@@ -218,6 +301,12 @@
               onClick: handleBindPallet,
             },
             {
+              label: '鐮存崯鏇挎崲',
+              type: 'primary',
+              auth: ['mes:pro-bag-code:replace'],
+              onClick: handleReplace,
+            },
+            {
               label: '瑙g粦鎵樼洏',
               auth: ['mes:pro-bag-code:update'],
               onClick: handleUnbindPallet,
diff --git a/src/views/mes/pro/bagCode/modules/replace-form.vue b/src/views/mes/pro/bagCode/modules/replace-form.vue
new file mode 100644
index 0000000..99df840
--- /dev/null
+++ b/src/views/mes/pro/bagCode/modules/replace-form.vue
@@ -0,0 +1,105 @@
+<script lang="ts" setup>
+  import type { VbenFormSchema } from "#/adapter/form";
+  import type { MesProBagCodeApi } from "#/api/mes/pro/bagCode";
+
+  import { useVbenModal } from "@vben/common-ui";
+
+  import { message } from "ant-design-vue";
+
+  import { useVbenForm } from "#/adapter/form";
+  import { replaceBagCodes } from "#/api/mes/pro/bagCode";
+
+  defineOptions({ name: "MesProBagCodeReplaceForm" });
+
+  interface ModalData {
+    batchId: number;
+    batchCode?: string;
+    ids: number[];
+  }
+
+  const emit = defineEmits<{
+    success: [result: MesProBagCodeApi.ReplaceResult];
+  }>();
+
+  const schema: VbenFormSchema[] = [
+    {
+      fieldName: "batchCode",
+      label: "鐢熶骇鎵规",
+      component: "Input",
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      fieldName: "bagCount",
+      label: "鏇挎崲琚嬫暟",
+      component: "InputNumber",
+      componentProps: {
+        class: "!w-full",
+        disabled: true,
+      },
+    },
+    {
+      fieldName: "reason",
+      label: "鐮存崯鍘熷洜",
+      component: "Textarea",
+      formItemClass: "col-span-2",
+      componentProps: {
+        maxlength: 255,
+        placeholder: "璇疯緭鍏ョ牬鎹熷師鍥�",
+        rows: 3,
+        showCount: true,
+      },
+      rules: "required",
+    },
+  ];
+
+  const [Form, formApi] = useVbenForm({
+    commonConfig: {
+      componentProps: {
+        class: "w-full",
+      },
+      labelWidth: 100,
+    },
+    layout: "horizontal",
+    schema,
+    showDefaultActions: false,
+  });
+
+  const [Modal, modalApi] = useVbenModal({
+    async onConfirm() {
+      const { valid } = await formApi.validate();
+      if (!valid) return;
+      const data = modalApi.getData<ModalData>();
+      const values = await formApi.getValues();
+      modalApi.lock();
+      try {
+        const result = await replaceBagCodes({
+          batchId: data.batchId,
+          oldIds: data.ids,
+          reason: values.reason,
+        });
+        message.success(`宸叉垚鍔熸浛鎹� ${result.count ?? data.ids.length} 涓鐮乣);
+        emit("success", result);
+        await modalApi.close();
+      } finally {
+        modalApi.unlock();
+      }
+    },
+    onOpenChange(isOpen: boolean) {
+      if (!isOpen) return;
+      const data = modalApi.getData<ModalData>();
+      void formApi.setValues({
+        bagCount: data.ids.length,
+        batchCode: data.batchCode,
+        reason: undefined,
+      });
+    },
+  });
+</script>
+
+<template>
+  <Modal title="鐮存崯琚嬬爜鏇挎崲" class="w-2/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/mes/pro/components/qr-trace-modal.vue b/src/views/mes/pro/components/qr-trace-modal.vue
index 44813d4..710a2e7 100644
--- a/src/views/mes/pro/components/qr-trace-modal.vue
+++ b/src/views/mes/pro/components/qr-trace-modal.vue
@@ -328,6 +328,26 @@
         field: "printTime",
         label: "瀵煎嚭鍗板埛鏃堕棿",
       },
+      {
+        field: "cancelTime",
+        label: "浣滃簾鏃堕棿",
+      },
+      {
+        field: "cancelReason",
+        label: "浣滃簾鍘熷洜",
+      },
+      {
+        field: "replacementUuid",
+        label: "鏇挎崲缁� UUID",
+      },
+      {
+        field: "replacedByCode",
+        label: "鏇挎崲鍚庤鐮�",
+      },
+      {
+        field: "replacementOfCode",
+        label: "鏇挎崲鏉ユ簮琚嬬爜",
+      },
     ],
   });
 

--
Gitblit v1.9.3