feat(mes): 新增袋码作废时间和原因字段及破损替换功能
- 在袋码数据表中新增 cancelTime 和 cancelReason 字段用于记录作废信息
- 扩展袋码 API 接口增加作废相关字段和破损替换接口
- 实现破损袋码批量替换功能,支持选择袋码进行替换操作
- 添加替换结果展示弹窗和替换操作的业务逻辑验证
- 更新扫码追溯模态框显示作废和替换相关的追溯信息
- 新增 ReplaceModal 组件用于处理破损袋码替换表单操作
| | |
| | | palletCode?: string; // æçç |
| | | status?: number; // ç¶æï¼åå
¸ mes_pro_bag_code_statusï¼ |
| | | printTime?: string; // 导åºï¼äº¤ä»å°å·ï¼æ¶é´ |
| | | cancelTime?: string; // ä½åºæ¶é´ |
| | | cancelReason?: string; // ä½åºåå |
| | | remark?: string; // 夿³¨ |
| | | createTime?: string; // å建æ¶é´ |
| | | } |
| | |
| | | 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; // è¢ç ç¼å· |
| | |
| | | bagNo?: number; // è¢åºå· |
| | | status?: number; // è¢ç ç¶æ |
| | | printTime?: string; // 导åºå°å·æ¶é´ |
| | | cancelTime?: string; // ä½åºæ¶é´ |
| | | cancelReason?: string; // ä½åºåå |
| | | replacementUuid?: string; // æ¿æ¢ç» UUID |
| | | replacedByCode?: string; // æ¿æ¢åçæ°è¢ç |
| | | replacementOfCode?: string; // è¢«æ¿æ¢çæ§è¢ç |
| | | batchId?: number; // æ¹æ¬¡ç¼å· |
| | | batchCode?: string; // æ¹æ¬¡å· |
| | | batchStatus?: number; // æ¹æ¬¡ç¶æ |
| | |
| | | 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>( |
| | |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | field: 'cancelTime', |
| | | title: 'ä½åºæ¶é´', |
| | | width: 170, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | field: 'cancelReason', |
| | | title: 'ä½åºåå ', |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'å建æ¶é´', |
| | | width: 170, |
| | |
| | | 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 { |
| | |
| | | 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" }); |
| | |
| | | |
| | | const [BindPalletFormModal, bindPalletFormModalApi] = useVbenModal({ |
| | | connectedComponent: BindPalletModal, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [ReplaceFormModal, replaceFormModalApi] = useVbenModal({ |
| | | connectedComponent: ReplaceModal, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | |
| | | /** çææ°æ®å
*/ |
| | | function handleGenerate() { |
| | | generateFormModalApi.open(); |
| | | } |
| | | |
| | | /** æ¿æ¢æååå±ç¤ºç»æå¹¶å·æ°å表 */ |
| | | function handleReplaceSuccess(result: MesProBagCodeApi.ReplaceResult) { |
| | | showReplaceResult(result); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | /** æ«ç 追溯 */ |
| | |
| | | |
| | | /** è·åå¾éçè¢ç è®°å½ */ |
| | | 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(); |
| | | } |
| | | |
| | | /** æ¹éç»å®æç */ |
| | |
| | | <Page auto-content-height> |
| | | <GenerateFormModal @success="handleRefresh" /> |
| | | <BindPalletFormModal @success="handleRefresh" /> |
| | | <ReplaceFormModal @success="handleReplaceSuccess" /> |
| | | <QrTraceQueryModal /> |
| | | <Grid table-title="è¢ç å表ï¼ä¸è¢ä¸ç ï¼"> |
| | | <template #toolbar-tools> |
| | |
| | | onClick: handleBindPallet, |
| | | }, |
| | | { |
| | | label: 'ç ´ææ¿æ¢', |
| | | type: 'primary', |
| | | auth: ['mes:pro-bag-code:replace'], |
| | | onClick: handleReplace, |
| | | }, |
| | | { |
| | | label: 'è§£ç»æç', |
| | | auth: ['mes:pro-bag-code:update'], |
| | | onClick: handleUnbindPallet, |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| | |
| | | field: "printTime", |
| | | label: "导åºå°å·æ¶é´", |
| | | }, |
| | | { |
| | | field: "cancelTime", |
| | | label: "ä½åºæ¶é´", |
| | | }, |
| | | { |
| | | field: "cancelReason", |
| | | label: "ä½åºåå ", |
| | | }, |
| | | { |
| | | field: "replacementUuid", |
| | | label: "æ¿æ¢ç» UUID", |
| | | }, |
| | | { |
| | | field: "replacedByCode", |
| | | label: "æ¿æ¢åè¢ç ", |
| | | }, |
| | | { |
| | | field: "replacementOfCode", |
| | | label: "æ¿æ¢æ¥æºè¢ç ", |
| | | }, |
| | | ], |
| | | }); |
| | | |