fix(wls): 修复多个组件中的类型安全和数据验证问题
- 在an-select-dialog.vue中添加非空断言操作符确保类型安全
- 重构batch-feedback.vue实现工序配置的动态数量验证逻辑
- 修复approval/config/data.ts中布尔值转换问题避免类型错误
- 修正productsales/data.ts中的销售订单代码字段映射
- 优化returnsales/form.vue中销售订单查询的数据处理逻辑
- 在approval/config/index.vue中使用unref解决响应式数据引用问题
- 简化returnsales/line-form.vue中批记录获取的条件判断逻辑
- 添加productreceipt/line-list.vue中入库明细数量一致性验证
- 修复po-select-dialog.vue中采购订单选择的类型安全问题
- 重构process-config-form.vue中的业务类型变更处理逻辑
- 修正stock-adjust-modal.vue中表单字段依赖关系配置并更新模态框标题设置
| | |
| | | <script lang="ts" setup> |
| | | import type { MesProTaskApi } from '#/api/mes/pro/task'; |
| | | |
| | | import { ref } from 'vue'; |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { Button, message, Tag } from 'ant-design-vue'; |
| | | |
| | | import { batchCreateFeedback } from '#/api/mes/pro/feedback'; |
| | | import { getRouteProcessByRouteAndProcess } from '#/api/mes/pro/route/process'; |
| | | import ProTaskSelectDialog from '#/views/mes/pro/task/components/select-dialog.vue'; |
| | | |
| | | defineOptions({ name: 'BatchFeedbackForm' }); |
| | |
| | | |
| | | const taskDialogRef = ref<InstanceType<typeof ProTaskSelectDialog>>(); |
| | | const selectedTasks = ref<MesProTaskApi.Task[]>([]); |
| | | const needReportQuantityMap = ref<Record<number, boolean>>({}); |
| | | const formData = ref({ |
| | | feedbackQuantity: undefined as number | undefined, |
| | | remark: '', |
| | | }); |
| | | const submitting = ref(false); |
| | | |
| | | /** 是否需要填写报工数量(任一选中任务工序要求填写数量即必填) */ |
| | | const needQuantity = computed(() => |
| | | selectedTasks.value.some( |
| | | (t) => needReportQuantityMap.value[t.id!] ?? true, |
| | | ), |
| | | ); |
| | | |
| | | function handleOpenTaskDialog() { |
| | | taskDialogRef.value?.open([], { multiple: true }); |
| | | } |
| | | |
| | | function handleTaskSelected(rows: MesProTaskApi.Task[]) { |
| | | /** 按选中任务工序加载是否需要报工数量 */ |
| | | async function handleTaskSelected(rows: MesProTaskApi.Task[]) { |
| | | selectedTasks.value = rows; |
| | | needReportQuantityMap.value = {}; |
| | | for (const task of rows) { |
| | | if (!task.routeId || !task.processId) { |
| | | continue; |
| | | } |
| | | try { |
| | | const routeProcess = await getRouteProcessByRouteAndProcess( |
| | | task.routeId, |
| | | task.processId, |
| | | ); |
| | | needReportQuantityMap.value[task.id!] = |
| | | routeProcess?.needReportQuantity ?? true; |
| | | } catch { |
| | | // 加载失败按默认处理(数量必填) |
| | | needReportQuantityMap.value[task.id!] = true; |
| | | } |
| | | } |
| | | } |
| | | |
| | | function handleRemoveTask(taskId: number) { |
| | | selectedTasks.value = selectedTasks.value.filter((t) => t.id !== taskId); |
| | | delete needReportQuantityMap.value[taskId]; |
| | | } |
| | | |
| | | async function handleSubmit() { |
| | |
| | | message.warning('请选择任务'); |
| | | return; |
| | | } |
| | | if (!formData.value.feedbackQuantity) { |
| | | if (needQuantity.value && !formData.value.feedbackQuantity) { |
| | | message.warning('请输入报工数量'); |
| | | return; |
| | | } |
| | |
| | | try { |
| | | const result = await batchCreateFeedback({ |
| | | taskIds: selectedTasks.value.map((t) => t.id!), |
| | | feedbackQuantity: formData.value.feedbackQuantity, |
| | | feedbackQuantity: needQuantity.value |
| | | ? (formData.value.feedbackQuantity ?? 0) |
| | | : 0, |
| | | remark: formData.value.remark || undefined, |
| | | }); |
| | | if (result.failCount) { |
| | |
| | | </Tag> |
| | | </div> |
| | | </a-form-item> |
| | | <a-form-item label="报工数量" required> |
| | | <a-form-item |
| | | v-if="needQuantity" |
| | | label="报工数量" |
| | | required |
| | | tooltip="工序配置为不需填写报工数量时,合格品/不良品数量由质检确定" |
| | | > |
| | | <a-input-number |
| | | v-model:value="formData.feedbackQuantity" |
| | | :min="0.01" |
| | |
| | | checked: config?.approvalEnabled ?? false, |
| | | checkedChildren: '开', |
| | | unCheckedChildren: '关', |
| | | onChange: (checked: boolean) => { |
| | | handleChange(row, 'INBOUND', checked); |
| | | onChange: (checked) => { |
| | | handleChange(row, 'INBOUND', Boolean(checked)); |
| | | }, |
| | | }); |
| | | }, |
| | |
| | | checked: config?.approvalEnabled ?? false, |
| | | checkedChildren: '开', |
| | | unCheckedChildren: '关', |
| | | onChange: (checked: boolean) => { |
| | | handleChange(row, 'OUTBOUND', checked); |
| | | onChange: (checked) => { |
| | | handleChange(row, 'OUTBOUND', Boolean(checked)); |
| | | }, |
| | | }); |
| | | }, |
| | |
| | | checked: config?.approvalEnabled ?? false, |
| | | checkedChildren: '开', |
| | | unCheckedChildren: '关', |
| | | onChange: (checked: boolean) => { |
| | | handleChange(row, 'TRANSFER', checked); |
| | | onChange: (checked) => { |
| | | handleChange(row, 'TRANSFER', Boolean(checked)); |
| | | }, |
| | | }); |
| | | }, |
| | |
| | | checked: config?.approvalEnabled ?? false, |
| | | checkedChildren: '开', |
| | | unCheckedChildren: '关', |
| | | onChange: (checked: boolean) => { |
| | | handleChange(row, 'CHECK', checked); |
| | | onChange: (checked) => { |
| | | handleChange(row, 'CHECK', Boolean(checked)); |
| | | }, |
| | | }); |
| | | }, |
| | |
| | | } |
| | | |
| | | /** 流程配置表单 */ |
| | | export function useProcessFormSchema(): VbenFormSchema[] { |
| | | export function useProcessFormSchema( |
| | | onBizTypeChange?: (value?: string) => void, |
| | | ): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'warehouseId', |
| | |
| | | componentProps: { |
| | | options: BIZ_TYPE_OPTIONS, |
| | | placeholder: '请选择业务类型', |
| | | onChange: onBizTypeChange, |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { WmsApprovalApi } from '#/api/mes/wm/approval'; |
| | | |
| | | import { ref } from 'vue'; |
| | | import { ref, unref } from 'vue'; |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | |
| | |
| | | columns: useGridColumns(handleApprovalChange), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | data: warehouseConfigList, |
| | | data: unref(warehouseConfigList), |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | |
| | | <script lang="ts" setup> |
| | | import type { WmsApprovalApi } from '#/api/mes/wm/approval'; |
| | | |
| | | import { computed, ref, watch } from 'vue'; |
| | | import { ref, watch } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | |
| | |
| | | }, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: useProcessFormSchema(), |
| | | schema: useProcessFormSchema((value) => { |
| | | currentBizType.value = value; |
| | | }), |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | |
| | | }, |
| | | onOpenChange(isOpen) { |
| | | if (isOpen) { |
| | | currentBizType.value = undefined; |
| | | const data = modalApi.getData() as { |
| | | warehouseId: number; |
| | | warehouseName: string; |
| | |
| | | }, |
| | | }); |
| | | |
| | | /** 当前选中的业务类型 */ |
| | | const currentBizType = computed(() => { |
| | | const values = formApi.getValues(); |
| | | return values.bizType; |
| | | }); |
| | | /** 当前选中的业务类型(formApi.getValues 为异步,改由字段 onChange 同步) */ |
| | | const currentBizType = ref<string>(); |
| | | |
| | | /** 监听业务类型变化,加载对应的流程列表 */ |
| | | watch(currentBizType, async (bizType) => { |
| | |
| | | message.warning('请选择采购订单'); |
| | | return; |
| | | } |
| | | emit('selected', selectedRows.id); |
| | | emit('selected', selectedRows.id!); |
| | | await modalApi.close(); |
| | | }, |
| | | async onOpened() { |
| | |
| | | message.warning('请选择到货通知单'); |
| | | return; |
| | | } |
| | | emit('selected', selectedRows.id); |
| | | emit('selected', selectedRows.id!); |
| | | await modalApi.close(); |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | |
| | | |
| | | if (!enabled) { |
| | | formApi.updateSchema([ |
| | | { fieldName: 'batchCode', dependencies: { show: () => false } }, |
| | | { fieldName: 'manualBatchEntry', dependencies: { show: () => false } }, |
| | | { fieldName: 'produceDate', dependencies: { show: () => false } }, |
| | | { fieldName: 'expireDate', dependencies: { show: () => false } }, |
| | | { fieldName: 'receiptDate', dependencies: { show: () => false } }, |
| | | { fieldName: 'vendorId', dependencies: { show: () => true } }, |
| | | { fieldName: 'purchaseOrderCode', dependencies: { show: () => false } }, |
| | | { fieldName: 'lotNumber', dependencies: { show: () => false } }, |
| | | { fieldName: 'qualityStatus', dependencies: { show: () => false } }, |
| | | { fieldName: 'batchCode', dependencies: { show: () => false, triggerFields: [''] } }, |
| | | { fieldName: 'manualBatchEntry', dependencies: { show: () => false, triggerFields: [''] } }, |
| | | { fieldName: 'produceDate', dependencies: { show: () => false, triggerFields: [''] } }, |
| | | { fieldName: 'expireDate', dependencies: { show: () => false, triggerFields: [''] } }, |
| | | { fieldName: 'receiptDate', dependencies: { show: () => false, triggerFields: [''] } }, |
| | | { fieldName: 'vendorId', dependencies: { show: () => true, triggerFields: [''] } }, |
| | | { fieldName: 'purchaseOrderCode', dependencies: { show: () => false, triggerFields: [''] } }, |
| | | { fieldName: 'lotNumber', dependencies: { show: () => false, triggerFields: [''] } }, |
| | | { fieldName: 'qualityStatus', dependencies: { show: () => false, triggerFields: [''] } }, |
| | | ]); |
| | | return; |
| | | } |
| | |
| | | const isIn = operateType.value === 1; |
| | | |
| | | formApi.updateSchema([ |
| | | { fieldName: 'batchCode', dependencies: { show: () => manualBatchEntry.value } }, |
| | | { fieldName: 'manualBatchEntry', dependencies: { show: () => true } }, |
| | | { fieldName: 'produceDate', dependencies: { show: () => isIn && !!item?.produceDateFlag } }, |
| | | { fieldName: 'expireDate', dependencies: { show: () => isIn && !!item?.expireDateFlag } }, |
| | | { fieldName: 'receiptDate', dependencies: { show: () => isIn && !!item?.receiptDateFlag } }, |
| | | { fieldName: 'vendorId', dependencies: { show: () => !isIn || !!item?.vendorFlag } }, |
| | | { fieldName: 'purchaseOrderCode', dependencies: { show: () => isIn && !!item?.purchaseOrderCodeFlag } }, |
| | | { fieldName: 'lotNumber', dependencies: { show: () => isIn && !!item?.lotNumberFlag } }, |
| | | { fieldName: 'qualityStatus', dependencies: { show: () => isIn && !!item?.qualityStatusFlag } }, |
| | | { fieldName: 'batchCode', dependencies: { show: () => manualBatchEntry.value, triggerFields: [''] } }, |
| | | { fieldName: 'manualBatchEntry', dependencies: { show: () => true, triggerFields: [''] } }, |
| | | { fieldName: 'produceDate', dependencies: { show: () => isIn && !!item?.produceDateFlag, triggerFields: [''] } }, |
| | | { fieldName: 'expireDate', dependencies: { show: () => isIn && !!item?.expireDateFlag, triggerFields: [''] } }, |
| | | { fieldName: 'receiptDate', dependencies: { show: () => isIn && !!item?.receiptDateFlag, triggerFields: [''] } }, |
| | | { fieldName: 'vendorId', dependencies: { show: () => !isIn || !!item?.vendorFlag, triggerFields: [''] } }, |
| | | { fieldName: 'purchaseOrderCode', dependencies: { show: () => isIn && !!item?.purchaseOrderCodeFlag, triggerFields: [''] } }, |
| | | { fieldName: 'lotNumber', dependencies: { show: () => isIn && !!item?.lotNumberFlag, triggerFields: [''] } }, |
| | | { fieldName: 'qualityStatus', dependencies: { show: () => isIn && !!item?.qualityStatusFlag, triggerFields: [''] } }, |
| | | ]); |
| | | } |
| | | |
| | |
| | | async function handleOperateTypeChange(type: 1 | 2) { |
| | | operateType.value = type; |
| | | modalTitle.value = type === 1 ? '库存入库' : '库存出库'; |
| | | modalApi.setState({ title: modalTitle.value }); |
| | | await formApi.setFieldValue('operateType', type); |
| | | // 更新批次字段显示 |
| | | await updateBatchFields(); |
| | |
| | | batchEnabled.value = false; |
| | | operateType.value = 1; |
| | | modalTitle.value = '库存调整'; |
| | | modalApi.setState({ title: modalTitle.value }); |
| | | return; |
| | | } |
| | | // 重置表单 |
| | | await formApi.resetForm(); |
| | | // 默认隐藏批次相关字段 |
| | | formApi.updateSchema([ |
| | | { fieldName: 'batchCode', dependencies: { show: () => false } }, |
| | | { fieldName: 'manualBatchEntry', dependencies: { show: () => false } }, |
| | | { fieldName: 'produceDate', dependencies: { show: () => false } }, |
| | | { fieldName: 'expireDate', dependencies: { show: () => false } }, |
| | | { fieldName: 'receiptDate', dependencies: { show: () => false } }, |
| | | { fieldName: 'purchaseOrderCode', dependencies: { show: () => false } }, |
| | | { fieldName: 'lotNumber', dependencies: { show: () => false } }, |
| | | { fieldName: 'qualityStatus', dependencies: { show: () => false } }, |
| | | { fieldName: 'batchCode', dependencies: { show: () => false, triggerFields: [''] } }, |
| | | { fieldName: 'manualBatchEntry', dependencies: { show: () => false, triggerFields: [''] } }, |
| | | { fieldName: 'produceDate', dependencies: { show: () => false, triggerFields: [''] } }, |
| | | { fieldName: 'expireDate', dependencies: { show: () => false, triggerFields: [''] } }, |
| | | { fieldName: 'receiptDate', dependencies: { show: () => false, triggerFields: [''] } }, |
| | | { fieldName: 'purchaseOrderCode', dependencies: { show: () => false, triggerFields: [''] } }, |
| | | { fieldName: 'lotNumber', dependencies: { show: () => false, triggerFields: [''] } }, |
| | | { fieldName: 'qualityStatus', dependencies: { show: () => false, triggerFields: [''] } }, |
| | | ]); |
| | | // 重置手动填写批次号状态 |
| | | manualBatchEntry.value = false; |
| | |
| | | } |
| | | } |
| | | }, |
| | | title: modalTitle, |
| | | title: '库存调整', |
| | | }); |
| | | |
| | | /** 提交表单 */ |
| | |
| | | details = await getProductReceiptDetailListByLineId(line.id!); |
| | | detailMap[line.id!] = details; |
| | | } |
| | | // 勾选入库的行必须已有上架明细,且明细数量之和等于行收货数量 |
| | | if (!details.length) { |
| | | message.error(`物料「${line.itemName}」已勾选入库,请先添加上架明细`); |
| | | return; |
| | | } |
| | | const lineQty = Number(line.quantity ?? 0); |
| | | const sumQty = details.reduce( |
| | | (acc, detail) => acc + Number(detail.quantity ?? 0), |
| | | 0, |
| | | ); |
| | | if (lineQty !== sumQty) { |
| | | message.error( |
| | | `物料「${line.itemName}」上架明细数量之和(${sumQty})与收货数量(${lineQty})不一致`, |
| | | ); |
| | | return; |
| | | } |
| | | row.details = details.map((detail) => ({ |
| | | warehouseId: detail.warehouseId!, |
| | | locationId: detail.locationId!, |
| | |
| | | contactAddress: notice?.recipientAddress, |
| | | contactName: notice?.recipientName, |
| | | contactTelephone: notice?.recipientTelephone, |
| | | salesOrderCode: notice?.salesOrderCode, |
| | | salesOrderCode: notice?.saleOrderCode, |
| | | }); |
| | | }, |
| | | status: MesWmSalesNoticeStatusEnum.APPROVED, |
| | |
| | | try { |
| | | formData.value = await getReturnSales(data.id); |
| | | // 通过销售订单号反查订单ID,确保 ErpSaleOrderSelect 能正确回显 |
| | | if (formData.value?.salesOrderCode && !formData.value.saleOrderId) { |
| | | const formValues = formData.value as MesWmReturnSalesApi.ReturnSales & { |
| | | saleOrderId?: number; |
| | | }; |
| | | if (formValues.salesOrderCode && !formValues.saleOrderId) { |
| | | const orderPage = await getSaleOrderPage({ |
| | | pageNo: 1, |
| | | pageSize: 1, |
| | | no: formData.value.salesOrderCode, |
| | | } as Record<string, unknown> as never); |
| | | const list = (orderPage as Record<string, unknown>)?.list as Array<Record<string, unknown>> | undefined; |
| | | if (list?.length) { |
| | | const orderId = list[0].id as number; |
| | | (formData.value as Record<string, unknown>).saleOrderId = orderId; |
| | | no: formValues.salesOrderCode, |
| | | } as never); |
| | | const first = orderPage?.list?.[0]; |
| | | if (first) { |
| | | const orderId = Number(first.id); |
| | | formValues.saleOrderId = orderId; |
| | | const order = await getSaleOrder(orderId); |
| | | saleOrderItems.value = order?.items || []; |
| | | } |
| | |
| | | pageNo: 1, |
| | | pageSize: 1, |
| | | }); |
| | | const batchList = batchPage?.list; |
| | | if (batchList?.length) { |
| | | await formApi.setFieldValue('batchId', batchList[0].id); |
| | | const firstBatch = batchPage?.list?.[0]; |
| | | if (firstBatch) { |
| | | await formApi.setFieldValue('batchId', firstBatch.id); |
| | | } |
| | | } catch { |
| | | // 查询失败时忽略 |