| | |
| | | import cn.hutool.core.lang.Assert; |
| | | import cn.hutool.core.util.ObjUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.transaction.vo.MesWmTransactionPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transaction.MesWmTransactionDO; |
| | |
| | | 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.transaction.MesWmTransactionMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockStateEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmWarehouseTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO; |
| | | 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 cn.iocoder.yudao.module.mes.service.wm.materialstockbag.MesWmMaterialStockBagService; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | |
| | | @Resource |
| | | private MesWmBatchService batchService; |
| | | @Resource |
| | | private MesMdItemService itemService; |
| | | private MesMdmItemService mdmItemService; |
| | | @Resource |
| | | private MesWmWarehouseService warehouseService; |
| | | @Resource |
| | | private MesWmWarehouseLocationService locationService; |
| | | @Resource |
| | | private MesWmWarehouseAreaService areaService; |
| | | |
| | | @Resource |
| | | private MesWmMaterialStockBagService materialStockBagService; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | |
| | | // 1.5 关联事务校验 |
| | | checkRelatedTransaction(reqDTO); |
| | | |
| | | // 2.1 获取或创建库存记录 |
| | | MesWmMaterialStockDO materialStock = materialStockService.getOrCreateMaterialStock( |
| | | reqDTO.getItemId(), reqDTO.getWarehouseId(), reqDTO.getLocationId(), reqDTO.getAreaId(), |
| | | reqDTO.getBatchId(), reqDTO.getBatchCode(), reqDTO.getVendorId(), reqDTO.getReceiptTime()); |
| | | // 2.1 获取库存记录 |
| | | MesWmMaterialStockDO materialStock; |
| | | MesWmTransactionTypeEnum transactionType = MesWmTransactionTypeEnum.valueOf(reqDTO.getType()); |
| | | boolean isInbound = transactionType != null && transactionType.isInbound(); |
| | | if (reqDTO.getMaterialStockId() != null) { |
| | | // 传入了 materialStockId,直接使用 |
| | | materialStock = materialStockService.getMaterialStock(reqDTO.getMaterialStockId()); |
| | | if (materialStock == null) { |
| | | throw exception(WM_MATERIAL_STOCK_NOT_EXISTS); |
| | | } |
| | | // 校验物料、批次、仓库等维度一致(防止传入错误的 materialStockId) |
| | | if (ObjUtil.notEqual(materialStock.getItemId(), reqDTO.getItemId()) |
| | | || ObjUtil.notEqual(materialStock.getWarehouseId(), reqDTO.getWarehouseId()) |
| | | || ObjUtil.notEqual(materialStock.getLocationId(), reqDTO.getLocationId()) |
| | | || ObjUtil.notEqual(materialStock.getAreaId(), reqDTO.getAreaId())) { |
| | | throw exception(WM_MATERIAL_STOCK_SELECTION_MISMATCH); |
| | | } |
| | | } else if (isInbound) { |
| | | // 入库事务:找不到则自动创建库存记录 |
| | | materialStock = materialStockService.getOrCreateMaterialStock( |
| | | reqDTO.getItemId(), reqDTO.getWarehouseId(), reqDTO.getLocationId(), reqDTO.getAreaId(), |
| | | reqDTO.getBatchId(), reqDTO.getBatchCode(), reqDTO.getVendorId(), reqDTO.getReceiptTime()); |
| | | } else { |
| | | // 出库事务:库存记录必须已存在,不允许自动创建(除非 checkFlag=false 允许负库存) |
| | | materialStock = materialStockService.getMaterialStockByCompositeKey( |
| | | reqDTO.getItemId(), reqDTO.getWarehouseId(), reqDTO.getLocationId(), reqDTO.getAreaId(), |
| | | reqDTO.getBatchId(), reqDTO.getVendorId()); |
| | | if (materialStock == null) { |
| | | // checkFlag=false 时允许负库存,自动创建库存记录(数量将从 0 扣减为负数) |
| | | boolean allowNegative = !ObjUtil.defaultIfNull(reqDTO.getCheckFlag(), true); |
| | | if (allowNegative) { |
| | | materialStock = materialStockService.getOrCreateMaterialStock( |
| | | reqDTO.getItemId(), reqDTO.getWarehouseId(), reqDTO.getLocationId(), reqDTO.getAreaId(), |
| | | reqDTO.getBatchId(), reqDTO.getBatchCode(), reqDTO.getVendorId(), reqDTO.getReceiptTime()); |
| | | } else { |
| | | throw exception(WM_MATERIAL_STOCK_NOT_EXISTS); |
| | | } |
| | | } |
| | | } |
| | | if (materialStock == null || materialStock.getId() == null) { |
| | | throw exception(WM_MATERIAL_STOCK_NOT_EXISTS); |
| | | } |
| | | // 2.2 冻结校验(仓库、库区、库位、库存记录) |
| | | checkFrozen(reqDTO, materialStock); |
| | | // 2.3 更新库存数量 |
| | | boolean checkFlag = ObjUtil.defaultIfNull(reqDTO.getCheckFlag(), true); |
| | | materialStockService.updateMaterialStockQuantity(materialStock.getId(), reqDTO.getQuantity(), checkFlag); |
| | | MesWmMaterialStockDO updatedStock = materialStockService.getMaterialStock(materialStock.getId()); |
| | | BigDecimal newQuantity = updatedStock.getQuantity(); |
| | | // 库存状态:显式指定优先(如扫码入暂存库),否则自动判定——≤0 已出库; |
| | | // >0 时仅目标仓库为合格库(20)才置合格态,否则保持暂存态(10), |
| | | // 避免盘点/调整/调拨等操作把暂存仓库库存翻成合格态 |
| | | Integer stockState = reqDTO.getStockState(); |
| | | if (stockState == null) { |
| | | if (newQuantity.signum() <= 0) { |
| | | stockState = MesWmStockStateEnum.OUT.getState(); |
| | | } else { |
| | | MesWmWarehouseDO warehouse = warehouseService.getWarehouse(reqDTO.getWarehouseId()); |
| | | boolean qualifiedWarehouse = warehouse != null |
| | | && MesWmWarehouseTypeEnum.QUALIFIED.getType().equals(warehouse.getType()); |
| | | stockState = qualifiedWarehouse |
| | | ? MesWmStockStateEnum.QUALIFIED.getState() : MesWmStockStateEnum.TEMP.getState(); |
| | | } |
| | | } |
| | | materialStockService.updateMaterialStockState(updatedStock.getId(), stockState); |
| | | |
| | | // 3. 插入事务流水 |
| | | MesWmTransactionDO transaction = MesWmTransactionDO.builder() |
| | | .type(reqDTO.getType()).quantity(reqDTO.getQuantity()).transactionTime(LocalDateTime.now()) |
| | | .itemId(reqDTO.getItemId()).batchId(reqDTO.getBatchId()).batchCode(reqDTO.getBatchCode()) |
| | | .bagCodeId(reqDTO.getBagCodeId()).bagCode(reqDTO.getBagCode()) |
| | | .warehouseId(reqDTO.getWarehouseId()).locationId(reqDTO.getLocationId()).areaId(reqDTO.getAreaId()) |
| | | .bizType(reqDTO.getBizType()).bizId(reqDTO.getBizId()).bizCode(reqDTO.getBizCode()).bizLineId(reqDTO.getBizLineId()) |
| | | .materialStockId(materialStock.getId()).relatedTransactionId(reqDTO.getRelatedTransactionId()) |
| | | .build(); |
| | | transactionMapper.insert(transaction); |
| | | // 4. 带袋码时,同步维护库存袋码明细(入库建立/激活、出库置已出库、移入迁移、报废) |
| | | boolean skipBagManage = ObjUtil.defaultIfNull(reqDTO.getSkipBagManage(), false); |
| | | if (!skipBagManage && (StrUtil.isNotBlank(reqDTO.getBagCode()) || reqDTO.getBagCodeId() != null)) { |
| | | handleStockBag(reqDTO, materialStock, transaction.getId(), transactionType); |
| | | } |
| | | return transaction.getId(); |
| | | } |
| | | |
| | | /** |
| | | * 按事务类型维护袋码明细,保证汇总库存、袋码明细、事务流水三者一致。 |
| | | */ |
| | | private void handleStockBag(MesWmTransactionSaveReqDTO reqDTO, MesWmMaterialStockDO materialStock, |
| | | Long transactionId, MesWmTransactionTypeEnum transactionType) { |
| | | switch (transactionType) { |
| | | case MOVE_IN: |
| | | // 移入/调拨/质检迁移:更新明细位置与状态 |
| | | materialStockBagService.moveIn(reqDTO, materialStock, transactionId); |
| | | break; |
| | | case IN: |
| | | case ADJUST_IN: |
| | | // 入库/盘盈:建立或激活明细 |
| | | materialStockBagService.inbound(reqDTO, materialStock, transactionId); |
| | | break; |
| | | case MOVE_OUT: |
| | | case OUT: |
| | | case ADJUST_OUT: |
| | | // 移出/出库/盘亏:置为已出库 |
| | | materialStockBagService.outbound(reqDTO, transactionId); |
| | | break; |
| | | default: |
| | | break; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | throw exception(WM_TRANSACTION_LIST_EMPTY); |
| | | } |
| | | reqDTOs.forEach(this::createTransaction); |
| | | } |
| | | |
| | | @Override |
| | | public PageResult<MesWmTransactionDO> getTransactionPage(MesWmTransactionPageReqVO pageReqVO) { |
| | | return transactionMapper.selectPage(pageReqVO); |
| | | } |
| | | |
| | | // ==================== 私有校验方法 ==================== |
| | |
| | | } |
| | | // 2. 入库时:启用批次管理的物料,必须传递批次号 |
| | | if (inbound) { |
| | | MesMdItemDO item = itemService.validateItemExists(reqDTO.getItemId()); |
| | | if (Boolean.TRUE.equals(item.getBatchFlag())) { |
| | | MesMdmItemDO item = mdmItemService.validateItemExists(reqDTO.getItemId()); |
| | | if (Boolean.TRUE.equals(item.getIsBatchManaged())) { |
| | | if (StrUtil.isEmpty(reqDTO.getBatchCode()) && reqDTO.getBatchId() == null) { |
| | | throw exception(WM_TRANSACTION_BATCH_REQUIRED); |
| | | } |