Commit b1e4a6df6620aab1f023b50a29566e59621f5015

Authored by YunaiV
1 parent f709822c

✨ feat(mes): 更新库存管理逻辑,重命名字段并添加事务记录

重构库存管理相关的请求和响应对象,更新字段名称以提高语义清晰度。新增库存事务流水记录,支持库存增减事件的追踪。此变更旨在提升库存管理的可维护性和可读性。
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/MesWmMaterialStockController.java
@@ -14,7 +14,6 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMat @@ -14,7 +14,6 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMat
14 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; 14 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
15 import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO; 15 import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
16 import cn.iocoder.yudao.module.mes.dal.dataobject.md.vendor.MesMdVendorDO; 16 import cn.iocoder.yudao.module.mes.dal.dataobject.md.vendor.MesMdVendorDO;
17 -import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;  
18 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO; 17 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
19 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO; 18 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
20 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO; 19 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
@@ -22,7 +21,6 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLoc @@ -22,7 +21,6 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLoc
22 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; 21 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
23 import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService; 22 import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
24 import cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService; 23 import cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService;
25 -import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;  
26 import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService; 24 import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
27 import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService; 25 import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
28 import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService; 26 import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService;
@@ -73,9 +71,6 @@ public class MesWmMaterialStockController { @@ -73,9 +71,6 @@ public class MesWmMaterialStockController {
73 @Resource 71 @Resource
74 private MesMdVendorService vendorService; 72 private MesMdVendorService vendorService;
75 73
76 - @Resource  
77 - private MesProWorkOrderService workOrderService;  
78 -  
79 @GetMapping("/get") 74 @GetMapping("/get")
80 @Operation(summary = "获得库存记录") 75 @Operation(summary = "获得库存记录")
81 @Parameter(name = "id", description = "编号", required = true, example = "1024") 76 @Parameter(name = "id", description = "编号", required = true, example = "1024")
@@ -128,7 +123,7 @@ public class MesWmMaterialStockController { @@ -128,7 +123,7 @@ public class MesWmMaterialStockController {
128 Map<Long, MesMdItemDO> itemMap = itemService.getItemMap( 123 Map<Long, MesMdItemDO> itemMap = itemService.getItemMap(
129 convertSet(list, MesWmMaterialStockDO::getItemId)); 124 convertSet(list, MesWmMaterialStockDO::getItemId));
130 Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap( 125 Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap(
131 - convertSet(list, MesWmMaterialStockDO::getUnitMeasureId)); 126 + convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId));
132 Map<Long, MesWmWarehouseDO> warehouseMap = warehouseService.getWarehouseMap( 127 Map<Long, MesWmWarehouseDO> warehouseMap = warehouseService.getWarehouseMap(
133 convertSet(list, MesWmMaterialStockDO::getWarehouseId)); 128 convertSet(list, MesWmMaterialStockDO::getWarehouseId));
134 Map<Long, MesWmWarehouseLocationDO> locationMap = locationService.getWarehouseLocationMap( 129 Map<Long, MesWmWarehouseLocationDO> locationMap = locationService.getWarehouseLocationMap(
@@ -137,16 +132,13 @@ public class MesWmMaterialStockController { @@ -137,16 +132,13 @@ public class MesWmMaterialStockController {
137 convertSet(list, MesWmMaterialStockDO::getAreaId)); 132 convertSet(list, MesWmMaterialStockDO::getAreaId));
138 Map<Long, MesMdVendorDO> vendorMap = vendorService.getVendorMap( 133 Map<Long, MesMdVendorDO> vendorMap = vendorService.getVendorMap(
139 convertSet(list, MesWmMaterialStockDO::getVendorId)); 134 convertSet(list, MesWmMaterialStockDO::getVendorId));
140 - Map<Long, MesProWorkOrderDO> workOrderMap = workOrderService.getWorkOrderMap(  
141 - convertSet(list, MesWmMaterialStockDO::getWorkOrderId));  
142 - // TODO DONE @芋艿:待 mes_wm_batch 模块迁移后,补充 batchCode 的拼接  
143 -  
144 // 2. 构建结果 135 // 2. 构建结果
145 return BeanUtils.toBean(list, MesWmMaterialStockRespVO.class, vo -> { 136 return BeanUtils.toBean(list, MesWmMaterialStockRespVO.class, vo -> {
146 - MapUtils.findAndThen(itemMap, vo.getItemId(), item ->  
147 - vo.setItemCode(item.getCode()).setItemName(item.getName()).setSpecification(item.getSpecification()));  
148 - MapUtils.findAndThen(unitMeasureMap, vo.getUnitMeasureId(),  
149 - unitMeasure -> vo.setUnitMeasureName(unitMeasure.getName())); 137 + MapUtils.findAndThen(itemMap, vo.getItemId(), item -> {
  138 + vo.setItemCode(item.getCode()).setItemName(item.getName()).setSpecification(item.getSpecification());
  139 + MapUtils.findAndThen(unitMeasureMap, item.getUnitMeasureId(),
  140 + unitMeasure -> vo.setUnitMeasureName(unitMeasure.getName()));
  141 + });
150 MapUtils.findAndThen(warehouseMap, vo.getWarehouseId(), 142 MapUtils.findAndThen(warehouseMap, vo.getWarehouseId(),
151 warehouse -> vo.setWarehouseName(warehouse.getName())); 143 warehouse -> vo.setWarehouseName(warehouse.getName()));
152 MapUtils.findAndThen(locationMap, vo.getLocationId(), 144 MapUtils.findAndThen(locationMap, vo.getLocationId(),
@@ -155,8 +147,6 @@ public class MesWmMaterialStockController { @@ -155,8 +147,6 @@ public class MesWmMaterialStockController {
155 area -> vo.setAreaName(area.getName())); 147 area -> vo.setAreaName(area.getName()));
156 MapUtils.findAndThen(vendorMap, vo.getVendorId(), 148 MapUtils.findAndThen(vendorMap, vo.getVendorId(),
157 vendor -> vo.setVendorName(vendor.getName())); 149 vendor -> vo.setVendorName(vendor.getName()));
158 - MapUtils.findAndThen(workOrderMap, vo.getWorkOrderId(),  
159 - workOrder -> vo.setWorkOrderCode(workOrder.getCode()));  
160 }); 150 });
161 } 151 }
162 152
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockFreezeReqVO.java
@@ -4,7 +4,6 @@ import io.swagger.v3.oas.annotations.media.Schema; @@ -4,7 +4,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
4 import jakarta.validation.constraints.NotNull; 4 import jakarta.validation.constraints.NotNull;
5 import lombok.Data; 5 import lombok.Data;
6 6
7 -// TODO DONE @AI:已重命名为 FreezeReqVO,语义与冻结/解冻操作一致  
8 @Schema(description = "管理后台 - MES 库存台账冻结/解冻 Request VO") 7 @Schema(description = "管理后台 - MES 库存台账冻结/解冻 Request VO")
9 @Data 8 @Data
10 public class MesWmMaterialStockFreezeReqVO { 9 public class MesWmMaterialStockFreezeReqVO {
@@ -15,6 +14,7 @@ public class MesWmMaterialStockFreezeReqVO { @@ -15,6 +14,7 @@ public class MesWmMaterialStockFreezeReqVO {
15 14
16 @Schema(description = "是否冻结", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") 15 @Schema(description = "是否冻结", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
17 @NotNull(message = "冻结状态不能为空") 16 @NotNull(message = "冻结状态不能为空")
18 - private Boolean frozen; 17 + private Boolean frozenFlag;
19 18
20 } 19 }
  20 +
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockRespVO.java
@@ -35,9 +35,6 @@ public class MesWmMaterialStockRespVO { @@ -35,9 +35,6 @@ public class MesWmMaterialStockRespVO {
35 @ExcelProperty("规格型号") 35 @ExcelProperty("规格型号")
36 private String specification; 36 private String specification;
37 37
38 - @Schema(description = "计量单位编号", example = "1")  
39 - private Long unitMeasureId;  
40 -  
41 @Schema(description = "计量单位名称", example = "千克") 38 @Schema(description = "计量单位名称", example = "千克")
42 @ExcelProperty("计量单位") 39 @ExcelProperty("计量单位")
43 private String unitMeasureName; 40 private String unitMeasureName;
@@ -77,32 +74,17 @@ public class MesWmMaterialStockRespVO { @@ -77,32 +74,17 @@ public class MesWmMaterialStockRespVO {
77 @ExcelProperty("供应商") 74 @ExcelProperty("供应商")
78 private String vendorName; 75 private String vendorName;
79 76
80 - @Schema(description = "生产工单编号", example = "1")  
81 - private Long workOrderId;  
82 -  
83 - @Schema(description = "生产工单编码", example = "WO20260101")  
84 - @ExcelProperty("生产工单")  
85 - private String workOrderCode;  
86 -  
87 @Schema(description = "在库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.0000") 77 @Schema(description = "在库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.0000")
88 @ExcelProperty("在库数量") 78 @ExcelProperty("在库数量")
89 - private BigDecimal quantityOnhand; 79 + private BigDecimal quantity;
90 80
91 @Schema(description = "入库时间") 81 @Schema(description = "入库时间")
92 @ExcelProperty("入库时间") 82 @ExcelProperty("入库时间")
93 - private LocalDateTime recptDate;  
94 -  
95 - @Schema(description = "生产日期")  
96 - @ExcelProperty("生产日期")  
97 - private LocalDateTime productionDate;  
98 -  
99 - @Schema(description = "库存有效期")  
100 - @ExcelProperty("有效期")  
101 - private LocalDateTime expireDate; 83 + private LocalDateTime receiptTime;
102 84
103 @Schema(description = "是否冻结", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") 85 @Schema(description = "是否冻结", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
104 @ExcelProperty("是否冻结") 86 @ExcelProperty("是否冻结")
105 - private Boolean frozen; 87 + private Boolean frozenFlag;
106 88
107 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) 89 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
108 @ExcelProperty("创建时间") 90 @ExcelProperty("创建时间")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/materialstock/MesWmMaterialStockDO.java
@@ -3,9 +3,8 @@ package cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock; @@ -3,9 +3,8 @@ package cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock;
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; 4 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
5 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO; 5 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO;
6 -import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;  
7 import cn.iocoder.yudao.module.mes.dal.dataobject.md.vendor.MesMdVendorDO; 6 import cn.iocoder.yudao.module.mes.dal.dataobject.md.vendor.MesMdVendorDO;
8 -import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; 7 +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
9 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO; 8 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
10 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO; 9 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
11 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO; 10 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO;
@@ -17,7 +16,6 @@ import lombok.*; @@ -17,7 +16,6 @@ import lombok.*;
17 import java.math.BigDecimal; 16 import java.math.BigDecimal;
18 import java.time.LocalDateTime; 17 import java.time.LocalDateTime;
19 18
20 -// TODO DONE @芋艿:当前包名 materialstock 与表名 mes_wm_material_stock 保持一致,语义清晰,暂不调整  
21 /** 19 /**
22 * MES 库存台账(仓库现有量)DO 20 * MES 库存台账(仓库现有量)DO
23 */ 21 */
@@ -36,6 +34,25 @@ public class MesWmMaterialStockDO extends BaseDO { @@ -36,6 +34,25 @@ public class MesWmMaterialStockDO extends BaseDO {
36 */ 34 */
37 @TableId 35 @TableId
38 private Long id; 36 private Long id;
  37 +
  38 + /**
  39 + * 在库数量
  40 + */
  41 + private BigDecimal quantity;
  42 + /**
  43 + * 入库时间
  44 + */
  45 + private LocalDateTime receiptTime;
  46 +
  47 + /**
  48 + * 供应商编号
  49 + *
  50 + * 关联 {@link MesMdVendorDO#getId()}
  51 + */
  52 + private Long vendorId;
  53 +
  54 + // ==================== 物料维度 ====================
  55 +
39 /** 56 /**
40 * 物料分类编号 57 * 物料分类编号
41 * 58 *
@@ -49,17 +66,20 @@ public class MesWmMaterialStockDO extends BaseDO { @@ -49,17 +66,20 @@ public class MesWmMaterialStockDO extends BaseDO {
49 */ 66 */
50 private Long itemId; 67 private Long itemId;
51 /** 68 /**
52 - * 计量单位编号 69 + * 批次编号
53 * 70 *
54 - * 关联 {@link MesMdUnitMeasureDO#getId()} 71 + * 关联 {@link MesWmBatchDO#getId()}
55 */ 72 */
56 - private Long unitMeasureId; 73 + private Long batchId;
57 /** 74 /**
58 - * 批次编号 75 + * 批次号
59 * 76 *
60 - * TODO DONE @芋艿:保留。待 mes_wm_batch 模块迁移后补充 @link 关联 77 + * 关联 {@link MesWmBatchDO#getCode()}
61 */ 78 */
62 - private Long batchId; 79 + private String batchCode;
  80 +
  81 + // ==================== 库存位置 ====================
  82 +
63 /** 83 /**
64 * 仓库编号 84 * 仓库编号
65 * 85 *
@@ -78,39 +98,12 @@ public class MesWmMaterialStockDO extends BaseDO { @@ -78,39 +98,12 @@ public class MesWmMaterialStockDO extends BaseDO {
78 * 关联 {@link MesWmWarehouseAreaDO#getId()} 98 * 关联 {@link MesWmWarehouseAreaDO#getId()}
79 */ 99 */
80 private Long areaId; 100 private Long areaId;
81 - /**  
82 - * 供应商编号  
83 - *  
84 - * 关联 {@link MesMdVendorDO#getId()}  
85 - */  
86 - private Long vendorId;  
87 - // TODO @AI:这个字段,应该没有的。  
88 - /**  
89 - * 生产工单编号  
90 - *  
91 - * 关联 {@link MesProWorkOrderDO#getId()}  
92 - */  
93 - private Long workOrderId;  
94 - // TODO @AI:onhand_quantity 更合理;更符合现在项目的风格。改下;  
95 - /**  
96 - * 在库数量  
97 - */  
98 - private BigDecimal quantityOnhand;  
99 - /**  
100 - * 入库时间  
101 - */  
102 - private LocalDateTime recptDate;  
103 - /**  
104 - * 库存有效期  
105 - */  
106 - private LocalDateTime expireDate;  
107 - /**  
108 - * 生产日期  
109 - */  
110 - private LocalDateTime productionDate; 101 +
  102 + // ==================== 状态 ====================
  103 +
111 /** 104 /**
112 * 是否冻结 105 * 是否冻结
113 */ 106 */
114 - private Boolean frozen; 107 + private Boolean frozenFlag;
115 108
116 } 109 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/transaction/MesWmTransactionDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.dal.dataobject.wm.transaction;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
  5 +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
  6 +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
  7 +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
  8 +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
  9 +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO;
  10 +import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
  11 +import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
  12 +import com.baomidou.mybatisplus.annotation.KeySequence;
  13 +import com.baomidou.mybatisplus.annotation.TableId;
  14 +import com.baomidou.mybatisplus.annotation.TableName;
  15 +import lombok.*;
  16 +
  17 +import java.math.BigDecimal;
  18 +import java.time.LocalDateTime;
  19 +
  20 +/**
  21 + * MES 库存事务流水 DO
  22 + *
  23 + * 记录每一笔库存增减事件,系统自动生成,只读查询,不允许人工维护。
  24 + */
  25 +@TableName("mes_wm_transaction")
  26 +@KeySequence("mes_wm_transaction_seq")
  27 +@Data
  28 +@EqualsAndHashCode(callSuper = true)
  29 +@ToString(callSuper = true)
  30 +@Builder
  31 +@NoArgsConstructor
  32 +@AllArgsConstructor
  33 +public class MesWmTransactionDO extends BaseDO {
  34 +
  35 + /**
  36 + * 编号
  37 + */
  38 + @TableId
  39 + private Long id;
  40 +
  41 + // ==================== 业务归属 ====================
  42 +
  43 + // codex reply:`transactionType` 收敛成 `type` 是对的,和项目里其他 DO 的命名保持一致。
  44 + // codex reply:不过这类改名要连同 `MesWmTransactionSaveReqVO`、Service 和查询条件一起同步,不然 DO 与上下游模型会割裂。
  45 + /**
  46 + * 事务类型
  47 + *
  48 + * 枚举 {@link MesWmTransactionTypeEnum}
  49 + */
  50 + private Integer type;
  51 + // codex reply:`transactionQuantity` 收敛成 `quantity` 是对的;在事务流水这个上下文里,`transaction` 前缀没有额外信息。
  52 + // codex reply:`transactionFlag` 删除也合理,直接存带符号数量更符合当前写入逻辑,后续聚合查询也可以直接 `SUM(quantity)`。
  53 + /**
  54 + * 本次变动数量
  55 + *
  56 + * 正数=入库,负数=出库
  57 + */
  58 + private BigDecimal quantity;
  59 +
  60 + /**
  61 + * 业务类型
  62 + *
  63 + * 对应 {@link MesBizTypeConstants} 中的常量
  64 + */
  65 + private Integer bizType;
  66 + /**
  67 + * 来源业务主单 ID
  68 + */
  69 + private Long bizId;
  70 + /**
  71 + * 来源业务单号
  72 + */
  73 + private String bizCode;
  74 + /**
  75 + * 来源业务行 ID
  76 + */
  77 + private Long bizLineId;
  78 +
  79 + /**
  80 + * 库存记录 ID
  81 + *
  82 + * 关联 {@link MesWmMaterialStockDO#getId()}
  83 + */
  84 + private Long materialStockId;
  85 + /**
  86 + * 关联的事务 ID
  87 + *
  88 + * 关联 {@link #getId()},用于调拨等成对流水关联
  89 + */
  90 + private Long relatedTransactionId;
  91 +
  92 + // ==================== 物料维度 ====================
  93 +
  94 + /**
  95 + * 物料 ID
  96 + *
  97 + * 关联 {@link MesMdItemDO#getId()}
  98 + */
  99 + private Long itemId;
  100 + /**
  101 + * 批次 ID
  102 + *
  103 + * 关联 {@link MesWmBatchDO#getId()}
  104 + */
  105 + private Long batchId;
  106 + /**
  107 + * 批次号
  108 + *
  109 + * 关联 {@link MesWmBatchDO#getCode()}
  110 + */
  111 + private String batchCode;
  112 + // codex reply:`packageId` 当前删除是合理的。事务流水链路没有包装维度的输入输出,先不保留“预留字段”更干净。
  113 +
  114 + // ==================== 库存位置 ====================
  115 +
  116 + /**
  117 + * 仓库 ID
  118 + *
  119 + * 关联 {@link MesWmWarehouseDO#getId()}
  120 + */
  121 + private Long warehouseId;
  122 + /**
  123 + * 库区 ID
  124 + *
  125 + * 关联 {@link MesWmWarehouseLocationDO#getId()}
  126 + */
  127 + private Long locationId;
  128 + /**
  129 + * 库位 ID
  130 + *
  131 + * 关联 {@link MesWmWarehouseAreaDO#getId()}
  132 + */
  133 + private Long areaId;
  134 +
  135 + // ==================== 时间 ====================
  136 +
  137 + // codex reply:这里我不建议继续沿用 `erpTime` / `receiptTime` 这组命名;项目里的同类字段基本统一为 `receiptDate`,单独改成 `Time` 会破坏一致性。
  138 + // codex reply:如果这两个字段暂时没有业务赋值,建议宁可先删;若后续保留,ERP 侧更建议 `erpAccountDate`,收货侧保持 `receiptDate`。
  139 + /**
  140 + * 事务发生时间
  141 + */
  142 + private LocalDateTime transactionTime;
  143 + /**
  144 + * ERP 账期
  145 + */
  146 + private LocalDateTime erpTime;
  147 + /**
  148 + * 入库时间
  149 + */
  150 + private LocalDateTime receiptTime;
  151 +
  152 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/itemconsume/MesWmItemConsumeLineMapper.java
@@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.itemconsume.vo.MesWmItemC @@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.itemconsume.vo.MesWmItemC
7 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeLineDO; 7 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeLineDO;
8 import org.apache.ibatis.annotations.Mapper; 8 import org.apache.ibatis.annotations.Mapper;
9 9
  10 +import java.util.List;
  11 +
10 /** 12 /**
11 * MES 物料消耗记录行 Mapper 13 * MES 物料消耗记录行 Mapper
12 * 14 *
@@ -22,4 +24,8 @@ public interface MesWmItemConsumeLineMapper extends BaseMapperX&lt;MesWmItemConsume @@ -22,4 +24,8 @@ public interface MesWmItemConsumeLineMapper extends BaseMapperX&lt;MesWmItemConsume
22 .orderByDesc(MesWmItemConsumeLineDO::getId)); 24 .orderByDesc(MesWmItemConsumeLineDO::getId));
23 } 25 }
24 26
  27 + default List<MesWmItemConsumeLineDO> selectListByConsumeId(Long consumeId) {
  28 + return selectList(MesWmItemConsumeLineDO::getConsumeId, consumeId);
  29 + }
  30 +
25 } 31 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java
@@ -27,9 +27,9 @@ public interface MesWmMaterialStockMapper extends BaseMapperX&lt;MesWmMaterialStock @@ -27,9 +27,9 @@ public interface MesWmMaterialStockMapper extends BaseMapperX&lt;MesWmMaterialStock
27 .inIfPresent(MesWmMaterialStockDO::getItemId, itemIds) 27 .inIfPresent(MesWmMaterialStockDO::getItemId, itemIds)
28 .eqIfPresent(MesWmMaterialStockDO::getWarehouseId, reqVO.getWarehouseId()) 28 .eqIfPresent(MesWmMaterialStockDO::getWarehouseId, reqVO.getWarehouseId())
29 .eqIfPresent(MesWmMaterialStockDO::getLocationId, reqVO.getLocationId()) 29 .eqIfPresent(MesWmMaterialStockDO::getLocationId, reqVO.getLocationId())
30 - .eqIfPresent(MesWmMaterialStockDO::getFrozen, reqVO.getFrozen())  
31 - .ne(MesWmMaterialStockDO::getQuantityOnhand, BigDecimal.ZERO)  
32 - .orderByAsc(MesWmMaterialStockDO::getRecptDate)); 30 + .eqIfPresent(MesWmMaterialStockDO::getFrozenFlag, reqVO.getFrozen())
  31 + .ne(MesWmMaterialStockDO::getQuantity, BigDecimal.ZERO)
  32 + .orderByAsc(MesWmMaterialStockDO::getReceiptTime));
33 } 33 }
34 34
35 default Long selectCountByWarehouseId(Long warehouseId) { 35 default Long selectCountByWarehouseId(Long warehouseId) {
@@ -48,11 +48,21 @@ public interface MesWmMaterialStockMapper extends BaseMapperX&lt;MesWmMaterialStock @@ -48,11 +48,21 @@ public interface MesWmMaterialStockMapper extends BaseMapperX&lt;MesWmMaterialStock
48 return selectByIds(ids); 48 return selectByIds(ids);
49 } 49 }
50 50
51 - // TODO @AI:参考别的模块,最好使用 update某个字段,有例子的;然后看看,有没可能,尽量不写字段,而是通过 lamba 表达式获得名字。  
52 - default void incrQuantityOnhand(Long id, BigDecimal quantity) {  
53 - update(null, new LambdaUpdateWrapper<MesWmMaterialStockDO>() 51 + /**
  52 + * 增量更新库存数量
  53 + *
  54 + * @param id 库存记录编号
  55 + * @param count 变动数量(正数=增加,负数=扣减)
  56 + * @return 影响行数(扣减时为 0 表示库存不足)
  57 + */
  58 + default int updateQuantity(Long id, BigDecimal count) {
  59 + LambdaUpdateWrapper<MesWmMaterialStockDO> updateWrapper = new LambdaUpdateWrapper<MesWmMaterialStockDO>()
54 .eq(MesWmMaterialStockDO::getId, id) 60 .eq(MesWmMaterialStockDO::getId, id)
55 - .setSql("quantity_onhand = quantity_onhand + " + quantity)); 61 + .setSql("quantity = quantity + " + count);
  62 + if (count.compareTo(BigDecimal.ZERO) < 0) {
  63 + updateWrapper.ge(MesWmMaterialStockDO::getQuantity, count.abs()); // CAS 防负库存
  64 + }
  65 + return update(null, updateWrapper);
56 } 66 }
57 67
58 default MesWmMaterialStockDO selectByCompositeKey(Long itemId, Long warehouseId, Long locationId, 68 default MesWmMaterialStockDO selectByCompositeKey(Long itemId, Long warehouseId, Long locationId,
@@ -74,8 +84,8 @@ public interface MesWmMaterialStockMapper extends BaseMapperX&lt;MesWmMaterialStock @@ -74,8 +84,8 @@ public interface MesWmMaterialStockMapper extends BaseMapperX&lt;MesWmMaterialStock
74 .eqIfPresent(MesWmMaterialStockDO::getBatchId, reqVO.getBatchId()) 84 .eqIfPresent(MesWmMaterialStockDO::getBatchId, reqVO.getBatchId())
75 .geIfPresent(MesWmMaterialStockDO::getUpdateTime, reqVO.getStartTime()) 85 .geIfPresent(MesWmMaterialStockDO::getUpdateTime, reqVO.getStartTime())
76 .leIfPresent(MesWmMaterialStockDO::getUpdateTime, reqVO.getEndTime()) 86 .leIfPresent(MesWmMaterialStockDO::getUpdateTime, reqVO.getEndTime())
77 - .ne(MesWmMaterialStockDO::getQuantityOnhand, BigDecimal.ZERO) // TODO @芋艿:需要在考虑下,要不要支持;  
78 - .orderByAsc(MesWmMaterialStockDO::getRecptDate)); 87 + .ne(MesWmMaterialStockDO::getQuantity, BigDecimal.ZERO) // TODO @芋艿:需要在考虑下,要不要支持;
  88 + .orderByAsc(MesWmMaterialStockDO::getReceiptTime));
79 } 89 }
80 90
81 } 91 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/transaction/MesWmTransactionMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.dal.mysql.wm.transaction;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  4 +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transaction.MesWmTransactionDO;
  5 +import org.apache.ibatis.annotations.Mapper;
  6 +
  7 +/**
  8 + * MES 库存事务流水 Mapper
  9 + */
  10 +@Mapper
  11 +public interface MesWmTransactionMapper extends BaseMapperX<MesWmTransactionDO> {
  12 +
  13 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -355,6 +355,7 @@ public interface ErrorCodeConstants { @@ -355,6 +355,7 @@ public interface ErrorCodeConstants {
355 // ========== MES 仓库管理-库存(1-040-703-000) ========== 355 // ========== MES 仓库管理-库存(1-040-703-000) ==========
356 ErrorCode WM_MATERIAL_STOCK_NOT_EXISTS = new ErrorCode(1_040_703_000, "库存记录不存在"); 356 ErrorCode WM_MATERIAL_STOCK_NOT_EXISTS = new ErrorCode(1_040_703_000, "库存记录不存在");
357 ErrorCode WM_MATERIAL_STOCK_INSUFFICIENT = new ErrorCode(1_040_703_001, "库存数量不足"); 357 ErrorCode WM_MATERIAL_STOCK_INSUFFICIENT = new ErrorCode(1_040_703_001, "库存数量不足");
  358 + ErrorCode WM_TRANSACTION_MATERIAL_STOCK_REQUIRED = new ErrorCode(1_040_703_002, "出库事务必须指定库存记录 ID");
358 359
359 // ========== MES 仓库管理-到货通知单(1-040-704-000) ========== 360 // ========== MES 仓库管理-到货通知单(1-040-704-000) ==========
360 ErrorCode WM_ARRIVAL_NOTICE_NOT_EXISTS = new ErrorCode(1_040_704_000, "到货通知单不存在"); 361 ErrorCode WM_ARRIVAL_NOTICE_NOT_EXISTS = new ErrorCode(1_040_704_000, "到货通知单不存在");
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java
@@ -8,7 +8,6 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterial @@ -8,7 +8,6 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterial
8 import jakarta.validation.Valid; 8 import jakarta.validation.Valid;
9 9
10 import java.math.BigDecimal; 10 import java.math.BigDecimal;
11 -import java.time.LocalDateTime;  
12 import java.util.Collection; 11 import java.util.Collection;
13 import java.util.List; 12 import java.util.List;
14 import java.util.Map; 13 import java.util.Map;
@@ -99,7 +98,7 @@ public interface MesWmMaterialStockService { @@ -99,7 +98,7 @@ public interface MesWmMaterialStockService {
99 * 增加库存 98 * 增加库存
100 * 99 *
101 * 按 itemId + warehouseId + locationId + areaId + batchId 查找已有库存, 100 * 按 itemId + warehouseId + locationId + areaId + batchId 查找已有库存,
102 - * 存在则增加 quantityOnhand;不存在则新建记录。 101 + * 存在则增加 quantity;不存在则新建记录。
103 * 102 *
104 * @param itemId 物料编号 103 * @param itemId 物料编号
105 * @param warehouseId 仓库编号 104 * @param warehouseId 仓库编号
@@ -108,11 +107,19 @@ public interface MesWmMaterialStockService { @@ -108,11 +107,19 @@ public interface MesWmMaterialStockService {
108 * @param batchId 批次编号 107 * @param batchId 批次编号
109 * @param quantity 增加数量 108 * @param quantity 增加数量
110 * @param vendorId 供应商编号 109 * @param vendorId 供应商编号
111 - * @param productionDate 生产日期  
112 - * @param expireDate 过期日期  
113 */ 110 */
114 void increaseStock(Long itemId, Long warehouseId, Long locationId, Long areaId, 111 void increaseStock(Long itemId, Long warehouseId, Long locationId, Long areaId,
115 - Long batchId, BigDecimal quantity, Long vendorId,  
116 - LocalDateTime productionDate, LocalDateTime expireDate); 112 + Long batchId, BigDecimal quantity, Long vendorId);
  113 +
  114 + /**
  115 + * 扣减库存
  116 + *
  117 + * 根据库存记录 ID 直接扣减数量。
  118 + *
  119 + * @param materialStockId 库存记录编号
  120 + * @param quantity 扣减数量(正数)
  121 + * @param storageCheckFlag 是否校验库存充足
  122 + */
  123 + void decreaseStock(Long materialStockId, BigDecimal quantity, boolean storageCheckFlag);
117 124
118 } 125 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java
@@ -22,6 +22,7 @@ import java.util.*; @@ -22,6 +22,7 @@ import java.util.*;
22 22
23 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 23 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
24 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 24 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  25 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_MATERIAL_STOCK_INSUFFICIENT;
25 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_MATERIAL_STOCK_NOT_EXISTS; 26 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_MATERIAL_STOCK_NOT_EXISTS;
26 27
27 /** 28 /**
@@ -81,7 +82,7 @@ public class MesWmMaterialStockServiceImpl implements MesWmMaterialStockService @@ -81,7 +82,7 @@ public class MesWmMaterialStockServiceImpl implements MesWmMaterialStockService
81 // 更新冻结状态 82 // 更新冻结状态
82 MesWmMaterialStockDO updateObj = new MesWmMaterialStockDO(); 83 MesWmMaterialStockDO updateObj = new MesWmMaterialStockDO();
83 updateObj.setId(updateReqVO.getId()); 84 updateObj.setId(updateReqVO.getId());
84 - updateObj.setFrozen(updateReqVO.getFrozen()); 85 + updateObj.setFrozenFlag(updateReqVO.getFrozenFlag());
85 materialStockMapper.updateById(updateObj); 86 materialStockMapper.updateById(updateObj);
86 } 87 }
87 88
@@ -113,30 +114,40 @@ public class MesWmMaterialStockServiceImpl implements MesWmMaterialStockService @@ -113,30 +114,40 @@ public class MesWmMaterialStockServiceImpl implements MesWmMaterialStockService
113 return materialStockMapper.selectListForStockTaking(reqVO); 114 return materialStockMapper.selectListForStockTaking(reqVO);
114 } 115 }
115 116
116 - // TODO DONE @AI:increaseStock 方法命名合理,语义清晰  
117 @Override 117 @Override
118 public void increaseStock(Long itemId, Long warehouseId, Long locationId, Long areaId, 118 public void increaseStock(Long itemId, Long warehouseId, Long locationId, Long areaId,
119 - Long batchId, BigDecimal quantity, Long vendorId,  
120 - LocalDateTime productionDate, LocalDateTime expireDate) { 119 + Long batchId, BigDecimal quantity, Long vendorId) {
121 // 1. 查找已有库存记录 120 // 1. 查找已有库存记录
122 MesWmMaterialStockDO stock = materialStockMapper.selectByCompositeKey( 121 MesWmMaterialStockDO stock = materialStockMapper.selectByCompositeKey(
123 itemId, warehouseId, locationId, areaId, batchId); 122 itemId, warehouseId, locationId, areaId, batchId);
124 123
125 // 2a. 存在则增加数量 124 // 2a. 存在则增加数量
126 if (stock != null) { 125 if (stock != null) {
127 - materialStockMapper.incrQuantityOnhand(stock.getId(), quantity); 126 + materialStockMapper.updateQuantity(stock.getId(), quantity);
128 return; 127 return;
129 } 128 }
130 129
131 - // 2. 不存在则新建 130 + // 2b. 不存在则新建
132 MesMdItemDO item = itemService.validateItemExists(itemId); 131 MesMdItemDO item = itemService.validateItemExists(itemId);
133 MesWmMaterialStockDO newStock = MesWmMaterialStockDO.builder() 132 MesWmMaterialStockDO newStock = MesWmMaterialStockDO.builder()
134 - .itemId(itemId).itemTypeId(item.getItemTypeId()).unitMeasureId(item.getUnitMeasureId()) 133 + .itemId(itemId).itemTypeId(item.getItemTypeId())
135 .warehouseId(warehouseId).locationId(locationId).areaId(areaId) 134 .warehouseId(warehouseId).locationId(locationId).areaId(areaId)
136 - .batchId(batchId).vendorId(vendorId).quantityOnhand(quantity)  
137 - .recptDate(LocalDateTime.now()).productionDate(productionDate).expireDate(expireDate).frozen(false) 135 + .batchId(batchId).vendorId(vendorId).quantity(quantity)
  136 + .receiptTime(LocalDateTime.now()).frozenFlag(false)
138 .build(); 137 .build();
139 materialStockMapper.insert(newStock); 138 materialStockMapper.insert(newStock);
140 } 139 }
141 140
  141 + @Override
  142 + public void decreaseStock(Long materialStockId, BigDecimal quantity, boolean storageCheckFlag) {
  143 + // 1. 校验库存记录存在
  144 + validateMaterialStockExists(materialStockId);
  145 +
  146 + // 2. 扣减库存(Mapper 层 CAS 防负库存)
  147 + int updatedRows = materialStockMapper.updateQuantity(materialStockId, quantity.negate());
  148 + if (storageCheckFlag && updatedRows == 0) {
  149 + throw exception(WM_MATERIAL_STOCK_INSUFFICIENT);
  150 + }
  151 + }
  152 +
142 } 153 }