From df16ac3c8e14d7f68b3746b03ec40430b8bea29f Mon Sep 17 00:00:00 2001 From: Timi Date: Fri, 4 Sep 2026 10:37:43 +0800 Subject: [PATCH] rename GaoStockRecord to GaoProductStockRecord --- .../modules/gao/bean/GaoPermissionCode.java | 2 + .../controller/GaoProductStockController.java | 29 ++- .../gao/entity/GaoOrderFulfillment.java | 2 +- .../api/modules/gao/entity/GaoProduct.java | 6 - ...Record.java => GaoProductStockRecord.java} | 12 +- .../mapper/GaoProductStockRecordMapper.java | 36 ++++ .../gao/mapper/GaoStockRecordMapper.java | 30 --- .../service/GaoProductStockRecordService.java | 41 +++++ .../gao/service/GaoProductStockService.java | 5 +- .../gao/service/GaoStockRecordService.java | 32 ---- .../GaoOrderFulfillmentServiceImplement.java | 12 +- .../implement/GaoProductServiceImplement.java | 11 -- ...aoProductStockRecordServiceImplement.java} | 65 ++++--- .../GaoProductStockServiceImplement.java | 173 ++++++++++++++---- .../GaoProductUnitServiceImplement.java | 29 +-- .../implement/GaoStoreServiceImplement.java | 2 +- .../gao/vo/GaoProductStockChangeRequest.java | 3 - .../GaoProductStockManualRevokeRequest.java | 26 +++ ...ge.java => GaoProductStockRecordPage.java} | 11 +- .../V60__remove_gao_product_base_unit.sql | 2 + ...V61__remove_gao_product_reference_cost.sql | 2 + ...ame_gao_stock_record_and_remove_reason.sql | 12 ++ ...er.xml => GaoProductStockRecordMapper.xml} | 31 +++- 23 files changed, 377 insertions(+), 197 deletions(-) rename src/main/java/com/imyeyu/api/modules/gao/entity/{GaoStockRecord.java => GaoProductStockRecord.java} (92%) create mode 100644 src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductStockRecordMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStockRecordMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/GaoProductStockRecordService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/GaoStockRecordService.java rename src/main/java/com/imyeyu/api/modules/gao/service/implement/{GaoStockRecordServiceImplement.java => GaoProductStockRecordServiceImplement.java} (55%) create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoProductStockManualRevokeRequest.java rename src/main/java/com/imyeyu/api/modules/gao/vo/{GaoStockRecordPage.java => GaoProductStockRecordPage.java} (64%) create mode 100644 src/main/resources/db/migration/timiserver/V60__remove_gao_product_base_unit.sql create mode 100644 src/main/resources/db/migration/timiserver/V61__remove_gao_product_reference_cost.sql create mode 100644 src/main/resources/db/migration/timiserver/V62__rename_gao_stock_record_and_remove_reason.sql rename src/main/resources/mapper/timi-server/gao/{GaoStockRecordMapper.xml => GaoProductStockRecordMapper.xml} (53%) diff --git a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java index d269d80..79b16fb 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java +++ b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java @@ -75,6 +75,8 @@ public enum GaoPermissionCode implements BuiltinPermissionCode { PRODUCT_STOCK_CREATE("PRODUCT_STOCK:CREATE", "产品库存操作", true, true), + PRODUCT_STOCK_MANUAL_REVOKE("PRODUCT_STOCK:MANUAL_REVOKE", "手工库存流水冲正", true, true), + SALES_ORDER_CREATE("SALES_ORDER:CREATE", "销售订单创建", true, true), SALES_ORDER_READ("SALES_ORDER:READ", "销售订单读取", true, true), diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoProductStockController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoProductStockController.java index 06111bd..c59c93c 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoProductStockController.java +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoProductStockController.java @@ -3,13 +3,14 @@ package com.imyeyu.api.modules.gao.controller; import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; import com.imyeyu.api.modules.gao.entity.GaoProductStock; -import com.imyeyu.api.modules.gao.entity.GaoStockRecord; +import com.imyeyu.api.modules.gao.entity.GaoProductStockRecord; import com.imyeyu.api.modules.gao.service.GaoProductStockService; -import com.imyeyu.api.modules.gao.service.GaoStockRecordService; +import com.imyeyu.api.modules.gao.service.GaoProductStockRecordService; import com.imyeyu.api.modules.gao.service.GaoStoreScopeService; import com.imyeyu.api.modules.gao.vo.GaoProductStockChangeRequest; +import com.imyeyu.api.modules.gao.vo.GaoProductStockManualRevokeRequest; import com.imyeyu.api.modules.gao.vo.GaoProductStockPage; -import com.imyeyu.api.modules.gao.vo.GaoStockRecordPage; +import com.imyeyu.api.modules.gao.vo.GaoProductStockRecordPage; import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.RequestRateLimit; @@ -31,7 +32,7 @@ import org.springframework.web.bind.annotation.RestController; public class GaoProductStockController { private final GaoProductStockService stockService; - private final GaoStockRecordService recordService; + private final GaoProductStockRecordService recordService; private final GaoStoreScopeService storeScopeService; @AOPLog @@ -76,6 +77,14 @@ public class GaoProductStockController { return stockService.manualOut(normalize(request)); } + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_MANUAL_REVOKE) + @PostMapping("/manual-revoke") + public GaoProductStock manualRevoke(@RequestBody GaoProductStockManualRevokeRequest request) { + return stockService.manualRevoke(request); + } + @AOPLog @RequestRateLimit @RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_CREATE) @@ -96,12 +105,22 @@ public class GaoProductStockController { @RequestRateLimit @RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_READ) @PostMapping("/record/list") - public PageResult recordList(@RequestBody GaoStockRecordPage page) { + public PageResult recordList(@RequestBody GaoProductStockRecordPage page) { TimiException.required(page, "not found stock record page"); page.setStoreId(storeScopeService.resolveStoreId(page.getStoreId())); return recordService.pageByQuery(page); } + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_READ) + @PostMapping("/record/detail") + public GaoProductStockRecord recordDetail(@RequestParam String id) { + GaoProductStockRecord result = recordService.detail(id); + storeScopeService.checkStoreId(result.getStoreId()); + return result; + } + private GaoProductStockChangeRequest normalize(GaoProductStockChangeRequest request) { TimiException.required(request, "not found stock change request"); request.setStoreId(storeScopeService.resolveStoreId(request.getStoreId())); diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoOrderFulfillment.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoOrderFulfillment.java index 21e208d..c7b5e88 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoOrderFulfillment.java +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoOrderFulfillment.java @@ -106,7 +106,7 @@ public class GaoOrderFulfillment extends UUIDEntity { /// 库存处理流水 @Transient - private List stockRecordList; + private List stockRecordList; /// 附件 @Transient diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProduct.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProduct.java index 4584bb0..34024cb 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProduct.java +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProduct.java @@ -65,9 +65,6 @@ public class GaoProduct extends UUIDEntity { /// 产品类型 private ProductType productType; - /// 基础库存单位名称 - private String baseUnit; - /// true 为启用库存管理 private Boolean stockEnabled; @@ -77,9 +74,6 @@ public class GaoProduct extends UUIDEntity { /// 状态 private Status status; - /// 参考成本,单位分 - private Long referenceCost; - /// 最低库存数量,基础单位 private BigDecimal minStockQuantity; diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoStockRecord.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductStockRecord.java similarity index 92% rename from src/main/java/com/imyeyu/api/modules/gao/entity/GaoStockRecord.java rename to src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductStockRecord.java index 6b5b593..b5d007a 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoStockRecord.java +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoProductStockRecord.java @@ -17,7 +17,7 @@ import java.util.List; /// @author Codex /// @since 2026-09-02 @Data -public class GaoStockRecord implements IDEntity, Creatable { +public class GaoProductStockRecord implements IDEntity, Creatable { /// 库存流水业务类型 public enum BizType { @@ -41,6 +41,9 @@ public class GaoStockRecord implements IDEntity, Creatable { /// 手工出库 MANUAL_OUT, + /// 手工流水冲正 + MANUAL_REVOKE, + /// 履约预占 ORDER_RESERVE, @@ -118,6 +121,10 @@ public class GaoStockRecord implements IDEntity, Creatable { /// 冲正来源流水 ID private String sourceRecordId; + /// 关联的冲正流水 ID + @Transient + private String revokeRecordId; + /// 幂等请求号 private String requestNo; @@ -127,9 +134,6 @@ public class GaoStockRecord implements IDEntity, Creatable { /// 发生时间 private Long occurredAt; - /// 原因 - private String reason; - /// 备注 private String remark; diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductStockRecordMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductStockRecordMapper.java new file mode 100644 index 0000000..f675b8d --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoProductStockRecordMapper.java @@ -0,0 +1,36 @@ +package com.imyeyu.api.modules.gao.mapper; + +import com.imyeyu.api.modules.gao.entity.GaoProductStockRecord; +import com.imyeyu.api.modules.gao.vo.GaoProductStockRecordPage; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/// GAO 库存流水 Mapper +/// +/// @author Codex +/// @since 2026-09-02 +public interface GaoProductStockRecordMapper extends BaseMapper { + + /// 按门店和请求号查询库存流水 + List selectByRequestNo(@Param("storeId") String storeId, @Param("requestNo") String requestNo); + + /// 锁定库存流水 + GaoProductStockRecord selectForUpdate(@Param("id") String id); + + /// 查询库存冲正流水 + GaoProductStockRecord selectRevokeBySourceRecordId(@Param("sourceRecordId") String sourceRecordId); + + /// 按业务查询库存流水 + List selectByBiz(@Param("bizType") GaoProductStockRecord.BizType bizType, @Param("bizId") String bizId); + + /// 查询指定业务和产品的库存流水 + GaoProductStockRecord selectByBizProduct(@Param("bizType") GaoProductStockRecord.BizType bizType, @Param("bizId") String bizId, @Param("productId") String productId, @Param("operationType") GaoProductStockRecord.OperationType operationType); + + /// 按条件统计库存流水 + long countByQuery(GaoProductStockRecordPage page); + + /// 按条件查询库存流水 + List selectByQuery(GaoProductStockRecordPage page); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStockRecordMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStockRecordMapper.java deleted file mode 100644 index 7c36e2d..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStockRecordMapper.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.imyeyu.api.modules.gao.mapper; - -import com.imyeyu.api.modules.gao.entity.GaoStockRecord; -import com.imyeyu.api.modules.gao.vo.GaoStockRecordPage; -import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Param; - -import java.util.List; - -/// GAO 库存流水 Mapper -/// -/// @author Codex -/// @since 2026-09-02 -public interface GaoStockRecordMapper extends BaseMapper { - - /// 按门店和请求号查询库存流水 - List selectByRequestNo(@Param("storeId") String storeId, @Param("requestNo") String requestNo); - - /// 按业务查询库存流水 - List selectByBiz(@Param("bizType") GaoStockRecord.BizType bizType, @Param("bizId") String bizId); - - /// 查询指定业务和产品的库存流水 - GaoStockRecord selectByBizProduct(@Param("bizType") GaoStockRecord.BizType bizType, @Param("bizId") String bizId, @Param("productId") String productId, @Param("operationType") GaoStockRecord.OperationType operationType); - - /// 按条件统计库存流水 - long countByQuery(GaoStockRecordPage page); - - /// 按条件查询库存流水 - List selectByQuery(GaoStockRecordPage page); -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoProductStockRecordService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoProductStockRecordService.java new file mode 100644 index 0000000..0ced846 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoProductStockRecordService.java @@ -0,0 +1,41 @@ +package com.imyeyu.api.modules.gao.service; + +import com.imyeyu.api.modules.common.entity.Attachment; +import com.imyeyu.api.modules.gao.entity.GaoProductStockRecord; +import com.imyeyu.api.modules.gao.vo.GaoProductStockRecordPage; +import com.imyeyu.spring.bean.PageResult; +import com.imyeyu.spring.service.BaseService; + +import java.util.List; + +/// GAO 库存流水服务 +/// +/// @author Codex +/// @since 2026-09-02 +public interface GaoProductStockRecordService extends BaseService { + + /// 按条件查询库存流水 + PageResult pageByQuery(GaoProductStockRecordPage page); + + /// 查询库存流水详情 + GaoProductStockRecord detail(String id); + + /// 按来源业务查询库存流水 + List listByBiz(GaoProductStockRecord.BizType bizType, String bizId); + + /// 按门店和请求号查询库存流水 + List listByRequestNo(String storeId, String requestNo); + + /// 锁定库存流水 + GaoProductStockRecord getForUpdate(String id); + + /// 查询库存冲正流水 + GaoProductStockRecord getRevokeBySourceRecordId(String sourceRecordId); + + /// 查询指定业务和产品的库存流水 + GaoProductStockRecord getByBizProduct(GaoProductStockRecord.BizType bizType, String bizId, String productId, + GaoProductStockRecord.OperationType operationType); + + /// 保存库存操作流水及附件 + void createOperationRecord(GaoProductStockRecord record, List attachmentList); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoProductStockService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoProductStockService.java index c526b85..e44a629 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoProductStockService.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoProductStockService.java @@ -1,8 +1,8 @@ package com.imyeyu.api.modules.gao.service; import com.imyeyu.api.modules.gao.entity.GaoProductStock; -import com.imyeyu.api.modules.gao.entity.GaoStockRecord; import com.imyeyu.api.modules.gao.vo.GaoProductStockChangeRequest; +import com.imyeyu.api.modules.gao.vo.GaoProductStockManualRevokeRequest; import com.imyeyu.api.modules.gao.vo.GaoProductStockPage; import com.imyeyu.api.modules.gao.vo.GaoStockChangeItem; import com.imyeyu.spring.bean.PageResult; @@ -30,6 +30,9 @@ public interface GaoProductStockService { /// 手工出库 GaoProductStock manualOut(GaoProductStockChangeRequest request); + /// 手工库存流水冲正 + GaoProductStock manualRevoke(GaoProductStockManualRevokeRequest request); + /// 盘盈 GaoProductStock stocktakeGain(GaoProductStockChangeRequest request); diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoStockRecordService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStockRecordService.java deleted file mode 100644 index a58ad5c..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoStockRecordService.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.imyeyu.api.modules.gao.service; - -import com.imyeyu.api.modules.common.entity.Attachment; -import com.imyeyu.api.modules.gao.entity.GaoStockRecord; -import com.imyeyu.api.modules.gao.vo.GaoStockRecordPage; -import com.imyeyu.spring.bean.PageResult; -import com.imyeyu.spring.service.BaseService; - -import java.util.List; - -/// GAO 库存流水服务 -/// -/// @author Codex -/// @since 2026-09-02 -public interface GaoStockRecordService extends BaseService { - - /// 按条件查询库存流水 - PageResult pageByQuery(GaoStockRecordPage page); - - /// 按来源业务查询库存流水 - List listByBiz(GaoStockRecord.BizType bizType, String bizId); - - /// 按门店和请求号查询库存流水 - List listByRequestNo(String storeId, String requestNo); - - /// 查询指定业务和产品的库存流水 - GaoStockRecord getByBizProduct(GaoStockRecord.BizType bizType, String bizId, String productId, - GaoStockRecord.OperationType operationType); - - /// 保存库存操作流水及附件 - void createOperationRecord(GaoStockRecord record, List attachmentList); -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoOrderFulfillmentServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoOrderFulfillmentServiceImplement.java index 7768185..0b4ec7b 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoOrderFulfillmentServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoOrderFulfillmentServiceImplement.java @@ -11,7 +11,7 @@ import com.imyeyu.api.modules.gao.entity.GaoProduct; import com.imyeyu.api.modules.gao.entity.GaoProductUnit; import com.imyeyu.api.modules.gao.entity.GaoSalesOrder; import com.imyeyu.api.modules.gao.entity.GaoSalesOrderItem; -import com.imyeyu.api.modules.gao.entity.GaoStockRecord; +import com.imyeyu.api.modules.gao.entity.GaoProductStockRecord; import com.imyeyu.api.modules.gao.entity.GaoOrderFulfillment; import com.imyeyu.api.modules.gao.entity.GaoOrderFulfillmentItem; import com.imyeyu.api.modules.gao.mapper.GaoOrderFulfillmentItemMapper; @@ -22,7 +22,7 @@ import com.imyeyu.api.modules.gao.service.GaoProductService; import com.imyeyu.api.modules.gao.service.GaoProductStockService; import com.imyeyu.api.modules.gao.service.GaoProductUnitService; import com.imyeyu.api.modules.gao.service.GaoSalesOrderService; -import com.imyeyu.api.modules.gao.service.GaoStockRecordService; +import com.imyeyu.api.modules.gao.service.GaoProductStockRecordService; import com.imyeyu.api.modules.gao.service.GaoStoreScopeService; import com.imyeyu.api.modules.gao.vo.GaoOrderFulfillmentActionRequest; import com.imyeyu.api.modules.gao.vo.GaoOrderFulfillmentCreateRequest; @@ -74,7 +74,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService implements GaoStockRecordService { +public class GaoProductStockRecordServiceImplement extends AbstractEntityService implements GaoProductStockRecordService { - private final GaoStockRecordMapper mapper; + private final GaoProductStockRecordMapper mapper; private final AttachmentService attachmentService; private final UserService userService; @Override - protected BaseMapper mapper() { + protected BaseMapper mapper() { return mapper; } @Override - public PageResult pageByQuery(GaoStockRecordPage page) { + public PageResult pageByQuery(GaoProductStockRecordPage page) { TimiException.required(page, "not found stock record page"); - PageResult result = new PageResult<>(); + PageResult result = new PageResult<>(); result.setTotal(mapper.countByQuery(page)); result.setList(mapper.selectByQuery(page)); fillTransient(result.getList()); @@ -47,24 +47,47 @@ public class GaoStockRecordServiceImplement extends AbstractEntityService listByBiz(GaoStockRecord.BizType bizType, String bizId) { + public GaoProductStockRecord detail(String id) { + TimiException.required(id, "not found stock record id"); + GaoProductStockRecord result = get(id); + TimiException.required(result, "not found stock record"); + GaoProductStockRecord revoke = mapper.selectRevokeBySourceRecordId(result.getId()); + result.setRevokeRecordId(revoke == null ? null : revoke.getId()); + fillTransient(List.of(result)); + return result; + } + + @Override + public List listByBiz(GaoProductStockRecord.BizType bizType, String bizId) { TimiException.required(bizType, "not found stock bizType"); TimiException.required(bizId, "not found stock bizId"); - List result = mapper.selectByBiz(bizType, bizId); + List result = mapper.selectByBiz(bizType, bizId); fillTransient(result); return result; } @Override - public List listByRequestNo(String storeId, String requestNo) { + public List listByRequestNo(String storeId, String requestNo) { TimiException.required(storeId, "not found storeId"); TimiException.required(requestNo, "not found requestNo"); return mapper.selectByRequestNo(storeId, requestNo); } @Override - public GaoStockRecord getByBizProduct(GaoStockRecord.BizType bizType, String bizId, String productId, - GaoStockRecord.OperationType operationType) { + public GaoProductStockRecord getForUpdate(String id) { + TimiException.required(id, "not found stock record id"); + return mapper.selectForUpdate(id); + } + + @Override + public GaoProductStockRecord getRevokeBySourceRecordId(String sourceRecordId) { + TimiException.required(sourceRecordId, "not found sourceRecordId"); + return mapper.selectRevokeBySourceRecordId(sourceRecordId); + } + + @Override + public GaoProductStockRecord getByBizProduct(GaoProductStockRecord.BizType bizType, String bizId, String productId, + GaoProductStockRecord.OperationType operationType) { TimiException.required(bizType, "not found stock bizType"); TimiException.required(bizId, "not found stock bizId"); TimiException.required(productId, "not found productId"); @@ -73,7 +96,7 @@ public class GaoStockRecordServiceImplement extends AbstractEntityService attachmentList) { + public void createOperationRecord(GaoProductStockRecord record, List attachmentList) { TimiException.required(record, "not found stock record"); mapper.insert(record); if (attachmentList != null && !attachmentList.isEmpty()) { @@ -82,12 +105,12 @@ public class GaoStockRecordServiceImplement extends AbstractEntityService recordList) { + private void fillTransient(List recordList) { if (recordList == null || recordList.isEmpty()) { return; } - List idList = recordList.stream().map(GaoStockRecord::getId).toList(); + List idList = recordList.stream().map(GaoProductStockRecord::getId).toList(); Map> attachmentMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_STOCK_RECORD, idList); - List userIdList = recordList.stream().map(GaoStockRecord::getOperatorUserId) + List userIdList = recordList.stream().map(GaoProductStockRecord::getOperatorUserId) .filter(value -> value != null && !value.isBlank()).distinct().toList(); Map userMap = userIdList.isEmpty() ? Map.of() : userService.mapByIdList(userIdList); - for (GaoStockRecord record : recordList) { + for (GaoProductStockRecord record : recordList) { record.setAttachmentList(attachmentMap.getOrDefault(record.getId(), List.of())); record.setOperator(userMap.get(record.getOperatorUserId())); } diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductStockServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductStockServiceImplement.java index dcfc7ed..6957762 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductStockServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductStockServiceImplement.java @@ -2,17 +2,19 @@ package com.imyeyu.api.modules.gao.service.implement; import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.modules.common.entity.Attachment; +import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.gao.entity.GaoProduct; import com.imyeyu.api.modules.gao.entity.GaoProductStock; import com.imyeyu.api.modules.gao.entity.GaoProductUnit; -import com.imyeyu.api.modules.gao.entity.GaoStockRecord; +import com.imyeyu.api.modules.gao.entity.GaoProductStockRecord; import com.imyeyu.api.modules.gao.mapper.GaoProductStockMapper; import com.imyeyu.api.modules.gao.service.GaoProductService; import com.imyeyu.api.modules.gao.service.GaoProductStockService; import com.imyeyu.api.modules.gao.service.GaoProductUnitService; -import com.imyeyu.api.modules.gao.service.GaoStockRecordService; +import com.imyeyu.api.modules.gao.service.GaoProductStockRecordService; import com.imyeyu.api.modules.gao.service.GaoStoreScopeService; import com.imyeyu.api.modules.gao.vo.GaoProductStockChangeRequest; +import com.imyeyu.api.modules.gao.vo.GaoProductStockManualRevokeRequest; import com.imyeyu.api.modules.gao.vo.GaoProductStockPage; import com.imyeyu.api.modules.gao.vo.GaoProductUnitSelection; import com.imyeyu.api.modules.gao.vo.GaoStockChangeItem; @@ -49,7 +51,8 @@ public class GaoProductStockServiceImplement implements GaoProductStockService { private static final BigDecimal ZERO = BigDecimal.ZERO; private final GaoProductStockMapper stockMapper; - private final GaoStockRecordService recordService; + private final AttachmentService attachmentService; + private final GaoProductStockRecordService recordService; private final GaoProductService productService; private final GaoProductUnitService unitService; private final GaoStoreScopeService storeScopeService; @@ -62,8 +65,17 @@ public class GaoProductStockServiceImplement implements GaoProductStockService { PageResult result = new PageResult<>(); result.setTotal(stockMapper.countByQuery(page)); List list = stockMapper.selectByQuery(page); + Map photoMap = attachmentService.mapFirstImageByIdList( + Attachment.BizType.GAO_PRODUCT, + list.stream().map(GaoProductStock::getProductId).filter(id -> id != null && !id.isBlank()).toList(), + GaoProduct.AttachType.PHOTO.name() + ); for (GaoProductStock stock : list) { fillBalance(stock); + if (stock.getProduct() != null) { + Attachment photo = photoMap.get(stock.getProductId()); + stock.getProduct().setAttachmentList(photo == null ? List.of() : List.of(photo)); + } } result.setList(list); return result; @@ -93,31 +105,37 @@ public class GaoProductStockServiceImplement implements GaoProductStockService { @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public GaoProductStock initialIn(GaoProductStockChangeRequest request) { - return loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_STOCK_INITIAL_IN", request, () -> change(request, GaoStockRecord.OperationType.INITIAL_IN)); + return loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_STOCK_INITIAL_IN", request, () -> change(request, GaoProductStockRecord.OperationType.INITIAL_IN)); } @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public GaoProductStock manualIn(GaoProductStockChangeRequest request) { - return loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_STOCK_MANUAL_IN", request, () -> change(request, GaoStockRecord.OperationType.MANUAL_IN)); + return loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_STOCK_MANUAL_IN", request, () -> change(request, GaoProductStockRecord.OperationType.MANUAL_IN)); } @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public GaoProductStock manualOut(GaoProductStockChangeRequest request) { - return loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_STOCK_MANUAL_OUT", request, () -> change(request, GaoStockRecord.OperationType.MANUAL_OUT)); + return loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_STOCK_MANUAL_OUT", request, () -> change(request, GaoProductStockRecord.OperationType.MANUAL_OUT)); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public GaoProductStock manualRevoke(GaoProductStockManualRevokeRequest request) { + return loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_STOCK_MANUAL_REVOKE", request, () -> manualRevokeInternal(request)); } @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public GaoProductStock stocktakeGain(GaoProductStockChangeRequest request) { - return loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_STOCK_STOCKTAKE_GAIN", request, () -> change(request, GaoStockRecord.OperationType.STOCKTAKE_GAIN)); + return loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_STOCK_STOCKTAKE_GAIN", request, () -> change(request, GaoProductStockRecord.OperationType.STOCKTAKE_GAIN)); } @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public GaoProductStock stocktakeLoss(GaoProductStockChangeRequest request) { - return loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_STOCK_STOCKTAKE_LOSS", request, () -> change(request, GaoStockRecord.OperationType.STOCKTAKE_LOSS)); + return loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_STOCK_STOCKTAKE_LOSS", request, () -> change(request, GaoProductStockRecord.OperationType.STOCKTAKE_LOSS)); } @Override @@ -141,62 +159,130 @@ public class GaoProductStockServiceImplement implements GaoProductStockService { @Override @Transactional(TimiServerDBConfig.ROLLBACKER) public List reserveForFulfillment(String storeId, String bizId, List items, String requestNo, String operatorUserId) { - return executeInternal(storeId, bizId, items, requestNo, operatorUserId, GaoStockRecord.OperationType.ORDER_RESERVE, null, null); + return executeInternal(storeId, bizId, items, requestNo, operatorUserId, GaoProductStockRecord.OperationType.ORDER_RESERVE, null); } @Override @Transactional(TimiServerDBConfig.ROLLBACKER) public List releaseForFulfillment(String storeId, String bizId, List items, String requestNo, String operatorUserId) { - return executeInternal(storeId, bizId, items, requestNo, operatorUserId, GaoStockRecord.OperationType.ORDER_RELEASE, null, null); + return executeInternal(storeId, bizId, items, requestNo, operatorUserId, GaoProductStockRecord.OperationType.ORDER_RELEASE, null); } @Override @Transactional(TimiServerDBConfig.ROLLBACKER) public List outForFulfillment(String storeId, String bizId, List items, String requestNo, String operatorUserId) { - return executeInternal(storeId, bizId, items, requestNo, operatorUserId, GaoStockRecord.OperationType.ORDER_OUT, null, null); + return executeInternal(storeId, bizId, items, requestNo, operatorUserId, GaoProductStockRecord.OperationType.ORDER_OUT, null); } @Override @Transactional(TimiServerDBConfig.ROLLBACKER) public List revokeForFulfillment(String storeId, String bizId, List items, String requestNo, String operatorUserId) { - return executeInternal(storeId, bizId, items, requestNo, operatorUserId, GaoStockRecord.OperationType.ORDER_REVOKE, null, null); + return executeInternal(storeId, bizId, items, requestNo, operatorUserId, GaoProductStockRecord.OperationType.ORDER_REVOKE, null); } - private GaoProductStock change(GaoProductStockChangeRequest request, GaoStockRecord.OperationType operationType) { + private GaoProductStock change(GaoProductStockChangeRequest request, GaoProductStockRecord.OperationType operationType) { TimiException.required(request, "not found stock change request"); String requestNo = requiredText(request.getRequestNo(), "not found requestNo"); String storeId = storeScopeService.resolveStoreId(request.getStoreId()); TimiException.required(storeId, "not found storeId"); - boolean purchase = operationType == GaoStockRecord.OperationType.INITIAL_IN - || operationType == GaoStockRecord.OperationType.MANUAL_IN - || operationType == GaoStockRecord.OperationType.STOCKTAKE_GAIN; + boolean purchase = operationType == GaoProductStockRecord.OperationType.INITIAL_IN + || operationType == GaoProductStockRecord.OperationType.MANUAL_IN + || operationType == GaoProductStockRecord.OperationType.STOCKTAKE_GAIN; GaoProductUnitSelection selection = productService.getProductUnit(storeId, request.getProductId(), request.getProductUnitId(), purchase); GaoProduct product = selection.getProduct(); GaoProductUnit unit = selection.getUnit(); TimiException.requiredTrue(Boolean.TRUE.equals(product.getStockEnabled()), "该产品未启用库存管理"); BigDecimal quantity = validateQuantity(product, request.getQuantity()); BigDecimal baseQuantity = unitService.convertQuantityToBase(unit, quantity); - List existingList = recordService.listByRequestNo(storeId, requestNo); + List existingList = recordService.listByRequestNo(storeId, requestNo); if (!existingList.isEmpty()) { - GaoStockRecord existing = existingList.getFirst(); - verifyExisting(existing, operationType, product.getId(), unit.getId(), quantity, baseQuantity, request.getRemark(), request.getReason()); + GaoProductStockRecord existing = existingList.getFirst(); + verifyExisting(existing, operationType, product.getId(), unit.getId(), quantity, baseQuantity, request.getRemark()); return detail(storeId, product.getId()); } TimiException.requiredTrue(hasAttachment(request.getAttachmentList()), "库存操作必须上传照片"); - TimiException.requiredTrue(hasReason(request.getReason()) || hasReason(request.getRemark()), "库存操作必须填写原因或备注"); + TimiException.requiredTrue(hasText(request.getRemark()), "库存操作必须填写备注"); GaoStockChangeItem item = GaoStockChangeItem.of(product, unit, quantity, baseQuantity); List result = executeInternal(storeId, product.getId(), List.of(item), requestNo, - userLoginService.getRequireLoginUserId(), operationType, request.getReason(), request.getRemark(), request.getAttachmentList()); + userLoginService.getRequireLoginUserId(), operationType, request.getRemark(), request.getAttachmentList()); return result.getFirst(); } - private List executeInternal(String storeId, String bizId, List sourceList, String requestNo, - String operatorUserId, GaoStockRecord.OperationType operationType, String reason, String remark) { - return executeInternal(storeId, bizId, sourceList, requestNo, operatorUserId, operationType, reason, remark, null); + private GaoProductStock manualRevokeInternal(GaoProductStockManualRevokeRequest request) { + TimiException.required(request, "not found stock manual revoke request"); + String requestNo = requiredText(request.getRequestNo(), "not found requestNo"); + String sourceRecordId = requiredText(request.getSourceRecordId(), "not found sourceRecordId"); + String remark = requiredText(request.getRemark(), "库存流水冲正备注不能为空"); + GaoProductStockRecord source = recordService.getForUpdate(sourceRecordId); + TimiException.required(source, "not found source stock record"); + TimiException.requiredTrue(source.getBizType() == GaoProductStockRecord.BizType.PRODUCT_STOCK, "原库存流水不是手工库存流水"); + TimiException.requiredTrue(source.getOperationType() == GaoProductStockRecord.OperationType.MANUAL_IN + || source.getOperationType() == GaoProductStockRecord.OperationType.MANUAL_OUT, "只有手工入库或手工出库流水可以冲正"); + TimiException.requiredNull(source.getSourceRecordId(), "只能冲正原始手工库存流水"); + String storeId = requiredText(source.getStoreId(), "原库存流水缺少门店"); + String productId = requiredText(source.getProductId(), "原库存流水缺少产品"); + storeScopeService.checkStoreId(storeId); + + List existingList = recordService.listByRequestNo(storeId, requestNo); + if (!existingList.isEmpty()) { + verifyManualRevokeExisting(existingList, sourceRecordId, remark); + return detail(storeId, productId); + } + TimiException.requiredNull(recordService.getRevokeBySourceRecordId(sourceRecordId), "该手工库存流水已经冲正"); + TimiException.requiredTrue(hasAttachment(request.getAttachmentList()), "库存流水冲正必须上传照片"); + TimiException.required(source.getQuantity(), "原库存流水缺少业务数量"); + TimiException.required(source.getBaseQuantity(), "原库存流水缺少基础单位数量"); + TimiException.required(source.getOnHandDelta(), "原库存流水缺少库存变动"); + BigDecimal sourceReservedDelta = decimal(source.getReservedDelta()); + TimiException.requiredTrue(sourceReservedDelta.signum() == 0, "手工库存流水不能包含预占变动"); + + GaoProductStock stock = lockOrCreateStock(storeId, productId); + BigDecimal onHandBefore = decimal(stock.getOnHandQuantity()); + BigDecimal reservedBefore = decimal(stock.getReservedQuantity()); + BigDecimal onHandDelta = source.getOnHandDelta().negate(); + BigDecimal reservedDelta = sourceReservedDelta.negate(); + int updated = stockMapper.updateQuantity(stock.getId(), onHandDelta, reservedDelta, userLoginService.getRequireLoginUserId()); + TimiException.requiredTrue(updated == 1, "库存余额已变化,无法冲正该流水"); + BigDecimal onHandAfter = onHandBefore.add(onHandDelta); + BigDecimal reservedAfter = reservedBefore.add(reservedDelta); + GaoProductStockRecord revoke = new GaoProductStockRecord(); + revoke.setStoreId(storeId); + revoke.setProductId(productId); + revoke.setProductUnitId(source.getProductUnitId()); + revoke.setProductNameSnapshot(source.getProductNameSnapshot()); + revoke.setUnitSnapshot(source.getUnitSnapshot()); + revoke.setBaseQuantityPerUnitSnapshot(source.getBaseQuantityPerUnitSnapshot()); + revoke.setQuantity(source.getQuantity()); + revoke.setBaseQuantity(source.getBaseQuantity()); + revoke.setOperationType(GaoProductStockRecord.OperationType.MANUAL_REVOKE); + revoke.setOnHandDelta(onHandDelta); + revoke.setReservedDelta(reservedDelta); + revoke.setOnHandBefore(onHandBefore); + revoke.setOnHandAfter(onHandAfter); + revoke.setReservedBefore(reservedBefore); + revoke.setReservedAfter(reservedAfter); + revoke.setBizType(GaoProductStockRecord.BizType.PRODUCT_STOCK); + revoke.setBizId(source.getBizId()); + revoke.setSourceRecordId(source.getId()); + revoke.setRequestNo(requestNo); + revoke.setOperatorUserId(userLoginService.getRequireLoginUserId()); + revoke.setOccurredAt(Time.now()); + revoke.setRemark(remark); + recordService.createOperationRecord(revoke, request.getAttachmentList()); + stock.setOnHandQuantity(onHandAfter); + stock.setReservedQuantity(reservedAfter); + stock.setUpdatedBy(revoke.getOperatorUserId()); + fillBalance(stock); + return stock; } private List executeInternal(String storeId, String bizId, List sourceList, String requestNo, - String operatorUserId, GaoStockRecord.OperationType operationType, String reason, String remark, List attachmentList) { + String operatorUserId, GaoProductStockRecord.OperationType operationType, String remark) { + return executeInternal(storeId, bizId, sourceList, requestNo, operatorUserId, operationType, remark, null); + } + + private List executeInternal(String storeId, String bizId, List sourceList, String requestNo, + String operatorUserId, GaoProductStockRecord.OperationType operationType, String remark, List attachmentList) { TimiException.required(storeId, "not found storeId"); TimiException.required(bizId, "not found stock bizId"); TimiException.requiredTrue(sourceList != null && !sourceList.isEmpty(), "库存变更至少需要一个产品"); @@ -211,7 +297,7 @@ public class GaoProductStockServiceImplement implements GaoProductStockService { TimiException.required(item.getBaseQuantity(), "基础单位数量不能为空"); TimiException.requiredTrue(0 < item.getBaseQuantity().signum(), "基础单位数量必须大于 0"); } - List existingList = recordService.listByRequestNo(storeId, normalizedRequestNo); + List existingList = recordService.listByRequestNo(storeId, normalizedRequestNo); if (!existingList.isEmpty()) { verifyExistingList(existingList, itemList, operationType, bizId); List result = new ArrayList<>(); @@ -231,7 +317,7 @@ public class GaoProductStockServiceImplement implements GaoProductStockService { TimiException.requiredTrue(updated == 1, "库存余额已变化或库存数量不足"); BigDecimal onHandAfter = onHandBefore.add(onHandDelta); BigDecimal reservedAfter = reservedBefore.add(reservedDelta); - GaoStockRecord record = new GaoStockRecord(); + GaoProductStockRecord record = new GaoProductStockRecord(); record.setStoreId(storeId); record.setProductId(item.getProductId()); record.setProductUnitId(item.getProductUnitId()); @@ -247,13 +333,12 @@ public class GaoProductStockServiceImplement implements GaoProductStockService { record.setOnHandAfter(onHandAfter); record.setReservedBefore(reservedBefore); record.setReservedAfter(reservedAfter); - record.setBizType(operationType.name().startsWith("ORDER_") ? GaoStockRecord.BizType.ORDER_FULFILLMENT : GaoStockRecord.BizType.PRODUCT_STOCK); + record.setBizType(operationType.name().startsWith("ORDER_") ? GaoProductStockRecord.BizType.ORDER_FULFILLMENT : GaoProductStockRecord.BizType.PRODUCT_STOCK); record.setBizId(bizId); record.setSourceRecordId(item.getSourceRecordId()); record.setRequestNo(normalizedRequestNo); record.setOperatorUserId(operatorUserId); record.setOccurredAt(Time.now()); - record.setReason(reason); record.setRemark(remark); recordService.createOperationRecord(record, attachmentList); stock.setOnHandQuantity(onHandAfter); @@ -284,15 +369,16 @@ public class GaoProductStockServiceImplement implements GaoProductStockService { return stock; } - private BigDecimal onHandDelta(GaoStockRecord.OperationType operationType, BigDecimal quantity) { + private BigDecimal onHandDelta(GaoProductStockRecord.OperationType operationType, BigDecimal quantity) { return switch (operationType) { case INITIAL_IN, MANUAL_IN, STOCKTAKE_GAIN, ORDER_REVOKE -> quantity; case MANUAL_OUT, STOCKTAKE_LOSS, ORDER_OUT -> quantity.negate(); case ORDER_RESERVE, ORDER_RELEASE -> ZERO; + case MANUAL_REVOKE -> throw new TimiException(TimiCode.ERROR, "手工库存冲正必须基于原库存流水"); }; } - private BigDecimal reservedDelta(GaoStockRecord.OperationType operationType, BigDecimal quantity) { + private BigDecimal reservedDelta(GaoProductStockRecord.OperationType operationType, BigDecimal quantity) { return switch (operationType) { case ORDER_RESERVE -> quantity; case ORDER_RELEASE, ORDER_OUT -> quantity.negate(); @@ -310,26 +396,33 @@ public class GaoProductStockServiceImplement implements GaoProductStockService { return quantity; } - private void verifyExisting(GaoStockRecord existing, GaoStockRecord.OperationType operationType, String productId, String unitId, - BigDecimal quantity, BigDecimal baseQuantity, String remark, String reason) { + private void verifyExisting(GaoProductStockRecord existing, GaoProductStockRecord.OperationType operationType, String productId, String unitId, + BigDecimal quantity, BigDecimal baseQuantity, String remark) { TimiException.requiredTrue(existing.getOperationType() == operationType && productId.equals(existing.getProductId()) && unitId.equals(existing.getProductUnitId()) && quantity.compareTo(existing.getQuantity()) == 0 && baseQuantity.compareTo(existing.getBaseQuantity()) == 0 - && sameText(existing.getRemark(), remark) - && sameText(existing.getReason(), reason), "requestNo 对应库存操作内容不一致"); + && sameText(existing.getRemark(), remark), "requestNo 对应库存操作内容不一致"); } - private void verifyExistingList(List existingList, List itemList, GaoStockRecord.OperationType operationType, String bizId) { - Map recordMap = new HashMap<>(); - for (GaoStockRecord record : existingList) { + private void verifyManualRevokeExisting(List existingList, String sourceRecordId, String remark) { + TimiException.requiredTrue(existingList.size() == 1, "requestNo 对应库存操作内容不一致"); + GaoProductStockRecord existing = existingList.getFirst(); + TimiException.requiredTrue(existing.getOperationType() == GaoProductStockRecord.OperationType.MANUAL_REVOKE + && sourceRecordId.equals(existing.getSourceRecordId()) + && sameText(existing.getRemark(), remark), "requestNo 对应库存操作内容不一致"); + } + + private void verifyExistingList(List existingList, List itemList, GaoProductStockRecord.OperationType operationType, String bizId) { + Map recordMap = new HashMap<>(); + for (GaoProductStockRecord record : existingList) { TimiException.requiredTrue(record.getOperationType() == operationType && bizId.equals(record.getBizId()), "requestNo 已被其他库存操作使用"); recordMap.put(record.getProductId(), record); } TimiException.requiredTrue(recordMap.size() == itemList.size(), "requestNo 对应库存操作内容不一致"); for (GaoStockChangeItem item : itemList) { - GaoStockRecord record = recordMap.get(item.getProductId()); + GaoProductStockRecord record = recordMap.get(item.getProductId()); TimiException.required(record, "requestNo 对应库存操作内容不一致"); TimiException.requiredTrue(item.getBaseQuantity().compareTo(record.getBaseQuantity()) == 0, "requestNo 对应库存操作内容不一致"); } @@ -353,7 +446,7 @@ public class GaoProductStockServiceImplement implements GaoProductStockService { return false; } - private boolean hasReason(String value) { + private boolean hasText(String value) { return value != null && !value.trim().isEmpty(); } diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductUnitServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductUnitServiceImplement.java index 9ec318a..c0d0022 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductUnitServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoProductUnitServiceImplement.java @@ -26,16 +26,13 @@ public class GaoProductUnitServiceImplement implements GaoProductUnitService { @Override public List validateAndNormalize(GaoProduct product, List sourceList) { TimiException.required(product, "not found product"); - String baseUnit = requiredText(product.getBaseUnit(), "基础单位不能为空"); - product.setBaseUnit(baseUnit); - List inputList = sourceList == null || sourceList.isEmpty() - ? List.of(createBaseUnit(product)) : sourceList; + TimiException.requiredTrue(sourceList != null && !sourceList.isEmpty(), "产品至少需要一个单位"); List result = new ArrayList<>(); Set nameSet = new HashSet<>(); Set idSet = new HashSet<>(); - GaoProductUnit base = null; - for (int i = 0; i < inputList.size(); i++) { - GaoProductUnit source = inputList.get(i); + int baseCount = 0; + for (int i = 0; i < sourceList.size(); i++) { + GaoProductUnit source = sourceList.get(i); TimiException.required(source, "产品单位不能为空"); GaoProductUnit unit = new GaoProductUnit(); unit.setId(source.getId()); @@ -53,14 +50,13 @@ public class GaoProductUnitServiceImplement implements GaoProductUnitService { unit.setAllowSale(TimiJava.defaultIfNull(source.getAllowSale(), true)); unit.setSort(TimiJava.defaultIfNull(source.getSort(), i)); if (unit.getIsBase()) { - TimiException.requiredNull(base, "一个产品只能有一个基础单位"); + baseCount++; + TimiException.requiredTrue(1 >= baseCount, "一个产品只能有一个基础单位"); TimiException.requiredTrue(unit.getBaseQuantityPerUnit().compareTo(ONE) == 0, "基础单位数量系数必须为 1"); - TimiException.requiredTrue(baseUnit.equals(unit.getName()), "基础单位名称必须与产品基础单位一致"); - base = unit; } result.add(unit); } - TimiException.required(base, "产品必须有一个基础单位"); + TimiException.requiredTrue(0 < baseCount, "产品必须有一个基础单位"); TimiException.requiredTrue(product.getProductType() != GaoProduct.ProductType.SERVICE || !Boolean.TRUE.equals(product.getStockEnabled()), "SERVICE 产品不能启用库存"); return result; } @@ -78,17 +74,6 @@ public class GaoProductUnitServiceImplement implements GaoProductUnitService { } } - private GaoProductUnit createBaseUnit(GaoProduct product) { - GaoProductUnit unit = new GaoProductUnit(); - unit.setName(product.getBaseUnit()); - unit.setBaseQuantityPerUnit(ONE); - unit.setIsBase(true); - unit.setAllowPurchase(Boolean.TRUE.equals(product.getStockEnabled())); - unit.setAllowSale(true); - unit.setSort(0); - return unit; - } - private String requiredText(String value, String message) { TimiException.required(value, message); String result = value.trim(); diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreServiceImplement.java index 48cdabc..64899a9 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreServiceImplement.java @@ -260,7 +260,7 @@ public class GaoStoreServiceImplement extends AbstractEntityService attachmentList; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoStockRecordPage.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoProductStockRecordPage.java similarity index 64% rename from src/main/java/com/imyeyu/api/modules/gao/vo/GaoStockRecordPage.java rename to src/main/java/com/imyeyu/api/modules/gao/vo/GaoProductStockRecordPage.java index 5efdb2e..7e68124 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoStockRecordPage.java +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoProductStockRecordPage.java @@ -1,6 +1,6 @@ package com.imyeyu.api.modules.gao.vo; -import com.imyeyu.api.modules.gao.entity.GaoStockRecord; +import com.imyeyu.api.modules.gao.entity.GaoProductStockRecord; import com.imyeyu.java.bean.BasePage; import lombok.Data; import lombok.EqualsAndHashCode; @@ -11,7 +11,7 @@ import lombok.EqualsAndHashCode; /// @since 2026-09-02 @Data @EqualsAndHashCode(callSuper = true) -public class GaoStockRecordPage extends BasePage { +public class GaoProductStockRecordPage extends BasePage { /// 门店 ID private String storeId; @@ -19,14 +19,17 @@ public class GaoStockRecordPage extends BasePage { /// 产品 ID private String productId; + /// 产品名称或编码关键词 + private String keyword; + /// 来源业务类型 - private GaoStockRecord.BizType bizType; + private GaoProductStockRecord.BizType bizType; /// 来源业务 ID private String bizId; /// 操作类型 - private GaoStockRecord.OperationType operationType; + private GaoProductStockRecord.OperationType operationType; /// 起始发生时间 private Long beginAt; diff --git a/src/main/resources/db/migration/timiserver/V60__remove_gao_product_base_unit.sql b/src/main/resources/db/migration/timiserver/V60__remove_gao_product_base_unit.sql new file mode 100644 index 0000000..67ba316 --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V60__remove_gao_product_base_unit.sql @@ -0,0 +1,2 @@ +ALTER TABLE `gao_product` + DROP COLUMN `base_unit`; diff --git a/src/main/resources/db/migration/timiserver/V61__remove_gao_product_reference_cost.sql b/src/main/resources/db/migration/timiserver/V61__remove_gao_product_reference_cost.sql new file mode 100644 index 0000000..c234533 --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V61__remove_gao_product_reference_cost.sql @@ -0,0 +1,2 @@ +ALTER TABLE `gao_product` + DROP COLUMN `reference_cost`; diff --git a/src/main/resources/db/migration/timiserver/V62__rename_gao_stock_record_and_remove_reason.sql b/src/main/resources/db/migration/timiserver/V62__rename_gao_stock_record_and_remove_reason.sql new file mode 100644 index 0000000..fef9b83 --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V62__rename_gao_stock_record_and_remove_reason.sql @@ -0,0 +1,12 @@ +RENAME TABLE `gao_stock_record` TO `gao_product_stock_record`; + +ALTER TABLE `gao_product_stock_record` + DROP KEY `uk_gao_stock_record_request_product`, + DROP KEY `uk_gao_stock_record_source`, + DROP KEY `idx_gao_stock_record_product_time`, + DROP KEY `idx_gao_stock_record_biz_time`, + DROP COLUMN `reason`, + ADD UNIQUE KEY `uk_gao_product_stock_record_request_product` (`store_id`, `request_no`, `operation_type`, `product_id`), + ADD UNIQUE KEY `uk_gao_product_stock_record_source` (`source_record_id`), + ADD KEY `idx_gao_product_stock_record_product_time` (`store_id`, `product_id`, `occurred_at`, `id`), + ADD KEY `idx_gao_product_stock_record_biz_time` (`store_id`, `biz_type`, `biz_id`, `occurred_at`, `id`); diff --git a/src/main/resources/mapper/timi-server/gao/GaoStockRecordMapper.xml b/src/main/resources/mapper/timi-server/gao/GaoProductStockRecordMapper.xml similarity index 53% rename from src/main/resources/mapper/timi-server/gao/GaoStockRecordMapper.xml rename to src/main/resources/mapper/timi-server/gao/GaoProductStockRecordMapper.xml index 3e26e41..6f54333 100644 --- a/src/main/resources/mapper/timi-server/gao/GaoStockRecordMapper.xml +++ b/src/main/resources/mapper/timi-server/gao/GaoProductStockRecordMapper.xml @@ -1,18 +1,28 @@ - - + SELECT * FROM `gao_product_stock_record` WHERE `store_id` = #{storeId} AND `request_no` = #{requestNo} ORDER BY `product_id`, `id` - + SELECT * FROM `gao_product_stock_record` + WHERE `id` = #{id} + FOR UPDATE + + + - + SELECT * FROM `gao_product_stock_record` WHERE `biz_type` = #{bizType} AND `biz_id` = #{bizId} AND `product_id` = #{productId} AND `operation_type` = #{operationType} ORDER BY `occurred_at`, `id` LIMIT 1 @@ -21,6 +31,7 @@ 1 = 1 AND r.`store_id` = #{storeId} AND r.`product_id` = #{productId} + AND (r.`product_name_snapshot` LIKE CONCAT('%', #{keyword}, '%') OR p.`name` LIKE CONCAT('%', #{keyword}, '%') OR p.`code` LIKE CONCAT('%', #{keyword}, '%')) AND r.`biz_type` = #{bizType} AND r.`biz_id` = #{bizId} AND r.`operation_type` = #{operationType} @@ -28,10 +39,10 @@ AND r.`occurred_at` <= #{endAt} - + SELECT r.* FROM `gao_product_stock_record` r LEFT JOIN `gao_product` p ON p.`id` = r.`product_id` AND p.`store_id` = r.`store_id` WHERE ORDER BY r.`occurred_at` DESC, r.`id` DESC LIMIT #{offset}, #{limit}