| | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.collection.SetUtils; |
| | | import cn.iocoder.yudao.framework.operatelog.core.util.OperateLogUtils; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockAdjustReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockFreezeReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockImportExcelVO; |
| | |
| | | import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService; |
| | | import com.mzt.logapi.starter.annotation.LogRecord; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.context.annotation.Lazy; |
| | | import org.springframework.stereotype.Service; |
| | |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; |
| | | import static cn.iocoder.yudao.module.mes.enums.LogRecordConstants.*; |
| | | |
| | | /** |
| | | * MES 库存台账 Service 实现类 |
| | |
| | | MesMdItemDO item = mdItemService.getItem(pageReqVO.getItemId()); |
| | | if (item != null && item.getMdmItemId() != null) { |
| | | itemIds = SetUtils.asSet(item.getMdmItemId()); |
| | | } else { |
| | | return PageResult.empty(); |
| | | } |
| | | } |
| | | // 1.3 解析 virtualFilter:转换为虚拟仓 warehouseId |
| | |
| | | throw exception(WM_MATERIAL_STOCK_REQUIRED); |
| | | } |
| | | MesWmMaterialStockDO stock = validateMaterialStockExists(materialStockId); |
| | | // 2. 校验库存记录的物料、批次、仓库、库区、库位等信息与前端选择的一致,避免串单或越权提交 |
| | | if (ObjUtil.notEqual(stock.getItemId(), itemId) |
| | | || ObjUtil.notEqual(stock.getBatchId(), batchId) |
| | | || (batchCode != null && ObjUtil.notEqual(stock.getBatchCode(), batchCode)) |
| | | || ObjUtil.notEqual(stock.getWarehouseId(), warehouseId) |
| | | || ObjUtil.notEqual(stock.getLocationId(), locationId) |
| | | || ObjUtil.notEqual(stock.getAreaId(), areaId)) { |
| | | throw exception(WM_MATERIAL_STOCK_SELECTION_MISMATCH); |
| | | // 2. 校验库存记录的物料、批次、仓库、库区、库位等信息与单据一致,避免串单或越权提交。 |
| | | // 逐个维度比对,命中时提示具体差异(库存侧 vs 单据侧)并给出处理办法,避免笼统报"信息不一致" |
| | | String mismatch = buildSelectedStockMismatch( |
| | | stock, itemId, batchId, batchCode, warehouseId, locationId, areaId); |
| | | if (mismatch != null) { |
| | | throw new ServiceException(WM_MATERIAL_STOCK_SELECTION_MISMATCH.getCode(), |
| | | "所选库存记录与单据不一致:" + mismatch + "。请重新选择该物料正确的库存;若仓库/库区/库位/批次被禁用无法修改,请删除本条明细后重新添加。"); |
| | | } |
| | | // 3. 校验库存数量充足(如果前端传了 quantity,则必须保证库存数量 >= quantity) |
| | | if (quantity != null && stock.getQuantity() != null && stock.getQuantity().compareTo(quantity) < 0) { |
| | | throw exception(WM_MATERIAL_STOCK_INSUFFICIENT); |
| | | } |
| | | return stock; |
| | | } |
| | | |
| | | /** |
| | | * 逐维度比对待选库存与单据传入的信息,返回第一个不一致维度的可读描述;全部一致返回 null |
| | | */ |
| | | private String buildSelectedStockMismatch(MesWmMaterialStockDO stock, Long itemId, Long batchId, String batchCode, |
| | | Long warehouseId, Long locationId, Long areaId) { |
| | | if (ObjUtil.notEqual(stock.getItemId(), itemId)) { |
| | | return "所选库存归属物料[" + itemIdText(itemId) + "],单据物料为[" + itemIdText(stock.getItemId()) + "]"; |
| | | } |
| | | if (ObjUtil.notEqual(stock.getBatchId(), batchId)) { |
| | | return "所选库存批次[" + batchText(stock.getBatchId(), stock.getBatchCode()) |
| | | + "],单据批次为[" + batchText(batchId, batchCode) + "]"; |
| | | } |
| | | if (batchCode != null && ObjUtil.notEqual(stock.getBatchCode(), batchCode)) { |
| | | return "所选库存批次号[" + stock.getBatchCode() + "],单据批次号为[" + batchCode + "]"; |
| | | } |
| | | if (ObjUtil.notEqual(stock.getWarehouseId(), warehouseId)) { |
| | | return "所选库存仓库[" + warehouseText(stock.getWarehouseId()) |
| | | + "],单据仓库为[" + warehouseText(warehouseId) + "]"; |
| | | } |
| | | if (ObjUtil.notEqual(stock.getLocationId(), locationId)) { |
| | | return "所选库存库区[" + locationText(stock.getLocationId()) |
| | | + "],单据库区为[" + locationText(locationId) + "]"; |
| | | } |
| | | if (ObjUtil.notEqual(stock.getAreaId(), areaId)) { |
| | | return "所选库存库位[" + areaText(stock.getAreaId()) |
| | | + "],单据库位为[" + areaText(areaId) + "]"; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private String itemIdText(Long itemId) { |
| | | return itemId == null ? "未选择" : ("物料ID=" + itemId); |
| | | } |
| | | |
| | | private String batchText(Long batchId, String batchCode) { |
| | | if (StrUtil.isNotBlank(batchCode)) { |
| | | return batchCode; |
| | | } |
| | | return batchId == null ? "未选择" : ("批次ID=" + batchId); |
| | | } |
| | | |
| | | private String warehouseText(Long warehouseId) { |
| | | if (warehouseId == null) { |
| | | return "未选择"; |
| | | } |
| | | MesWmWarehouseDO warehouse = warehouseService.getWarehouse(warehouseId); |
| | | return warehouse != null && StrUtil.isNotBlank(warehouse.getName()) |
| | | ? warehouse.getName() : ("仓库ID=" + warehouseId); |
| | | } |
| | | |
| | | private String locationText(Long locationId) { |
| | | if (locationId == null) { |
| | | return "未选择"; |
| | | } |
| | | MesWmWarehouseLocationDO location = locationService.getWarehouseLocation(locationId); |
| | | return location != null && StrUtil.isNotBlank(location.getName()) |
| | | ? location.getName() : ("库区ID=" + locationId); |
| | | } |
| | | |
| | | private String areaText(Long areaId) { |
| | | if (areaId == null) { |
| | | return "未选择"; |
| | | } |
| | | MesWmWarehouseAreaDO area = areaService.getWarehouseArea(areaId); |
| | | return area != null && StrUtil.isNotBlank(area.getName()) |
| | | ? area.getName() : ("库位ID=" + areaId); |
| | | } |
| | | |
| | | @Override |
| | |
| | | } else { |
| | | // 出库时校验批次存在 |
| | | if (batchId != null) { |
| | | batchService.validateBatchExists(batchId, reqVO.getItemId()); |
| | | batchCode = batchService.validateBatchExists(batchId, reqVO.getItemId()).getCode(); |
| | | } |
| | | } |
| | | |
| | |
| | | * @return 批次记录(物料未启用批次管理时返回 null) |
| | | */ |
| | | private MesWmBatchDO resolveBatch(MesWmMaterialStockAdjustReqVO reqVO, MesMdmItemDO item) { |
| | | // 1. 物料未启用批次管理,返回 null |
| | | if (Boolean.FALSE.equals(item.getIsBatchManaged())) { |
| | | return null; |
| | | // 即使物料未启用普通批次管理,也要保留并校验盘号追溯批次。 |
| | | if (reqVO.getBatchId() != null) { |
| | | MesWmBatchDO existing = batchService.validateBatchExists(reqVO.getBatchId(), reqVO.getItemId()); |
| | | if (StrUtil.isNotBlank(reqVO.getReelNo()) |
| | | && !Objects.equals(StrUtil.trim(reqVO.getReelNo()), StrUtil.trim(existing.getReelNo()))) { |
| | | throw exception(WM_BATCH_REEL_MISMATCH); |
| | | } |
| | | return existing; |
| | | } |
| | | |
| | | // 2. 传入 batchId,校验并返回已有批次 |
| | | if (reqVO.getBatchId() != null) { |
| | | return batchService.validateBatchExists(reqVO.getBatchId(), reqVO.getItemId()); |
| | | // 1. 未指定追溯批次且物料未启用批次管理,返回 null |
| | | if (Boolean.FALSE.equals(item.getIsBatchManaged()) && StrUtil.isBlank(reqVO.getReelNo())) { |
| | | return null; |
| | | } |
| | | |
| | | // 3. 未传 batchId,自动创建批次 |
| | | MesWmBatchGenerateReqVO batchReqVO = new MesWmBatchGenerateReqVO() |
| | | .setItemId(reqVO.getItemId()) |
| | | .setBatchCode(reqVO.getBatchCode()) |
| | | .setReelNo(StrUtil.trimToNull(reqVO.getReelNo())) |
| | | .setVendorId(reqVO.getVendorId()) |
| | | .setProduceDate(reqVO.getProduceDate() != null ? reqVO.getProduceDate().atStartOfDay() : null) |
| | | .setExpireDate(reqVO.getExpireDate() != null ? reqVO.getExpireDate().atStartOfDay() : null) |
| | |
| | | } |
| | | |
| | | @Override |
| | | @LogRecord(type = MES_WM_MATERIAL_STOCK_TYPE, subType = MES_WM_MATERIAL_STOCK_IMPORT_SUB_TYPE, bizNo = "0", |
| | | success = MES_WM_MATERIAL_STOCK_IMPORT_SUCCESS) |
| | | public MesWmMaterialStockImportRespVO importMaterialStockList(List<MesWmMaterialStockImportExcelVO> importList) { |
| | | // 操作类型取值:增加 / 减少 / 覆盖 |
| | | final String OP_ADD = "增加"; |
| | |
| | | respVO.getFailureNames().put(key, ex.getMessage()); |
| | | } |
| | | } |
| | | // 记录导入结果汇总,供 @LogRecord 的 success 文案引用 |
| | | OperateLogUtils.putImportSummary(respVO.getCreateNames(), respVO.getUpdateNames(), respVO.getFailureNames()); |
| | | return respVO; |
| | | } |
| | | |