| | |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.ObjUtil; |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductReceiptStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; |
| | | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO; |
| | |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.stream.Collectors; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; |
| | |
| | | private MesWmTransactionService wmTransactionService; |
| | | @Resource |
| | | private MesProWorkOrderService workOrderService; |
| | | @Resource |
| | | private MesMdItemService itemService; |
| | | @Resource |
| | | private MesWmWarehouseService warehouseService; |
| | | @Resource |
| | |
| | | throw exception(WM_PRODUCT_RECPT_STATUS_ERROR); |
| | | } |
| | | |
| | | // 校验上架明细已覆盖每一行收货数量:状态一旦推进到「待执行入库」,明细就不能再编辑, |
| | | // 若此时明细为空或数量不足,单据将永远无法执行入库(会被卡死在待执行入库) |
| | | validateProductReceiptStockDetail(receipt); |
| | | |
| | | // 执行上架(待上架 → 待入库) |
| | | productReceiptMapper.updateById(new MesWmProductReceiptDO() |
| | | .setId(id).setStatus(MesWmProductReceiptStatusEnum.APPROVED.getStatus())); |
| | | } |
| | | |
| | | /** |
| | | * 校验执行上架前的上架明细:每一行的收货数量都必须被该行的上架明细合计覆盖 |
| | | * |
| | | * 提示按「具体行 + 双方数量 + 下一步动作」组织,避免只报一句笼统的"数量不匹配" |
| | | */ |
| | | private void validateProductReceiptStockDetail(MesWmProductReceiptDO receipt) { |
| | | List<MesWmProductReceiptLineDO> lines = productReceiptLineService.getProductReceiptLineListByRecptId(receipt.getId()); |
| | | if (CollUtil.isEmpty(lines)) { |
| | | throw new ServiceException(WM_PRODUCT_RECPT_NO_LINE.getCode(), |
| | | String.format("收货单[%s]没有收货行,无法执行上架:请先在单据中添加收货行(物料与收货数量)后再执行上架。", |
| | | receipt.getCode())); |
| | | } |
| | | Map<Long, MesMdItemDO> itemMap = itemService.getItemMap(lines.stream() |
| | | .map(MesWmProductReceiptLineDO::getItemId).filter(Objects::nonNull).collect(Collectors.toSet())); |
| | | for (int i = 0; i < lines.size(); i++) { |
| | | MesWmProductReceiptLineDO line = lines.get(i); |
| | | String lineLabel = String.format("第 %d 行[%s]", i + 1, formatItemLabel(line.getItemId(), itemMap)); |
| | | if (line.getBatchCode() != null) { |
| | | lineLabel += String.format("(批次[%s])", line.getBatchCode()); |
| | | } |
| | | BigDecimal lineQuantity = line.getQuantity() == null ? BigDecimal.ZERO : line.getQuantity(); |
| | | List<MesWmProductReceiptDetailDO> details = |
| | | productReceiptDetailService.getProductReceiptDetailListByLineId(line.getId()); |
| | | // 明细为空时直接提示"去点上架",比提示"合计 0 与 20 不一致"更明确 |
| | | if (CollUtil.isEmpty(details)) { |
| | | throw new ServiceException(WM_PRODUCT_RECPT_NO_DETAIL.getCode(), |
| | | String.format("收货单[%s]%s还没有上架明细(收货数量[%s]):请先在该行点「上架」,填写入库仓库与上架数量," |
| | | + "合计填满 %s 后再执行上架。", receipt.getCode(), lineLabel, |
| | | formatQuantity(lineQuantity), formatQuantity(lineQuantity))); |
| | | } |
| | | BigDecimal detailQuantity = CollectionUtils.getSumValue(details, |
| | | MesWmProductReceiptDetailDO::getQuantity, BigDecimal::add, BigDecimal.ZERO); |
| | | if (detailQuantity.compareTo(lineQuantity) != 0) { |
| | | BigDecimal diff = lineQuantity.subtract(detailQuantity); |
| | | String action = diff.signum() > 0 |
| | | ? String.format("还差[%s]:请在该行点「上架」补登缺少的上架明细", formatQuantity(diff)) |
| | | : String.format("多出[%s]:请删除或调整该行多余的上架明细", formatQuantity(diff.negate())); |
| | | throw new ServiceException(WM_PRODUCT_RECPT_DETAIL_QUANTITY_MISMATCH.getCode(), |
| | | String.format("收货单[%s]%s上架明细合计[%s]与收货数量[%s]不一致,%s,调整一致后再执行上架。", |
| | | receipt.getCode(), lineLabel, formatQuantity(detailQuantity), |
| | | formatQuantity(lineQuantity), action)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 数量显示:去掉数据库 scale 带出的多余 0(20.000000 → 20) |
| | | */ |
| | | private String formatQuantity(BigDecimal quantity) { |
| | | if (quantity == null) { |
| | | return "0"; |
| | | } |
| | | return quantity.stripTrailingZeros().toPlainString(); |
| | | } |
| | | |
| | | /** |
| | | * 物料显示名:优先「名称/编码」,查不到时回退为 ID,避免把裸 ID 直接抛给用户 |
| | | */ |
| | | private String formatItemLabel(Long itemId, Map<Long, MesMdItemDO> itemMap) { |
| | | MesMdItemDO item = itemId == null ? null : itemMap.get(itemId); |
| | | if (item == null) { |
| | | return itemId == null ? "物料[未选择]" : String.format("物料[ID=%s]", itemId); |
| | | } |
| | | return String.format("%s/%s", item.getName(), item.getCode()); |
| | | } |
| | | |
| | | @Override |
| | | public Boolean checkProductReceiptQuantity(Long id) { |
| | | List<MesWmProductReceiptLineDO> lines = productReceiptLineService.getProductReceiptLineListByRecptId(id); |