feat(wm): 入库单和领料单支持手工填写关联信息
- 采购入库单新增 notice_code、vendor_name 冗余字段,供应商和到货通知单改为非必填
- 生产领料单新增 work_order_code、task_code、workstation_name 冗余字段,工单、任务、工作站改为非必填
- 查询接口优先使用单据上的手工文本,为空时回退按编号反查,兼容历史数据
- 保存校验逻辑调整:未选择主数据时跳过供应商存在性校验和通知单一致性比对
| | |
| | | {"name": "报工单", "module": "生产", "table": "mes_pro_feedback", "doc": "草稿0→审批中2→待检3/完成4,按工序顺序校验(上道工序报满才可报),联动倒冲消耗/产出入库/IPQC", "upstream": ["生产任务"], "downstream": ["产品产出", "物料消耗", "IPQC"], "keywords": "报工 feedback"}, |
| | | {"name": "工艺路线", "module": "生产", "table": "mes_pro_route", "doc": "工序顺序+质检/关键/倒冲标记,驱动工单任务与报工规则", "upstream": [], "downstream": ["生产工单"], "keywords": "工艺 路线 route 工序"}, |
| | | {"name": "到货通知单", "module": "仓储", "table": "mes_wm_arrival_notice", "doc": "采购订单确认收货生成,草稿0→待质检2→待入库3→完成4", "upstream": ["采购订单"], "downstream": ["IQC", "采购入库"], "keywords": "到货 收货 arrival"}, |
| | | {"name": "采购入库单", "module": "仓储", "table": "mes_wm_item_receipt", "doc": "草稿0→待上架2→待执行3→完成4,完成后库存+并回写 ERP inCount/inStatus", "upstream": ["到货通知"], "downstream": ["库存台账", "采购订单(回写)"], "keywords": "采购入库 收货入库 receipt"}, |
| | | {"name": "生产领料单", "module": "仓储", "table": "mes_wm_product_issue", "doc": "草稿0→待拣货2→待执行3→完成4,库存-", "upstream": ["生产工单"], "downstream": ["库存台账"], "keywords": "领料 issue"}, |
| | | {"name": "采购入库单", "module": "仓储", "table": "mes_wm_item_receipt", "doc": "草稿0→待上架2→待执行3→完成4,完成后库存+并回写 ERP inCount/inStatus;北方科技branch:到货通知单号(notice_code)与供应商名称(vendor_name)改为手工填写且非必填,可不依赖前置到货通知单", "upstream": ["到货通知(可选)"], "downstream": ["库存台账", "采购订单(回写)"], "keywords": "采购入库 收货入库 receipt 手工填写 供应商 到货通知单"}, |
| | | {"name": "生产领料单", "module": "仓储", "table": "mes_wm_product_issue", "doc": "草稿0→待拣货2→待执行3→完成4,库存-;北方科技branch:生产订单号(work_order_code)、生产任务号(task_code)、工作站名称(workstation_name)改为手工填写且非必填,不再联动带出;无工单ID时跳过BOM防错料校验、不写任务投料记录", "upstream": ["生产工单(可选/手填)"], "downstream": ["库存台账"], "keywords": "领料 issue 手工填写 生产订单 生产任务 工作站"}, |
| | | {"name": "销售出库单", "module": "仓储", "table": "mes_wm_product_sales", "doc": "发货通知审核后生成,提交→OQC检测→拣货→发运10→待执行3→完成4,回写销售订单出库数与状态", "upstream": ["发货通知", "销售订单"], "downstream": ["库存台账", "OQC"], "keywords": "销售出库 发货 出库单 sales"}, |
| | | {"name": "销售退货单", "module": "仓储", "table": "mes_wm_return_sales", "doc": "草稿→待检验(RQC)→待上架→待入库→完成,售后与手工两来源", "upstream": ["售后退货申请"], "downstream": ["库存台账", "RQC"], "keywords": "销售退货 退货入库"}, |
| | | {"name": "供应商退货单", "module": "仓储", "table": "mes_wm_return_vendor", "doc": "IQC 不合格退货发起,待拣货→待执行→完成,库存-", "upstream": ["IQC"], "downstream": ["库存台账", "采购订单(returnCount)"], "keywords": "供应商退货 退料 退货 vendor"}, |
| | |
| | | `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '名称', |
| | | `iqc_id` bigint DEFAULT NULL COMMENT '来料检验单编号', |
| | | `notice_id` bigint DEFAULT NULL COMMENT '到货通知单编号', |
| | | `notice_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '到货通知单编码(手工填写/冗余)', |
| | | `purchase_order_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '采购订单号', |
| | | `purchase_order_id` bigint DEFAULT NULL COMMENT '采购订单编号', |
| | | `vendor_id` bigint DEFAULT NULL COMMENT '供应商编号', |
| | | `vendor_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '供应商名称(手工填写/冗余)', |
| | | `receipt_date` datetime DEFAULT NULL COMMENT '退库日期', |
| | | `status` int DEFAULT NULL COMMENT '状态', |
| | | `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', |
| | |
| | | `code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '单号', |
| | | `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '名称', |
| | | `work_order_id` bigint DEFAULT NULL COMMENT '生产工单 ID', |
| | | `work_order_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '生产工单编号(手工填写/冗余)', |
| | | `task_id` bigint DEFAULT NULL COMMENT '生产任务ID', |
| | | `task_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '生产任务编码(手工填写/冗余)', |
| | | `issue_date` datetime DEFAULT NULL COMMENT '领料日期', |
| | | `required_time` datetime DEFAULT NULL COMMENT '需求时间', |
| | | `status` int DEFAULT NULL COMMENT '状态', |
| | |
| | | `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', |
| | | `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', |
| | | `workstation_id` int DEFAULT NULL, |
| | | `workstation_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '工作站名称(手工填写/冗余)', |
| | | PRIMARY KEY (`id`) USING BTREE |
| | | ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='MES 生产领料单'; |
| | | |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | |
| | | convertSet(list, MesWmItemReceiptDO::getNoticeId)); |
| | | // 2. 构建结果 |
| | | return BeanUtils.toBean(list, MesWmItemReceiptRespVO.class, vo -> { |
| | | MapUtils.findAndThen(supplierMap, vo.getVendorId(), |
| | | supplier -> vo.setVendorName(supplier.getName())); |
| | | // 供应商名称、到货通知单编码以单据上的文本为准(支持手工填写);为空时才回退到按编号反查,兼容历史数据 |
| | | if (StrUtil.isEmpty(vo.getVendorName())) { |
| | | MapUtils.findAndThen(supplierMap, vo.getVendorId(), |
| | | supplier -> vo.setVendorName(supplier.getName())); |
| | | } |
| | | MapUtils.findAndThen(iqcMap, vo.getIqcId(), |
| | | iqc -> vo.setIqcCode(iqc.getCode())); |
| | | MapUtils.findAndThen(noticeMap, vo.getNoticeId(), notice -> |
| | | vo.setNoticeCode(notice.getCode())); |
| | | if (StrUtil.isEmpty(vo.getNoticeCode())) { |
| | | MapUtils.findAndThen(noticeMap, vo.getNoticeId(), notice -> |
| | | vo.setNoticeCode(notice.getCode())); |
| | | } |
| | | }); |
| | | } |
| | | |
| | |
| | | @Schema(description = "供应商编号", example = "1") |
| | | private Long vendorId; |
| | | |
| | | @Schema(description = "供应商名称", example = "某供应商") |
| | | private String vendorName; |
| | | |
| | | @Schema(description = "入库日期") |
| | | @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) |
| | | private LocalDateTime[] receiptDate; |
| | |
| | | @Schema(description = "到货通知单编号", example = "1") |
| | | private Long noticeId; |
| | | |
| | | @Schema(description = "供应商编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "供应商不能为空") |
| | | @Schema(description = "到货通知单编码", example = "AN2026020001") |
| | | private String noticeCode; |
| | | |
| | | @Schema(description = "供应商编号", example = "1") |
| | | private Long vendorId; |
| | | |
| | | @Schema(description = "供应商名称", example = "某供应商") |
| | | private String vendorName; |
| | | |
| | | @Schema(description = "入库日期", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | @NotNull(message = "入库日期不能为空") |
| | | private LocalDateTime receiptDate; |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.wm.productissue; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | |
| | | convertSet(workOrderMap.values(), MesProWorkOrderDO::getClientId)); |
| | | // 2. 构建结果 |
| | | return BeanUtils.toBean(list, MesWmProductIssueRespVO.class, vo -> { |
| | | MapUtils.findAndThen(workstationMap, vo.getWorkstationId(), |
| | | workstation -> vo.setWorkstationName(workstation.getName())); |
| | | MapUtils.findAndThen(taskMap, vo.getTaskId(), task -> |
| | | vo.setTaskCode(task.getCode()).setTaskName(task.getName())); |
| | | MapUtils.findAndThen(workOrderMap, vo.getWorkOrderId(), workOrder -> { |
| | | vo.setWorkOrderCode(workOrder.getCode()); |
| | | MapUtils.findAndThen(clientMap, workOrder.getClientId(), client -> |
| | | vo.setClientName(client.getName())); |
| | | }); |
| | | // 工作站名称以单据上的文本为准(支持手工填写);为空时才按编号反查,兼容历史数据 |
| | | if (StrUtil.isEmpty(vo.getWorkstationName())) { |
| | | MapUtils.findAndThen(workstationMap, vo.getWorkstationId(), |
| | | workstation -> vo.setWorkstationName(workstation.getName())); |
| | | } |
| | | // 生产任务编码同理;taskName 不落库,仍按编号反查 |
| | | if (StrUtil.isEmpty(vo.getTaskCode())) { |
| | | MapUtils.findAndThen(taskMap, vo.getTaskId(), task -> |
| | | vo.setTaskCode(task.getCode()).setTaskName(task.getName())); |
| | | } |
| | | // 生产工单编号同理 |
| | | if (StrUtil.isEmpty(vo.getWorkOrderCode())) { |
| | | MapUtils.findAndThen(workOrderMap, vo.getWorkOrderId(), workOrder -> |
| | | vo.setWorkOrderCode(workOrder.getCode())); |
| | | } |
| | | // 客户名称:仍按 workOrderId → workOrder.clientId 反查;手填单没有工单编号时保持为空 |
| | | MapUtils.findAndThen(workOrderMap, vo.getWorkOrderId(), workOrder -> |
| | | MapUtils.findAndThen(clientMap, workOrder.getClientId(), client -> |
| | | vo.setClientName(client.getName()))); |
| | | }); |
| | | } |
| | | |
| | |
| | | @Schema(description = "领料单名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "生产领料") |
| | | private String name; |
| | | |
| | | @Schema(description = "生产工单 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @Schema(description = "生产工单 ID", example = "1") |
| | | private Long workOrderId; |
| | | |
| | | @Schema(description = "生产工单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "WO20250226001") |
| | | @Schema(description = "生产工单编号", example = "WO20250226001") |
| | | private String workOrderCode; |
| | | |
| | | @Schema(description = "工作站 ID", example = "1") |
| | |
| | | @NotBlank(message = "领料单名称不能为空") |
| | | private String name; |
| | | |
| | | @Schema(description = "生产任务 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "生产任务 ID 不能为空") |
| | | @Schema(description = "生产任务 ID", example = "1") |
| | | private Long taskId; |
| | | |
| | | @Schema(description = "生产任务编码", example = "TASK001") |
| | | private String taskCode; |
| | | |
| | | @Schema(description = "生产工单 ID", example = "1") |
| | | private Long workOrderId; |
| | | |
| | | @Schema(description = "生产工单编号", example = "WO20250226001") |
| | | private String workOrderCode; |
| | | |
| | | @Schema(description = "工作站 ID", example = "1") |
| | | private Long workstationId; |
| | | |
| | | @Schema(description = "工作站名称", example = "装配工作站") |
| | | private String workstationName; |
| | | |
| | | @Schema(description = "需求时间", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | @NotNull(message = "需求时间不能为空") |
| | | private LocalDateTime requiredTime; |
| | |
| | | */ |
| | | private Long noticeId; |
| | | /** |
| | | * 到货通知单编码 |
| | | * |
| | | * 冗余自 {@link MesWmArrivalNoticeDO#getCode()},也可以手动填写 |
| | | */ |
| | | private String noticeCode; |
| | | /** |
| | | * 采购订单号 |
| | | * |
| | | * 冗余自 {@link MesWmArrivalNoticeDO#getPurchaseOrderCode()},也可以手动填写 |
| | |
| | | */ |
| | | private Long vendorId; |
| | | /** |
| | | * 供应商名称 |
| | | * |
| | | * 冗余自 ERP 供应商,也可以手动填写 |
| | | */ |
| | | private String vendorName; |
| | | /** |
| | | * 入库日期 |
| | | */ |
| | | private LocalDateTime receiptDate; |
| | |
| | | */ |
| | | private Long workstationId; |
| | | /** |
| | | * 工作站名称 |
| | | * |
| | | * 冗余自 {@link MesMdWorkstationDO#getName()},也可以手动填写 |
| | | */ |
| | | private String workstationName; |
| | | /** |
| | | * 生产工单 ID |
| | | * |
| | | * 关联 {@link MesProWorkOrderDO#getId()} |
| | | */ |
| | | private Long workOrderId; |
| | | /** |
| | | * 生产工单编号 |
| | | * |
| | | * 冗余自 {@link MesProWorkOrderDO#getCode()},也可以手动填写 |
| | | */ |
| | | private String workOrderCode; |
| | | /** |
| | | * 生产任务 ID |
| | | * |
| | | * 关联 {@link MesProTaskDO#getId()} |
| | | */ |
| | | private Long taskId; |
| | | /** |
| | | * 生产任务编码 |
| | | * |
| | | * 冗余自 {@link MesProTaskDO#getCode()},也可以手动填写 |
| | | */ |
| | | private String taskCode; |
| | | /** |
| | | * 领料日期 |
| | | */ |
| | | private LocalDateTime issueDate; |
| | |
| | | .likeIfPresent(MesWmItemReceiptDO::getCode, reqVO.getCode()) |
| | | .likeIfPresent(MesWmItemReceiptDO::getName, reqVO.getName()) |
| | | .eqIfPresent(MesWmItemReceiptDO::getVendorId, reqVO.getVendorId()) |
| | | .likeIfPresent(MesWmItemReceiptDO::getVendorName, reqVO.getVendorName()) |
| | | .betweenIfPresent(MesWmItemReceiptDO::getReceiptDate, reqVO.getReceiptDate()) |
| | | .orderByDesc(MesWmItemReceiptDO::getId)); |
| | | } |
| | |
| | | private void validateItemReceiptSaveData(MesWmItemReceiptSaveReqVO reqVO) { |
| | | // 校验编码唯一 |
| | | validateCodeUnique(reqVO.getId(), reqVO.getCode()); |
| | | // 校验供应商存在(采购入库使用 ERP 供应商) |
| | | srmSupplierApi.getSupplier(reqVO.getVendorId()); |
| | | // 校验供应商存在(采购入库使用 ERP 供应商);供应商已改为可选,允许手工填写名称,此时不校验主数据 |
| | | if (reqVO.getVendorId() != null) { |
| | | srmSupplierApi.getSupplier(reqVO.getVendorId()); |
| | | } |
| | | // 校验到货通知单存在 |
| | | if (reqVO.getNoticeId() != null) { |
| | | MesWmArrivalNoticeDO notice = arrivalNoticeService.validateArrivalNoticeReadyForReceipt(reqVO.getNoticeId()); |
| | | if (ObjUtil.notEqual(notice.getVendorId(), reqVO.getVendorId())) { |
| | | // 仅在选择了供应商主数据时才比对与到货通知单的一致性;手填供应商名称时跳过 |
| | | if (reqVO.getVendorId() != null && ObjUtil.notEqual(notice.getVendorId(), reqVO.getVendorId())) { |
| | | throw exception(WM_ARRIVAL_NOTICE_VENDOR_MISMATCH); |
| | | } |
| | | } |
| | |
| | | private void validateItemInWorkOrderBom(Long issueId, Long itemId) { |
| | | // 校验领料单存在,并获取工单编号 |
| | | MesWmProductIssueDO issue = issueService.validateProductIssueExists(issueId); |
| | | // 未关联工单(工单改为手工填写、未选主数据)时没有 BOM 可校验,直接放行 |
| | | if (issue.getWorkOrderId() == null) { |
| | | return; |
| | | } |
| | | // 校验物料是否在工单 BOM 中(防错料) |
| | | List<MesProWorkOrderBomDO> workOrderBoms = workOrderBomService.getWorkOrderBomListByWorkOrderId(issue.getWorkOrderId()); |
| | | if (CollUtil.isEmpty(workOrderBoms)) { |
| | |
| | | // 1. 校验关联数据 |
| | | validateProductIssueSaveData(createReqVO); |
| | | |
| | | // 2. 从 task 自动带入 workOrderId 和 workstationId |
| | | MesProTaskDO task = taskService.validateTaskExists(createReqVO.getTaskId()); |
| | | if (createReqVO.getWorkOrderId() == null) { |
| | | createReqVO.setWorkOrderId(task.getWorkOrderId()); |
| | | } |
| | | if (createReqVO.getWorkstationId() == null) { |
| | | createReqVO.setWorkstationId(task.getWorkstationId()); |
| | | // 2. 从 task 自动带入 workOrderId 和 workstationId; |
| | | // 生产任务改为可选:手填单据不传 taskId,工单与工作站由人工填写,不自动带入 |
| | | if (createReqVO.getTaskId() != null) { |
| | | MesProTaskDO task = taskService.validateTaskExists(createReqVO.getTaskId()); |
| | | if (createReqVO.getWorkOrderId() == null) { |
| | | createReqVO.setWorkOrderId(task.getWorkOrderId()); |
| | | } |
| | | if (createReqVO.getWorkstationId() == null) { |
| | | createReqVO.setWorkstationId(task.getWorkstationId()); |
| | | } |
| | | } |
| | | |
| | | // 3. 插入主表 |
| | |
| | | // 1.2 校验关联数据 |
| | | validateProductIssueSaveData(updateReqVO); |
| | | |
| | | // 2. 从 task 自动带入 workOrderId 和 workstationId |
| | | MesProTaskDO task = taskService.validateTaskExists(updateReqVO.getTaskId()); |
| | | if (updateReqVO.getWorkOrderId() == null) { |
| | | updateReqVO.setWorkOrderId(task.getWorkOrderId()); |
| | | } |
| | | if (updateReqVO.getWorkstationId() == null) { |
| | | updateReqVO.setWorkstationId(task.getWorkstationId()); |
| | | // 2. 从 task 自动带入 workOrderId 和 workstationId; |
| | | // 生产任务改为可选:手填单据不传 taskId,工单与工作站由人工填写,不自动带入 |
| | | if (updateReqVO.getTaskId() != null) { |
| | | MesProTaskDO task = taskService.validateTaskExists(updateReqVO.getTaskId()); |
| | | if (updateReqVO.getWorkOrderId() == null) { |
| | | updateReqVO.setWorkOrderId(task.getWorkOrderId()); |
| | | } |
| | | if (updateReqVO.getWorkstationId() == null) { |
| | | updateReqVO.setWorkstationId(task.getWorkstationId()); |
| | | } |
| | | } |
| | | |
| | | // 3. 更新主表 |
| | |
| | | */ |
| | | private void validateProductIssueSaveData(MesWmProductIssueSaveReqVO reqVO) { |
| | | validateCodeUnique(reqVO.getId(), reqVO.getCode()); |
| | | // 校验 task 存在 |
| | | MesProTaskDO task = taskService.validateTaskExists(reqVO.getTaskId()); |
| | | // 校验 task 关联的工单已确认 |
| | | Long workOrderId = reqVO.getWorkOrderId() != null ? reqVO.getWorkOrderId() : task.getWorkOrderId(); |
| | | if (workOrderId != null) { |
| | | workOrderService.validateWorkOrderConfirmed(workOrderId); |
| | | // 生产任务改为可选:传了才校验任务存在,并按任务带出的工单校验已确认 |
| | | if (reqVO.getTaskId() != null) { |
| | | MesProTaskDO task = taskService.validateTaskExists(reqVO.getTaskId()); |
| | | // 校验 task 关联的工单已确认 |
| | | Long workOrderId = reqVO.getWorkOrderId() != null ? reqVO.getWorkOrderId() : task.getWorkOrderId(); |
| | | if (workOrderId != null) { |
| | | workOrderService.validateWorkOrderConfirmed(workOrderId); |
| | | } |
| | | } else if (reqVO.getWorkOrderId() != null) { |
| | | // 没传 taskId 但传了 workOrderId,仍校验工单已确认 |
| | | workOrderService.validateWorkOrderConfirmed(reqVO.getWorkOrderId()); |
| | | } |
| | | if (reqVO.getWorkstationId() != null) { |
| | | workstationService.validateWorkstationExistsAndEnable(reqVO.getWorkstationId()); |
| | |
| | | package cn.iocoder.yudao.module.mes.service.wm.itemreceipt; |
| | | |
| | | import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; |
| | | import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi; |
| | | import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi; |
| | | import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.MesWmItemReceiptSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceiptDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceiptDetailDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.itemreceipt.MesWmItemReceiptMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmItemReceiptStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeLineService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService; |
| | | import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.springframework.context.annotation.Import; |
| | | import org.springframework.test.context.bean.override.mockito.MockitoBean; |
| | |
| | | import jakarta.annotation.Resource; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | import java.util.Arrays; |
| | | import java.util.Collections; |
| | | |
| | |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; |
| | | import static org.junit.jupiter.api.Assertions.*; |
| | | import static org.mockito.ArgumentMatchers.anyList; |
| | | import static org.mockito.ArgumentMatchers.anyLong; |
| | | import static org.mockito.ArgumentMatchers.eq; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | |
| | | private MesQcIqcService iqcService; |
| | | @MockitoBean |
| | | private MesWmTransactionService wmTransactionService; |
| | | @MockitoBean |
| | | private MesWmArrivalNoticeLineService arrivalNoticeLineService; |
| | | @MockitoBean |
| | | private SrmSupplierApi srmSupplierApi; |
| | | @MockitoBean |
| | | private BpmProcessInstanceApi processInstanceApi; |
| | | @MockitoBean |
| | | private ErpPurchaseOrderApi purchaseOrderApi; |
| | | @MockitoBean |
| | | private MdmItemApi mdmItemApi; |
| | | |
| | | // ========== createItemReceipt:供应商 / 到货通知单支持手工填写 ========== |
| | | |
| | | @Test |
| | | public void testCreateItemReceipt_success_withManualVendorNameAndNoticeCode() { |
| | | // 准备参数:只手工填写供应商名称与到货通知单号,不选供应商主数据、不选到货通知单 |
| | | MesWmItemReceiptSaveReqVO reqVO = new MesWmItemReceiptSaveReqVO(); |
| | | reqVO.setCode("IR-MANUAL-" + randomLongId()); |
| | | reqVO.setName("手工供应商入库单"); |
| | | reqVO.setVendorId(null); |
| | | reqVO.setVendorName("某某贸易有限公司"); |
| | | reqVO.setNoticeId(null); |
| | | reqVO.setNoticeCode("AN2026020001"); |
| | | reqVO.setReceiptDate(LocalDateTime.now()); |
| | | |
| | | // 调用 |
| | | Long id = itemReceiptService.createItemReceipt(reqVO); |
| | | |
| | | // 断言 1:保存成功,手工填写的内容落库 |
| | | MesWmItemReceiptDO saved = itemReceiptMapper.selectById(id); |
| | | assertNotNull(saved); |
| | | assertNull(saved.getVendorId()); |
| | | assertEquals("某某贸易有限公司", saved.getVendorName()); |
| | | assertNull(saved.getNoticeId()); |
| | | assertEquals("AN2026020001", saved.getNoticeCode()); |
| | | // 断言 2:未选择供应商主数据时不校验供应商 |
| | | verify(srmSupplierApi, never()).getSupplier(anyLong()); |
| | | } |
| | | |
| | | @Test |
| | | public void testCreateItemReceipt_success_withNoticeIdButManualVendor() { |
| | | // 准备参数:关联了到货通知单(兼容历史单据),但供应商是手工填写 |
| | | Long noticeId = randomLongId(); |
| | | when(arrivalNoticeService.validateArrivalNoticeReadyForReceipt(eq(noticeId))) |
| | | .thenReturn(randomPojo(MesWmArrivalNoticeDO.class, o -> o.setVendorId(randomLongId()))); |
| | | |
| | | MesWmItemReceiptSaveReqVO reqVO = new MesWmItemReceiptSaveReqVO(); |
| | | reqVO.setCode("IR-MANUAL-" + randomLongId()); |
| | | reqVO.setName("手工供应商入库单"); |
| | | reqVO.setNoticeId(noticeId); |
| | | reqVO.setVendorId(null); |
| | | reqVO.setVendorName("某某贸易有限公司"); |
| | | reqVO.setReceiptDate(LocalDateTime.now()); |
| | | |
| | | // 调用:不应因「供应商与到货通知单不一致」而报错 |
| | | Long id = itemReceiptService.createItemReceipt(reqVO); |
| | | |
| | | // 断言:保存成功 |
| | | assertNotNull(itemReceiptMapper.selectById(id)); |
| | | } |
| | | |
| | | @Test |
| | | public void testCreateItemReceipt_success_withVendorId() { |
| | | // 准备参数:仍可选择供应商主数据 |
| | | Long vendorId = randomLongId(); |
| | | MesWmItemReceiptSaveReqVO reqVO = new MesWmItemReceiptSaveReqVO(); |
| | | reqVO.setCode("IR-MANUAL-" + randomLongId()); |
| | | reqVO.setName("选择供应商入库单"); |
| | | reqVO.setVendorId(vendorId); |
| | | reqVO.setReceiptDate(LocalDateTime.now()); |
| | | |
| | | // 调用 |
| | | Long id = itemReceiptService.createItemReceipt(reqVO); |
| | | |
| | | // 断言 1:保存成功 |
| | | assertNotNull(itemReceiptMapper.selectById(id)); |
| | | // 断言 2:选择了供应商主数据时仍会校验供应商存在 |
| | | verify(srmSupplierApi).getSupplier(eq(vendorId)); |
| | | } |
| | | |
| | | // ========== finishItemReceipt ========== |
| | | |
| | |
| | | "name" varchar(255) DEFAULT NULL, |
| | | "iqc_id" bigint DEFAULT NULL, |
| | | "notice_id" bigint DEFAULT NULL, |
| | | "notice_code" varchar(64) DEFAULT NULL, |
| | | "vendor_id" bigint DEFAULT NULL, |
| | | "vendor_name" varchar(128) DEFAULT NULL, |
| | | "purchase_order_code" varchar(64) DEFAULT NULL, |
| | | "purchase_order_id" bigint DEFAULT NULL, |
| | | "receipt_date" timestamp DEFAULT NULL, |
| | | "status" tinyint NOT NULL DEFAULT 0, |
| | | "process_instance_id" varchar(64) DEFAULT NULL, |
| | | "process_definition_key" varchar(64) DEFAULT NULL, |
| | | "remark" varchar(500) DEFAULT NULL, |
| | | "creator" varchar(64) DEFAULT '', |
| | | "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, |