| | |
| | | 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.mysql.wm.materialstock.MesWmMaterialStockMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.warehouse.MesWmWarehouseAreaMapper; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| | |
| | | |
| | | @Resource |
| | | private MesWmMaterialStockMapper materialStockMapper; |
| | | @Resource |
| | | private MesWmWarehouseAreaMapper warehouseAreaMapper; |
| | | |
| | | @Resource |
| | | private MesMdmItemService mdmItemService; |
| | |
| | | @Override |
| | | public MesWmMaterialStockDO getOrCreateMaterialStock(Long itemId, Long warehouseId, Long locationId, Long areaId, |
| | | Long batchId, String batchCode, Long vendorId, LocalDateTime receiptTime) { |
| | | // 1. 查找已有库存记录(vendorId 参与唯一键) |
| | | MesWmMaterialStockDO stock = materialStockMapper.selectByCompositeKey( |
| | | // 所有库存创建入口共用库位锁,避免与导入并发时创建重复台账。 |
| | | lockAreaAndValidateMixing(areaId, itemId, batchId); |
| | | MesWmMaterialStockDO stock = materialStockMapper.selectByCompositeKeyForUpdate( |
| | | itemId, warehouseId, locationId, areaId, batchId, vendorId); |
| | | if (stock != null) { |
| | | return stock; |
| | |
| | | } |
| | | } |
| | | |
| | | private void lockAreaAndValidateMixing(Long areaId, Long itemId, Long batchId) { |
| | | MesWmWarehouseAreaDO area = warehouseAreaMapper.selectOne(new LambdaQueryWrapperX<MesWmWarehouseAreaDO>() |
| | | .eq(MesWmWarehouseAreaDO::getId, areaId).last("FOR UPDATE")); |
| | | if (area == null) { |
| | | areaService.validateWarehouseAreaExists(areaId); |
| | | throw exception(WM_STOCK_IMPORT_INVALID, "库位不存在"); |
| | | } |
| | | List<MesWmMaterialStockDO> stocks = materialStockMapper.selectAreaStocksForUpdate(areaId); |
| | | if (Boolean.FALSE.equals(area.getAllowItemMixing()) && itemId != null |
| | | && CollUtil.anyMatch(stocks, stock -> ObjUtil.notEqual(itemId, stock.getItemId()))) { |
| | | throw exception(WM_MATERIAL_STOCK_AREA_ITEM_MIXING_NOT_ALLOWED, area.getName()); |
| | | } |
| | | if (Boolean.FALSE.equals(area.getAllowBatchMixing()) && batchId != null |
| | | && CollUtil.anyMatch(stocks, stock -> ObjUtil.notEqual(batchId, stock.getBatchId()))) { |
| | | throw exception(WM_MATERIAL_STOCK_AREA_BATCH_MIXING_NOT_ALLOWED, area.getName()); |
| | | } |
| | | } |
| | | @Override |
| | | public void checkAreaMixingRule(Long areaId, Long itemId, Long batchId) { |
| | | // 1.1 校验库位必须存在 |
| | |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long importMaterialStockIn(MesWmMaterialStockAdjustReqVO reqVO, Long importId, String reference) { |
| | | return doAdjustMaterialStock(reqVO, true, importId, reference); |
| | | } |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long adjustMaterialStockOut(MesWmMaterialStockAdjustReqVO reqVO) { |
| | | return doAdjustMaterialStock(reqVO, false); |
| | | } |
| | |
| | | * @return 库存记录ID |
| | | */ |
| | | private Long doAdjustMaterialStock(MesWmMaterialStockAdjustReqVO reqVO, boolean isInbound) { |
| | | return doAdjustMaterialStock(reqVO, isInbound, null, null); |
| | | } |
| | | |
| | | private Long doAdjustMaterialStock(MesWmMaterialStockAdjustReqVO reqVO, boolean isInbound, Long importId, String reference) { |
| | | // 1. 校验物料存在且启用 |
| | | MesMdmItemDO item = mdmItemService.validateItemExistsAndEnable(reqVO.getItemId()); |
| | | |
| | |
| | | } |
| | | |
| | | // 3. 校验库位混放规则 |
| | | checkAreaMixingRule(reqVO.getAreaId(), reqVO.getItemId(), batchId); |
| | | if (importId == null) { |
| | | checkAreaMixingRule(reqVO.getAreaId(), reqVO.getItemId(), batchId); |
| | | } |
| | | |
| | | // 4. 获取或创建库存记录 |
| | | MesWmMaterialStockDO stock = getOrCreateMaterialStock( |
| | | reqVO.getItemId(), |
| | | reqVO.getWarehouseId(), |
| | | reqVO.getLocationId(), |
| | | reqVO.getAreaId(), |
| | | batchId, |
| | | batchCode, |
| | | reqVO.getVendorId(), |
| | | LocalDateTime.now() |
| | | ); |
| | | |
| | | MesWmMaterialStockDO stock; |
| | | if (importId != null) { |
| | | lockAreaAndValidateMixing(reqVO.getAreaId(), reqVO.getItemId(), batchId); |
| | | List<MesWmMaterialStockDO> matches = reqVO.getVendorId() == null |
| | | ? materialStockMapper.selectImportBaseStocksForUpdate(reqVO.getItemId(), reqVO.getWarehouseId(), |
| | | reqVO.getLocationId(), reqVO.getAreaId(), batchId) |
| | | : materialStockMapper.selectImportStocksForUpdate(reqVO.getItemId(), reqVO.getWarehouseId(), |
| | | reqVO.getLocationId(), reqVO.getAreaId(), batchId, reqVO.getVendorId()); |
| | | if (matches.size() > 1) throw exception(WM_STOCK_IMPORT_INVALID, |
| | | reqVO.getVendorId() == null ? "该库存维度存在多条记录,请填写供应商编码并处理重复库存" : "库存维度存在重复记录,请先处理后导入"); |
| | | if (reqVO.getVendorId() == null && matches.size() == 1) reqVO.setVendorId(matches.get(0).getVendorId()); |
| | | if (matches.isEmpty()) { |
| | | stock = MesWmMaterialStockDO.builder().itemId(item.getId()).itemTypeId(item.getCategoryId()) |
| | | .warehouseId(reqVO.getWarehouseId()).locationId(reqVO.getLocationId()).areaId(reqVO.getAreaId()) |
| | | .batchId(batchId).batchCode(batchCode).vendorId(reqVO.getVendorId()) |
| | | .quantity(BigDecimal.ZERO).receiptTime(LocalDateTime.now()).frozen(false).build(); |
| | | materialStockMapper.insert(stock); |
| | | } else { |
| | | stock = matches.get(0); |
| | | } |
| | | } else { |
| | | stock = getOrCreateMaterialStock(reqVO.getItemId(), reqVO.getWarehouseId(), reqVO.getLocationId(), |
| | | reqVO.getAreaId(), batchId, batchCode, reqVO.getVendorId(), LocalDateTime.now()); |
| | | } |
| | | // 5. 计算变动数量:入库为正,出库为负 |
| | | BigDecimal quantity = isInbound ? reqVO.getQuantity() : reqVO.getQuantity().negate(); |
| | | |
| | | // 6. 创建库存事务流水(内部会更新库存数量) |
| | | int bizType = isInbound ? MesBizTypeConstants.WM_STOCK_ADJUST_IN : MesBizTypeConstants.WM_STOCK_ADJUST_OUT; |
| | | int bizType = importId != null ? MesBizTypeConstants.WM_STOCK_IMPORT_IN : (isInbound ? MesBizTypeConstants.WM_STOCK_ADJUST_IN : MesBizTypeConstants.WM_STOCK_ADJUST_OUT); |
| | | MesWmTransactionSaveReqDTO transaction = new MesWmTransactionSaveReqDTO() |
| | | .setType(isInbound ? MesWmTransactionTypeEnum.IN.getType() : MesWmTransactionTypeEnum.OUT.getType()) |
| | | .setBizType(bizType) |
| | | .setBizId(stock.getId()) |
| | | .setBizCode("STOCK_ADJUST_" + stock.getId()) |
| | | .setBizLineId(stock.getId()) |
| | | .setBizId(importId != null ? importId : stock.getId()) |
| | | .setBizCode(importId != null ? reference : "STOCK_ADJUST_" + stock.getId()) |
| | | .setBizLineId(importId != null ? importId : stock.getId()) |
| | | .setItemId(reqVO.getItemId()) |
| | | .setQuantity(quantity) |
| | | .setBatchId(batchId) |
| | |
| | | .setAreaId(reqVO.getAreaId()) |
| | | .setVendorId(reqVO.getVendorId()) |
| | | .setReceiptTime(LocalDateTime.now()) |
| | | .setMaterialStockId(importId != null ? stock.getId() : null) |
| | | .setCheckFlag(!isInbound); |
| | | wmTransactionService.createTransaction(transaction); |
| | | |