| | |
| | | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.productreceipt.vo.MesWmProductReceiptPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.productreceipt.vo.MesWmProductReceiptSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.productreceipt.vo.MesWmProductReceiptStockReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productreceipt.MesWmProductReceiptDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productreceipt.MesWmProductReceiptDetailDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productreceipt.MesWmProductReceiptLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productreceipt.MesWmProductReceiptDetailMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productreceipt.MesWmProductReceiptLineMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productreceipt.MesWmProductReceiptMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductReceiptStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.feedback.MesProFeedbackService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceLineService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO; |
| | | import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService; |
| | |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; |
| | |
| | | |
| | | @Resource |
| | | private MesWmProductReceiptMapper productReceiptMapper; |
| | | @Resource |
| | | private MesWmProductReceiptLineMapper productReceiptLineMapper; |
| | | @Resource |
| | | private MesWmProductReceiptDetailMapper productReceiptDetailMapper; |
| | | |
| | | @Resource |
| | | private MesWmProductReceiptLineService productReceiptLineService; |
| | |
| | | private MesWmWarehouseLocationService locationService; |
| | | @Resource |
| | | private MesWmWarehouseAreaService areaService; |
| | | @Resource |
| | | private MesWmProductProduceMapper productProduceMapper; |
| | | @Resource |
| | | private MesWmProductProduceLineService productProduceLineService; |
| | | @Resource |
| | | private MesProFeedbackService feedbackService; |
| | | @Resource |
| | | private MesMdAutoCodeRecordService autoCodeRecordService; |
| | | |
| | | @Override |
| | | public Long createProductReceipt(MesWmProductReceiptSaveReqVO createReqVO) { |
| | |
| | | if (ObjUtil.notEqual(MesWmProductReceiptStatusEnum.APPROVED.getStatus(), receipt.getStatus())) { |
| | | throw exception(WM_PRODUCT_RECPT_STATUS_ERROR); |
| | | } |
| | | // 未勾选入库或未分配完的数量默认补到不合格仓库 |
| | | supplementDefectDetails(receipt); |
| | | // 校验明细非空 |
| | | List<MesWmProductReceiptDetailDO> details = productReceiptDetailService.getProductReceiptDetailListByRecptId(id); |
| | | if (CollUtil.isEmpty(details)) { |
| | |
| | | .setId(id).setStatus(MesWmProductReceiptStatusEnum.FINISHED.getStatus())); |
| | | } |
| | | |
| | | /** |
| | | * 为未勾选入库(is_stock=false/空)或分配数量不足的行补一条不合格仓库明细 |
| | | * |
| | | * <p>每行剩余数量 = 行收货数量 - 已分配明细数量之和,剩余 > 0 时自动插入不合格仓库三级明细, |
| | | * 保证执行入库时一个数量对应一条流水。 |
| | | */ |
| | | private void supplementDefectDetails(MesWmProductReceiptDO receipt) { |
| | | List<MesWmProductReceiptLineDO> lines = productReceiptLineService.getProductReceiptLineListByRecptId(receipt.getId()); |
| | | for (MesWmProductReceiptLineDO line : lines) { |
| | | List<MesWmProductReceiptDetailDO> details = productReceiptDetailService.getProductReceiptDetailListByLineId(line.getId()); |
| | | BigDecimal sumDetailQty = CollectionUtils.getSumValue(details, |
| | | MesWmProductReceiptDetailDO::getQuantity, BigDecimal::add, BigDecimal.ZERO); |
| | | BigDecimal remaining = line.getQuantity().subtract(sumDetailQty); |
| | | if (remaining.compareTo(BigDecimal.ZERO) <= 0) { |
| | | continue; |
| | | } |
| | | // 查询不合格仓库三级(按编码约定识别) |
| | | MesWmWarehouseDO defectWarehouse = warehouseService.getWarehouseByCode(MesWmWarehouseDO.DEFECT_WAREHOUSE); |
| | | MesWmWarehouseLocationDO defectLocation = locationService.getWarehouseLocationByCode(MesWmWarehouseLocationDO.DEFECT_LOCATION); |
| | | MesWmWarehouseAreaDO defectArea = areaService.getWarehouseAreaByCode(MesWmWarehouseAreaDO.DEFECT_AREA); |
| | | if (defectWarehouse == null || defectLocation == null || defectArea == null) { |
| | | throw exception(WM_PRODUCT_RECPT_NO_DEFECT_WAREHOUSE); |
| | | } |
| | | productReceiptDetailMapper.insert(new MesWmProductReceiptDetailDO() |
| | | .setLineId(line.getId()) |
| | | .setReceiptId(receipt.getId()) |
| | | .setItemId(line.getItemId()) |
| | | .setQuantity(remaining) |
| | | .setBatchId(line.getBatchId()) |
| | | .setWarehouseId(defectWarehouse.getId()) |
| | | .setLocationId(defectLocation.getId()) |
| | | .setAreaId(defectArea.getId())); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long createProductReceiptByFeedback(Long feedbackId) { |
| | | // 幂等:一次报工只生成一条产品入库单 |
| | | MesWmProductReceiptDO existing = productReceiptMapper.selectByFeedbackId(feedbackId); |
| | | if (existing != null) { |
| | | return existing.getId(); |
| | | } |
| | | // 校验报工存在 |
| | | MesProFeedbackDO feedback = feedbackService.validateFeedbackExists(feedbackId); |
| | | // 生成编码并插入入库单 |
| | | MesWmProductReceiptDO receipt = new MesWmProductReceiptDO() |
| | | .setCode(autoCodeRecordService.generateAutoCode(MesMdAutoCodeRuleCodeEnum.PRODUCTRECPT_CODE.getCode())) |
| | | .setName("产品入库-" + feedback.getCode()) |
| | | .setWorkOrderId(feedback.getWorkOrderId()) |
| | | .setItemId(feedback.getItemId()) |
| | | .setFeedbackId(feedbackId) |
| | | .setReceiptDate(LocalDateTime.now()) |
| | | .setStatus(MesWmProductReceiptStatusEnum.PREPARE.getStatus()); |
| | | productReceiptMapper.insert(receipt); |
| | | // 从产出单产出行生成入库单行(合格/不合格分行,质量状态原样带入) |
| | | MesWmProductProduceDO produce = productProduceMapper.selectByFeedbackId(feedbackId); |
| | | if (produce != null) { |
| | | List<MesWmProductProduceLineDO> produceLines = productProduceLineService.getProductProduceLineListByProduceId(produce.getId()); |
| | | for (MesWmProductProduceLineDO produceLine : produceLines) { |
| | | if (produceLine.getQuantity() == null || produceLine.getQuantity().compareTo(BigDecimal.ZERO) <= 0) { |
| | | continue; |
| | | } |
| | | productReceiptLineMapper.insert(new MesWmProductReceiptLineDO() |
| | | .setReceiptId(receipt.getId()) |
| | | .setItemId(produceLine.getItemId()) |
| | | .setQuantity(produceLine.getQuantity()) |
| | | .setBatchId(produceLine.getBatchId()) |
| | | .setBatchCode(produceLine.getBatchCode()) |
| | | .setQualityStatus(produceLine.getQualityStatus()) |
| | | .setIsStock(Boolean.FALSE)); |
| | | } |
| | | } |
| | | return receipt.getId(); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void submitStock(MesWmProductReceiptStockReqVO reqVO) { |
| | | // 校验入库单存在且为待上架(APPROVING) |
| | | MesWmProductReceiptDO receipt = validateProductReceiptExists(reqVO.getReceiptId()); |
| | | if (ObjUtil.notEqual(MesWmProductReceiptStatusEnum.APPROVING.getStatus(), receipt.getStatus())) { |
| | | throw exception(WM_PRODUCT_RECPT_STATUS_ERROR); |
| | | } |
| | | List<MesWmProductReceiptStockReqVO.Row> rows = reqVO.getRows(); |
| | | if (CollUtil.isEmpty(rows)) { |
| | | throw exception(WM_PRODUCT_RECPT_NO_LINE); |
| | | } |
| | | // 逐行处理:更新是否入库 + 重建上架明细 |
| | | for (MesWmProductReceiptStockReqVO.Row row : rows) { |
| | | MesWmProductReceiptLineDO line = productReceiptLineMapper.selectById(row.getLineId()); |
| | | if (line == null || !ObjUtil.equal(line.getReceiptId(), receipt.getId())) { |
| | | throw exception(WM_PRODUCT_RECPT_LINE_NOT_EXISTS); |
| | | } |
| | | Boolean isStock = Boolean.TRUE.equals(row.getIsStock()); |
| | | productReceiptLineMapper.updateById(new MesWmProductReceiptLineDO() |
| | | .setId(line.getId()).setIsStock(isStock)); |
| | | // 删除该行旧明细,重建分配 |
| | | productReceiptDetailService.deleteProductReceiptDetailByLineId(line.getId()); |
| | | if (!isStock) { |
| | | continue; // 未勾选:不分配,执行入库时自动进不合格仓库 |
| | | } |
| | | // 勾选入库:分配数量之和必须等于行收货数量 |
| | | List<MesWmProductReceiptStockReqVO.Detail> details = row.getDetails(); |
| | | if (CollUtil.isEmpty(details)) { |
| | | throw exception(WM_PRODUCT_RECPT_STOCK_QUANTITY_MISMATCH); |
| | | } |
| | | BigDecimal sumDetailQty = details.stream() |
| | | .map(MesWmProductReceiptStockReqVO.Detail::getQuantity) |
| | | .filter(Objects::nonNull) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | if (line.getQuantity() == null || sumDetailQty.compareTo(line.getQuantity()) != 0) { |
| | | throw exception(WM_PRODUCT_RECPT_STOCK_QUANTITY_MISMATCH); |
| | | } |
| | | // 校验仓库层级关系并插入明细 |
| | | for (MesWmProductReceiptStockReqVO.Detail detail : details) { |
| | | areaService.validateWarehouseAreaExists(detail.getWarehouseId(), detail.getLocationId(), detail.getAreaId()); |
| | | productReceiptDetailMapper.insert(new MesWmProductReceiptDetailDO() |
| | | .setLineId(line.getId()) |
| | | .setReceiptId(receipt.getId()) |
| | | .setItemId(line.getItemId()) |
| | | .setQuantity(detail.getQuantity()) |
| | | .setBatchId(line.getBatchId()) |
| | | .setWarehouseId(detail.getWarehouseId()) |
| | | .setLocationId(detail.getLocationId()) |
| | | .setAreaId(detail.getAreaId())); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void createTransactionList(MesWmProductReceiptDO receipt, List<MesWmProductReceiptDetailDO> details) { |
| | | // 1. 查询虚拟线边库 |
| | | MesWmWarehouseDO virtualWarehouse = warehouseService.getWarehouseByCode(MesWmWarehouseDO.WIP_VIRTUAL_WAREHOUSE); |