diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/transaction/MesWmTransactionDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/transaction/MesWmTransactionDO.java index f0b5e8a..b1c3176 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/transaction/MesWmTransactionDO.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/transaction/MesWmTransactionDO.java @@ -40,16 +40,12 @@ public class MesWmTransactionDO extends BaseDO { // ==================== 业务归属 ==================== - // codex reply:`transactionType` 收敛成 `type` 是对的,和项目里其他 DO 的命名保持一致。 - // codex reply:不过这类改名要连同 `MesWmTransactionSaveReqVO`、Service 和查询条件一起同步,不然 DO 与上下游模型会割裂。 /** * 事务类型 * * 枚举 {@link MesWmTransactionTypeEnum} */ private Integer type; - // codex reply:`transactionQuantity` 收敛成 `quantity` 是对的;在事务流水这个上下文里,`transaction` 前缀没有额外信息。 - // codex reply:`transactionFlag` 删除也合理,直接存带符号数量更符合当前写入逻辑,后续聚合查询也可以直接 `SUM(quantity)`。 /** * 本次变动数量 * @@ -109,7 +105,6 @@ public class MesWmTransactionDO extends BaseDO { * 关联 {@link MesWmBatchDO#getCode()} */ private String batchCode; - // codex reply:`packageId` 当前删除是合理的。事务流水链路没有包装维度的输入输出,先不保留“预留字段”更干净。 // ==================== 库存位置 ==================== @@ -134,8 +129,6 @@ public class MesWmTransactionDO extends BaseDO { // ==================== 时间 ==================== - // codex reply:这里我不建议继续沿用 `erpTime` / `receiptTime` 这组命名;项目里的同类字段基本统一为 `receiptDate`,单独改成 `Time` 会破坏一致性。 - // codex reply:如果这两个字段暂时没有业务赋值,建议宁可先删;若后续保留,ERP 侧更建议 `erpAccountDate`,收货侧保持 `receiptDate`。 /** * 事务发生时间 */ diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/batch/MesWmBatchMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/batch/MesWmBatchMapper.java index af47402..872a3c8 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/batch/MesWmBatchMapper.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/batch/MesWmBatchMapper.java @@ -20,6 +20,10 @@ import java.util.List; @Mapper public interface MesWmBatchMapper extends BaseMapperX { + default MesWmBatchDO selectByCode(String code) { + return selectOne(MesWmBatchDO:: getCode, code); + } + default PageResult selectPage(MesWmBatchPageReqVO reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX() .likeIfPresent(MesWmBatchDO::getCode, reqVO.getCode()) diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java index bef9a10..5794d44 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java @@ -51,15 +51,16 @@ public interface MesWmMaterialStockMapper extends BaseMapperX updateWrapper = new LambdaUpdateWrapper() .eq(MesWmMaterialStockDO::getId, id) .setSql("quantity = quantity + " + count); - if (count.compareTo(BigDecimal.ZERO) < 0) { + if (checkFlag && count.compareTo(BigDecimal.ZERO) < 0) { updateWrapper.ge(MesWmMaterialStockDO::getQuantity, count.abs()); // CAS 防负库存 } return update(null, updateWrapper); diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java index 8ea3e46..188c1cf 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java @@ -355,7 +355,7 @@ public interface ErrorCodeConstants { // ========== MES 仓库管理-库存(1-040-703-000) ========== ErrorCode WM_MATERIAL_STOCK_NOT_EXISTS = new ErrorCode(1_040_703_000, "库存记录不存在"); ErrorCode WM_MATERIAL_STOCK_INSUFFICIENT = new ErrorCode(1_040_703_001, "库存数量不足"); - ErrorCode WM_TRANSACTION_MATERIAL_STOCK_REQUIRED = new ErrorCode(1_040_703_002, "出库事务必须指定库存记录 ID"); + ErrorCode WM_TRANSACTION_TYPE_NOT_EXISTS = new ErrorCode(1_040_703_002, "库存事务类型不存在"); // ========== MES 仓库管理-到货通知单(1-040-704-000) ========== ErrorCode WM_ARRIVAL_NOTICE_NOT_EXISTS = new ErrorCode(1_040_704_000, "到货通知单不存在"); diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java index f0a5c45..292d39a 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java @@ -29,6 +29,21 @@ public final class MesBizTypeConstants { public static final int WM_PACKAGE = 105; // 装箱单:MesWmPackageDO public static final int WM_STOCK = 106; // 库存:MesWmMaterialStockDO public static final int WM_BATCH = 107; // 批次:MesWmBatchDO + public static final int WM_TRANSACTION = 108; // 库存事务流水:MesWmTransactionDO + public static final int WM_ITEM_RECEIPT_IN = 110; // 采购入库:MesWmItemReceiptDO + public static final int WM_TRANSFER_OUT = 111; // 调拨出库:MesWmTransferDO + public static final int WM_TRANSFER_IN = 112; // 调拨入库:MesWmTransferDO + public static final int WM_MISC_ISSUE = 113; // 其他出库:MesWmMiscIssueDO + public static final int WM_MISC_RECPT = 114; // 其他入库:MesWmMiscReceiptDO + public static final int WM_ISSUE = 115; // 生产领料:MesWmProductionIssueDO + public static final int WM_RT_ISSUE = 116; // 生产退料:MesWmReturnIssueDO + public static final int WM_PRODUCT_RECPT = 117; // 生产入库:MesWmProductReceiptDO + public static final int WM_PRODUCT_SALES = 118; // 销售出库:MesWmProductSalesDO + public static final int WM_RT_SALES = 119; // 销售退货入库:MesWmReturnSalesDO + public static final int WM_OUTSOURCE_ISSUE = 120; // 外协发料:MesWmOutsourceIssueDO + public static final int WM_OUTSOURCE_RECPT = 121; // 外协入库:MesWmOutsourceReceiptDO + public static final int WM_ITEM_CONSUME = 122; // 生产消耗:MesWmItemConsumeDO + public static final int WM_PRODUCT_PRODUCE = 123; // 产品产出:MesWmProductProduceDO // ========== CAL 排班模块 [200, 300) ========== diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmTransactionTypeEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmTransactionTypeEnum.java new file mode 100644 index 0000000..ec14715 --- /dev/null +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmTransactionTypeEnum.java @@ -0,0 +1,70 @@ +package cn.iocoder.yudao.module.mes.enums.wm; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * MES 库存事务类型枚举 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum MesWmTransactionTypeEnum implements ArrayValuable { + + /** + * 入库 + */ + IN(1, "入库", true), + /** + * 出库 + */ + OUT(2, "出库", false), + /** + * 调拨移出 + */ + MOVE_OUT(3, "调拨移出", false), + /** + * 调拨移入 + */ + MOVE_IN(4, "调拨移入", true), + /** + * 盘盈 + */ + ADJUST_IN(5, "盘盈", true), + /** + * 盘亏 + */ + ADJUST_OUT(6, "盘亏", false); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesWmTransactionTypeEnum::getType).toArray(Integer[]::new); + + /** + * 类型值 + */ + private final Integer type; + /** + * 类型名 + */ + private final String name; + /** + * 是否为入库方向 + */ + private final boolean inbound; + + public static MesWmTransactionTypeEnum valueOf(Integer type) { + return CollUtil.findOne(CollUtil.newArrayList(values()), + e -> ObjUtil.equal(e.getType(), type)); + } + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchService.java index a9c37fc..9acaf28 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchService.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchService.java @@ -23,6 +23,22 @@ public interface MesWmBatchService { PageResult getBatchPage(MesWmBatchPageReqVO pageReqVO); /** + * 获得批次 + * + * @param id 批次编号 + * @return 批次记录 + */ + MesWmBatchDO getBatch(Long id); + + /** + * 根据批次号获得批次 + * + * @param code 批次号 + * @return 批次记录 + */ + MesWmBatchDO getBatchByCode(String code); + + /** * 获取或生成批次编码 *

* 根据物料批次配置,查询或生成批次记录 diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java index b8264eb..16dc6ca 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java @@ -54,6 +54,16 @@ public class MesWmBatchServiceImpl implements MesWmBatchService { } @Override + public MesWmBatchDO getBatch(Long id) { + return batchMapper.selectById(id); + } + + @Override + public MesWmBatchDO getBatchByCode(String code) { + return batchMapper.selectByCode(code); + } + + @Override @Transactional(rollbackFor = Exception.class) public MesWmBatchDO getOrGenerateBatchCode(MesWmBatchGenerateReqVO reqVO) { // 1.1 查询物料信息,检查是否启用批次管理 diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java index d38fde8..f79da0a 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java @@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterial import jakarta.validation.Valid; import java.math.BigDecimal; +import java.time.LocalDateTime; import java.util.Collection; import java.util.List; import java.util.Map; @@ -28,14 +29,6 @@ public interface MesWmMaterialStockService { MesWmMaterialStockDO getMaterialStock(Long id); /** - * 校验库存记录存在 - * - * @param id 编号 - * @return 库存记录 - */ - MesWmMaterialStockDO validateMaterialStockExists(Long id); - - /** * 获得库存台账分页 * * @param pageReqVO 分页参数 @@ -95,31 +88,27 @@ public interface MesWmMaterialStockService { } /** - * 增加库存 - * - * 按 itemId + warehouseId + locationId + areaId + batchId 查找已有库存, - * 存在则增加 quantity;不存在则新建记录。 + * 获取或创建库存记录(按组合键唯一) * * @param itemId 物料编号 * @param warehouseId 仓库编号 * @param locationId 库区编号 * @param areaId 库位编号 * @param batchId 批次编号 - * @param quantity 增加数量 * @param vendorId 供应商编号 + * @param receiptTime 入库时间(为空则默认当前时间) + * @return 库存记录编号 */ - void increaseStock(Long itemId, Long warehouseId, Long locationId, Long areaId, - Long batchId, BigDecimal quantity, Long vendorId); + Long getOrCreateMaterialStock(Long itemId, Long warehouseId, Long locationId, Long areaId, + Long batchId, Long vendorId, LocalDateTime receiptTime); /** - * 扣减库存 - * - * 根据库存记录 ID 直接扣减数量。 + * 更新库存数量 * - * @param materialStockId 库存记录编号 - * @param quantity 扣减数量(正数) - * @param storageCheckFlag 是否校验库存充足 + * @param materialStockId 库存记录编号 + * @param quantity 变动数量(正数=增加,负数=扣减) + * @param checkFlag 是否校验库存充足(为 true 且扣减后为负则报错) */ - void decreaseStock(Long materialStockId, BigDecimal quantity, boolean storageCheckFlag); + void updateMaterialStockQuantity(Long materialStockId, BigDecimal quantity, boolean checkFlag); } diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java index 3efd747..48a97dd 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java @@ -46,7 +46,6 @@ public class MesWmMaterialStockServiceImpl implements MesWmMaterialStockService return materialStockMapper.selectById(id); } - @Override public MesWmMaterialStockDO validateMaterialStockExists(Long id) { MesWmMaterialStockDO stock = materialStockMapper.selectById(id); if (stock == null) { @@ -79,11 +78,10 @@ public class MesWmMaterialStockServiceImpl implements MesWmMaterialStockService public void updateMaterialStockFrozen(MesWmMaterialStockFreezeReqVO updateReqVO) { // 校验存在 validateMaterialStockExists(updateReqVO.getId()); + // 更新冻结状态 - MesWmMaterialStockDO updateObj = new MesWmMaterialStockDO(); - updateObj.setId(updateReqVO.getId()); - updateObj.setFrozenFlag(updateReqVO.getFrozenFlag()); - materialStockMapper.updateById(updateObj); + materialStockMapper.updateById(new MesWmMaterialStockDO() + .setId(updateReqVO.getId()).setFrozenFlag(updateReqVO.getFrozenFlag())); } @Override @@ -115,37 +113,34 @@ public class MesWmMaterialStockServiceImpl implements MesWmMaterialStockService } @Override - public void increaseStock(Long itemId, Long warehouseId, Long locationId, Long areaId, - Long batchId, BigDecimal quantity, Long vendorId) { + public Long getOrCreateMaterialStock(Long itemId, Long warehouseId, Long locationId, Long areaId, + Long batchId, Long vendorId, LocalDateTime receiptTime) { // 1. 查找已有库存记录 MesWmMaterialStockDO stock = materialStockMapper.selectByCompositeKey( itemId, warehouseId, locationId, areaId, batchId); - - // 2a. 存在则增加数量 if (stock != null) { - materialStockMapper.updateQuantity(stock.getId(), quantity); - return; + return stock.getId(); } - // 2b. 不存在则新建 + // 2. 不存在则新建 MesMdItemDO item = itemService.validateItemExists(itemId); MesWmMaterialStockDO newStock = MesWmMaterialStockDO.builder() .itemId(itemId).itemTypeId(item.getItemTypeId()) .warehouseId(warehouseId).locationId(locationId).areaId(areaId) - .batchId(batchId).vendorId(vendorId).quantity(quantity) - .receiptTime(LocalDateTime.now()).frozenFlag(false) + .batchId(batchId).vendorId(vendorId) + .quantity(BigDecimal.ZERO) // 初始数量为 0,由 updateMaterialStockQuantity 更新 + .receiptTime(receiptTime != null ? receiptTime : LocalDateTime.now()) + .frozenFlag(false) .build(); materialStockMapper.insert(newStock); + return newStock.getId(); } @Override - public void decreaseStock(Long materialStockId, BigDecimal quantity, boolean storageCheckFlag) { - // 1. 校验库存记录存在 - validateMaterialStockExists(materialStockId); - - // 2. 扣减库存(Mapper 层 CAS 防负库存) - int updatedRows = materialStockMapper.updateQuantity(materialStockId, quantity.negate()); - if (storageCheckFlag && updatedRows == 0) { + public void updateMaterialStockQuantity(Long materialStockId, BigDecimal quantity, boolean checkFlag) { + // 更新库存数量(Mapper 层 CAS 防负库存;checkFlag=false 时允许负数) + int updatedRows = materialStockMapper.updateQuantity(materialStockId, quantity, checkFlag); + if (updatedRows == 0) { throw exception(WM_MATERIAL_STOCK_INSUFFICIENT); } } diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscissue/MesWmMiscIssueServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscissue/MesWmMiscIssueServiceImpl.java index a2592a1..da0feef 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscissue/MesWmMiscIssueServiceImpl.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscissue/MesWmMiscIssueServiceImpl.java @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.mes.service.wm.miscissue; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; 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.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.module.mes.controller.admin.wm.miscissue.vo.MesWmMiscIssuePageReqVO; @@ -12,17 +11,20 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.wm.miscissue.MesWmMiscIssueDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.miscissue.MesWmMiscIssueLineDO; import cn.iocoder.yudao.module.mes.dal.mysql.wm.miscissue.MesWmMiscIssueMapper; import cn.iocoder.yudao.module.mes.enums.wm.MesWmMiscIssueStatusEnum; -import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService; +import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; +import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum; +import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService; +import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; -import java.math.BigDecimal; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; /** @@ -38,6 +40,8 @@ public class MesWmMiscIssueServiceImpl implements MesWmMiscIssueService { @Resource private MesWmMiscIssueLineService miscIssueLineService; + @Resource + private MesWmTransactionService wmTransactionService; @Override public Long createMiscIssue(MesWmMiscIssueSaveReqVO createReqVO) { @@ -110,21 +114,23 @@ public class MesWmMiscIssueServiceImpl implements MesWmMiscIssueService { throw exception(WM_MISC_ISSUE_STATUS_INVALID); } - // 遍历所有行,扣减库存 - // TODO @AI:需要实现 materialStockService.decreaseStock 方法来扣减库存 - List lines = miscIssueLineService.getMiscIssueLineListByIssueId(id); - for (MesWmMiscIssueLineDO line : lines) { - // materialStockService.decreaseStock( - // line.getItemId(), line.getWarehouseId(), line.getLocationId(), line.getAreaId(), - // line.getBatchId(), line.getQuantity()); - log.warn("[finishMiscIssue][杂项出库单({}) 行({}) 需要扣减库存,但 decreaseStock 方法尚未实现]", id, line.getId()); - } + // 2. 遍历所有行,创建库存事务(扣减库存 + 记录流水) + createTransactionList(issue); - // 更新出库单状态(待执行出库 → 已完成) + // 3. 更新出库单状态(待执行出库 → 已完成) miscIssueMapper.updateById(new MesWmMiscIssueDO() .setId(id).setStatus(MesWmMiscIssueStatusEnum.FINISHED.getStatus())); } + private void createTransactionList(MesWmMiscIssueDO issue) { + List lines = miscIssueLineService.getMiscIssueLineListByIssueId(issue.getId()); + wmTransactionService.createTransactionList(convertList(lines, line -> new MesWmTransactionSaveReqDTO() + .setType(MesWmTransactionTypeEnum.OUT.getType()).setItemId(line.getItemId()) + .setQuantity(line.getQuantity().negate()) // 出库数量为负数 + .setWarehouseId(line.getWarehouseId()).setLocationId(line.getLocationId()).setAreaId(line.getAreaId()) + .setBizType(MesBizTypeConstants.WM_MISC_ISSUE).setBizId(issue.getId()).setBizCode(issue.getCode()).setBizLineId(line.getId()))); + } + @Override @Transactional(rollbackFor = Exception.class) public void cancelMiscIssue(Long id) { diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscreceipt/MesWmMiscReceiptServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscreceipt/MesWmMiscReceiptServiceImpl.java index f52b1a5..71b89d4 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscreceipt/MesWmMiscReceiptServiceImpl.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscreceipt/MesWmMiscReceiptServiceImpl.java @@ -11,7 +11,10 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.wm.miscreceipt.MesWmMiscReceip import cn.iocoder.yudao.module.mes.dal.mysql.wm.miscreceipt.MesWmMiscReceiptLineMapper; import cn.iocoder.yudao.module.mes.dal.mysql.wm.miscreceipt.MesWmMiscReceiptMapper; import cn.iocoder.yudao.module.mes.enums.wm.MesWmMiscReceiptStatusEnum; -import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService; +import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; +import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum; +import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService; +import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -21,6 +24,7 @@ import org.springframework.validation.annotation.Validated; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; /** @@ -33,15 +37,13 @@ public class MesWmMiscReceiptServiceImpl implements MesWmMiscReceiptService { @Resource private MesWmMiscReceiptMapper miscReceiptMapper; - @Resource private MesWmMiscReceiptLineMapper miscReceiptLineMapper; @Resource private MesWmMiscReceiptDetailService miscReceiptDetailService; - @Resource - private MesWmMaterialStockService materialStockService; + private MesWmTransactionService wmTransactionService; @Override public Long createMiscReceipt(MesWmMiscReceiptSaveReqVO createReqVO) { @@ -116,27 +118,23 @@ public class MesWmMiscReceiptServiceImpl implements MesWmMiscReceiptService { throw exception(WM_MISC_RECEIPT_STATUS_NOT_APPROVED); } - // 遍历所有行,更新库存台账 - List lines = miscReceiptLineMapper.selectListByReceiptId(id); - for (MesWmMiscReceiptLineDO line : lines) { - materialStockService.increaseStock( - line.getItemId(), - line.getWarehouseId(), - line.getLocationId(), - line.getAreaId(), - null, // batchId 传 null(杂项入库使用 batchCode) - line.getQuantity(), - null, // vendorId 传 null(杂项入库无供应商) - line.getProductionDate(), - line.getExpireDate() - ); - } + // 2. 遍历所有行,创建库存事务(增加库存 + 记录流水) + createTransactionList(receipt); - // 执行入库(已审批 → 已完成) + // 3. 执行入库(已审批 → 已完成) miscReceiptMapper.updateById(new MesWmMiscReceiptDO() .setId(id).setStatus(MesWmMiscReceiptStatusEnum.FINISHED.getStatus())); } + private void createTransactionList(MesWmMiscReceiptDO receipt) { + List lines = miscReceiptLineMapper.selectListByReceiptId(receipt.getId()); + wmTransactionService.createTransactionList(convertList(lines, line -> new MesWmTransactionSaveReqDTO() + .setType(MesWmTransactionTypeEnum.IN.getType()).setItemId(line.getItemId()) + .setQuantity(line.getQuantity()) // 入库数量为正数 + .setWarehouseId(line.getWarehouseId()).setLocationId(line.getLocationId()).setAreaId(line.getAreaId()) + .setBizType(MesBizTypeConstants.WM_MISC_RECPT).setBizId(receipt.getId()).setBizCode(receipt.getCode()).setBizLineId(line.getId()))); + } + @Override @Transactional(rollbackFor = Exception.class) public void cancelMiscReceipt(Long id) { diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskLineServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskLineServiceImpl.java index 2a8981a..9565708 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskLineServiceImpl.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskLineServiceImpl.java @@ -69,7 +69,7 @@ public class MesWmStockTakingTaskLineServiceImpl implements MesWmStockTakingTask // 3. 批量生成明细行 List lines = convertList(stocks, stock -> MesWmStockTakingTaskLineDO.builder() .taskId(task.getId()).materialStockId(stock.getId()).itemId(stock.getItemId()) - .batchId(stock.getBatchId()).quantity(stock.getQuantityOnhand()).takingQuantity(BigDecimal.ZERO) + .batchId(stock.getBatchId()).quantity(stock.getQuantity()).takingQuantity(BigDecimal.ZERO) .warehouseId(stock.getWarehouseId()).locationId(stock.getLocationId()).areaId(stock.getAreaId()) .status(MesWmStockTakingTaskLineStatusEnum.LOSS.getStatus()).build()); stockTakingTaskLineMapper.insertBatch(lines); diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskServiceImpl.java index 3bf9e9b..8ba847b 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskServiceImpl.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskServiceImpl.java @@ -211,7 +211,7 @@ public class MesWmStockTakingTaskServiceImpl implements MesWmStockTakingTaskServ private void updateMaterialStockFrozen(Long materialStockId, boolean frozen) { MesWmMaterialStockFreezeReqVO reqVO = new MesWmMaterialStockFreezeReqVO(); reqVO.setId(materialStockId); - reqVO.setFrozen(frozen); + reqVO.setFrozenFlag(frozen); materialStockService.updateMaterialStockFrozen(reqVO); } diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionService.java new file mode 100644 index 0000000..6f78dc6 --- /dev/null +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionService.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.mes.service.wm.transaction; + +import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO; +import jakarta.validation.Valid; + +import java.util.List; + +/** + * MES 库存事务流水 Service 接口 + * + * 对齐 KTG WmTransactionServiceImpl.processTransaction 实现: + * 校验 → 库存台账更新 → 事务流水插入。 + */ +public interface MesWmTransactionService { + + /** + * 创建库存事务(含校验 + 库存更新 + 插入流水) + * + * @param reqDTO 事务数据 + * @return 事务流水编号 + */ + Long createTransaction(@Valid MesWmTransactionSaveReqDTO reqDTO); + + /** + * 批量创建库存事务 + * + * @param reqDTOs 事务数据列表 + */ + void createTransactionList(List reqDTOs); + +} diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java new file mode 100644 index 0000000..9bb142f --- /dev/null +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java @@ -0,0 +1,107 @@ +package cn.iocoder.yudao.module.mes.service.wm.transaction; + +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.wm.batch.MesWmBatchDO; +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transaction.MesWmTransactionDO; +import cn.iocoder.yudao.module.mes.dal.mysql.wm.transaction.MesWmTransactionMapper; +import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum; +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 jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * MES 库存事务流水 Service 实现类 + */ +@Service +@Validated +public class MesWmTransactionServiceImpl implements MesWmTransactionService { + + @Resource + private MesWmTransactionMapper transactionMapper; + + @Resource + private MesWmMaterialStockService materialStockService; + @Resource + private MesWmBatchService batchService; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createTransaction(MesWmTransactionSaveReqDTO reqDTO) { + // 1.1 校验事务类型 + MesWmTransactionTypeEnum typeEnum = MesWmTransactionTypeEnum.valueOf(reqDTO.getType()); + Assert.notNull(typeEnum, "事务类型({}) 不存在", reqDTO.getType()); + // 1.2 校验 quantity 正负号与事务方向一致(入库必须正数,出库必须负数) + boolean inbound = typeEnum.isInbound(); + if (inbound) { + Assert.isTrue(reqDTO.getQuantity().compareTo(BigDecimal.ZERO) > 0, + "入库事务数量必须为正数,实际值: {}", reqDTO.getQuantity()); + } else { + Assert.isTrue(reqDTO.getQuantity().compareTo(BigDecimal.ZERO) < 0, + "出库事务数量必须为负数,实际值: {}", reqDTO.getQuantity()); + } + // 1.3 batchId / batchCode 互补 + completeBatchInfo(reqDTO); + + // 2.1 获取或创建库存记录 + Long materialStockId = materialStockService.getOrCreateMaterialStock( + reqDTO.getItemId(), reqDTO.getWarehouseId(), reqDTO.getLocationId(), reqDTO.getAreaId(), + reqDTO.getBatchId(), reqDTO.getVendorId(), reqDTO.getReceiptTime()); + // 2.2 更新库存数量 + boolean checkFlag = ObjUtil.defaultIfNull(reqDTO.getCheckFlag(), true); + materialStockService.updateMaterialStockQuantity(materialStockId, reqDTO.getQuantity(), checkFlag); + + // 3. 插入事务流水 + MesWmTransactionDO transaction = MesWmTransactionDO.builder() + .type(reqDTO.getType()).quantity(reqDTO.getQuantity()).transactionTime(LocalDateTime.now()) + .itemId(reqDTO.getItemId()).batchId(reqDTO.getBatchId()).batchCode(reqDTO.getBatchCode()) + .warehouseId(reqDTO.getWarehouseId()).locationId(reqDTO.getLocationId()).areaId(reqDTO.getAreaId()) + .bizType(reqDTO.getBizType()).bizId(reqDTO.getBizId()).bizCode(reqDTO.getBizCode()).bizLineId(reqDTO.getBizLineId()) + .materialStockId(materialStockId).relatedTransactionId(reqDTO.getRelatedTransactionId()) + .build(); + transactionMapper.insert(transaction); + return transaction.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void createTransactionList(List reqDTOs) { + reqDTOs.forEach(this::createTransaction); + } + + /** + * batchId / batchCode 互补:有 batchId 无 batchCode 时,查出 batchCode;反之亦然。 + * + * @param reqDTO 事务请求 DTO + */ + private void completeBatchInfo(MesWmTransactionSaveReqDTO reqDTO) { + if (reqDTO.getBatchId() != null && StrUtil.isNotEmpty(reqDTO.getBatchCode())) { + return; + } + // 情况一:有 batchId,补 batchCode + if (reqDTO.getBatchId() != null) { + MesWmBatchDO batch = batchService.getBatch(reqDTO.getBatchId()); + if (batch != null) { + reqDTO.setBatchCode(batch.getCode()); + } + return; + } + // 情况二:有 batchCode,补 batchId + if (StrUtil.isNotEmpty(reqDTO.getBatchCode())) { + MesWmBatchDO batch = batchService.getBatchByCode(reqDTO.getBatchCode()); + if (batch != null) { + reqDTO.setBatchId(batch.getId()); + } + } + } + +} diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/dto/MesWmTransactionSaveReqDTO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/dto/MesWmTransactionSaveReqDTO.java new file mode 100644 index 0000000..ef2e68f --- /dev/null +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/dto/MesWmTransactionSaveReqDTO.java @@ -0,0 +1,117 @@ +package cn.iocoder.yudao.module.mes.service.wm.transaction.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * MES 库存事务 创建 Request DTO + */ +@Data +@Accessors(chain = true) +public class MesWmTransactionSaveReqDTO { + + // ===== 事务核心 ===== + + /** + * 事务类型 + * + * 枚举 {@link cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum} + */ + @NotNull(message = "事务类型不能为空") + private Integer type; + + // ===== 业务来源 ===== + + /** + * 业务类型 + * + * 对应 {@link cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants} + */ + @NotNull(message = "业务类型不能为空") + private Integer bizType; + /** + * 业务主单 ID + */ + @NotNull(message = "业务主单 ID 不能为空") + private Long bizId; + /** + * 业务单号 + */ + @NotNull(message = "业务单号不能为空") + private String bizCode; + /** + * 业务行 ID + */ + @NotNull(message = "业务行 ID 不能为空") + private Long bizLineId; + + // ===== 物料维度 ===== + + /** + * 物料 ID + */ + @NotNull(message = "物料 ID 不能为空") + private Long itemId; + /** + * 变动数量(入库为正数,出库为负数) + */ + @NotNull(message = "变动数量不能为空") + private BigDecimal quantity; + /** + * 批次 ID + */ + private Long batchId; + /** + * 批次号 + */ + private String batchCode; + + // ===== 库存位置 ===== + + /** + * 仓库 ID + */ + @NotNull(message = "仓库 ID 不能为空") + private Long warehouseId; + /** + * 库区 ID + */ + @NotNull(message = "库区 ID 不能为空") + private Long locationId; + /** + * 库位 ID + */ + private Long areaId; + + // ===== 台账补充字段(入库时用于初始化 MesWmMaterialStockDO) ===== + + /** + * 供应商 ID(采购入库场景) + */ + private Long vendorId; + /** + * 入库时间(为空默认当前时间) + * + * 对应 KTG 的 recptDate + */ + private LocalDateTime receiptTime; + + // ===== 关联 ===== + + /** + * 关联事务 ID(成对事务中,入库事务关联出库事务) + */ + private Long relatedTransactionId; + + // ===== 可选控制(非存储字段) ===== + + /** + * 是否校验库存充足(默认 true;线边库消耗等场景设 false 允许负库存) + */ + private Boolean checkFlag; + +} diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferServiceImpl.java index 53c62c2..045fb21 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferServiceImpl.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferServiceImpl.java @@ -274,7 +274,7 @@ public class MesWmTransferServiceImpl implements MesWmTransferService { } MesWmMaterialStockFreezeReqVO freezeReqVO = new MesWmMaterialStockFreezeReqVO(); freezeReqVO.setId(line.getMaterialStockId()); - freezeReqVO.setFrozen(frozen); + freezeReqVO.setFrozenFlag(frozen); materialStockService.updateMaterialStockFrozen(freezeReqVO); } }