v1.0.31 #43
@@ -75,6 +75,8 @@ public enum GaoPermissionCode implements BuiltinPermissionCode {
|
|||||||
|
|
||||||
PRODUCT_STOCK_CREATE("PRODUCT_STOCK:CREATE", "产品库存操作", true, true),
|
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_CREATE("SALES_ORDER:CREATE", "销售订单创建", true, true),
|
||||||
|
|
||||||
SALES_ORDER_READ("SALES_ORDER:READ", "销售订单读取", true, true),
|
SALES_ORDER_READ("SALES_ORDER:READ", "销售订单读取", true, true),
|
||||||
|
|||||||
@@ -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.annotation.RequireGaoPermission;
|
||||||
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
|
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
|
||||||
import com.imyeyu.api.modules.gao.entity.GaoProductStock;
|
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.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.service.GaoStoreScopeService;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoProductStockChangeRequest;
|
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.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.java.bean.timi.TimiException;
|
||||||
import com.imyeyu.spring.annotation.AOPLog;
|
import com.imyeyu.spring.annotation.AOPLog;
|
||||||
import com.imyeyu.spring.annotation.RequestRateLimit;
|
import com.imyeyu.spring.annotation.RequestRateLimit;
|
||||||
@@ -31,7 +32,7 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
public class GaoProductStockController {
|
public class GaoProductStockController {
|
||||||
|
|
||||||
private final GaoProductStockService stockService;
|
private final GaoProductStockService stockService;
|
||||||
private final GaoStockRecordService recordService;
|
private final GaoProductStockRecordService recordService;
|
||||||
private final GaoStoreScopeService storeScopeService;
|
private final GaoStoreScopeService storeScopeService;
|
||||||
|
|
||||||
@AOPLog
|
@AOPLog
|
||||||
@@ -76,6 +77,14 @@ public class GaoProductStockController {
|
|||||||
return stockService.manualOut(normalize(request));
|
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
|
@AOPLog
|
||||||
@RequestRateLimit
|
@RequestRateLimit
|
||||||
@RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_CREATE)
|
@RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_CREATE)
|
||||||
@@ -96,12 +105,22 @@ public class GaoProductStockController {
|
|||||||
@RequestRateLimit
|
@RequestRateLimit
|
||||||
@RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_READ)
|
@RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_READ)
|
||||||
@PostMapping("/record/list")
|
@PostMapping("/record/list")
|
||||||
public PageResult<GaoStockRecord> recordList(@RequestBody GaoStockRecordPage page) {
|
public PageResult<GaoProductStockRecord> recordList(@RequestBody GaoProductStockRecordPage page) {
|
||||||
TimiException.required(page, "not found stock record page");
|
TimiException.required(page, "not found stock record page");
|
||||||
page.setStoreId(storeScopeService.resolveStoreId(page.getStoreId()));
|
page.setStoreId(storeScopeService.resolveStoreId(page.getStoreId()));
|
||||||
return recordService.pageByQuery(page);
|
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) {
|
private GaoProductStockChangeRequest normalize(GaoProductStockChangeRequest request) {
|
||||||
TimiException.required(request, "not found stock change request");
|
TimiException.required(request, "not found stock change request");
|
||||||
request.setStoreId(storeScopeService.resolveStoreId(request.getStoreId()));
|
request.setStoreId(storeScopeService.resolveStoreId(request.getStoreId()));
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ public class GaoOrderFulfillment extends UUIDEntity {
|
|||||||
|
|
||||||
/// 库存处理流水
|
/// 库存处理流水
|
||||||
@Transient
|
@Transient
|
||||||
private List<GaoStockRecord> stockRecordList;
|
private List<GaoProductStockRecord> stockRecordList;
|
||||||
|
|
||||||
/// 附件
|
/// 附件
|
||||||
@Transient
|
@Transient
|
||||||
|
|||||||
@@ -65,9 +65,6 @@ public class GaoProduct extends UUIDEntity {
|
|||||||
/// 产品类型
|
/// 产品类型
|
||||||
private ProductType productType;
|
private ProductType productType;
|
||||||
|
|
||||||
/// 基础库存单位名称
|
|
||||||
private String baseUnit;
|
|
||||||
|
|
||||||
/// true 为启用库存管理
|
/// true 为启用库存管理
|
||||||
private Boolean stockEnabled;
|
private Boolean stockEnabled;
|
||||||
|
|
||||||
@@ -77,9 +74,6 @@ public class GaoProduct extends UUIDEntity {
|
|||||||
/// 状态
|
/// 状态
|
||||||
private Status status;
|
private Status status;
|
||||||
|
|
||||||
/// 参考成本,单位分
|
|
||||||
private Long referenceCost;
|
|
||||||
|
|
||||||
/// 最低库存数量,基础单位
|
/// 最低库存数量,基础单位
|
||||||
private BigDecimal minStockQuantity;
|
private BigDecimal minStockQuantity;
|
||||||
|
|
||||||
|
|||||||
+8
-4
@@ -17,7 +17,7 @@ import java.util.List;
|
|||||||
/// @author Codex
|
/// @author Codex
|
||||||
/// @since 2026-09-02
|
/// @since 2026-09-02
|
||||||
@Data
|
@Data
|
||||||
public class GaoStockRecord implements IDEntity<String>, Creatable {
|
public class GaoProductStockRecord implements IDEntity<String>, Creatable {
|
||||||
|
|
||||||
/// 库存流水业务类型
|
/// 库存流水业务类型
|
||||||
public enum BizType {
|
public enum BizType {
|
||||||
@@ -41,6 +41,9 @@ public class GaoStockRecord implements IDEntity<String>, Creatable {
|
|||||||
/// 手工出库
|
/// 手工出库
|
||||||
MANUAL_OUT,
|
MANUAL_OUT,
|
||||||
|
|
||||||
|
/// 手工流水冲正
|
||||||
|
MANUAL_REVOKE,
|
||||||
|
|
||||||
/// 履约预占
|
/// 履约预占
|
||||||
ORDER_RESERVE,
|
ORDER_RESERVE,
|
||||||
|
|
||||||
@@ -118,6 +121,10 @@ public class GaoStockRecord implements IDEntity<String>, Creatable {
|
|||||||
/// 冲正来源流水 ID
|
/// 冲正来源流水 ID
|
||||||
private String sourceRecordId;
|
private String sourceRecordId;
|
||||||
|
|
||||||
|
/// 关联的冲正流水 ID
|
||||||
|
@Transient
|
||||||
|
private String revokeRecordId;
|
||||||
|
|
||||||
/// 幂等请求号
|
/// 幂等请求号
|
||||||
private String requestNo;
|
private String requestNo;
|
||||||
|
|
||||||
@@ -127,9 +134,6 @@ public class GaoStockRecord implements IDEntity<String>, Creatable {
|
|||||||
/// 发生时间
|
/// 发生时间
|
||||||
private Long occurredAt;
|
private Long occurredAt;
|
||||||
|
|
||||||
/// 原因
|
|
||||||
private String reason;
|
|
||||||
|
|
||||||
/// 备注
|
/// 备注
|
||||||
private String remark;
|
private String remark;
|
||||||
|
|
||||||
@@ -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<GaoProductStockRecord, String> {
|
||||||
|
|
||||||
|
/// 按门店和请求号查询库存流水
|
||||||
|
List<GaoProductStockRecord> selectByRequestNo(@Param("storeId") String storeId, @Param("requestNo") String requestNo);
|
||||||
|
|
||||||
|
/// 锁定库存流水
|
||||||
|
GaoProductStockRecord selectForUpdate(@Param("id") String id);
|
||||||
|
|
||||||
|
/// 查询库存冲正流水
|
||||||
|
GaoProductStockRecord selectRevokeBySourceRecordId(@Param("sourceRecordId") String sourceRecordId);
|
||||||
|
|
||||||
|
/// 按业务查询库存流水
|
||||||
|
List<GaoProductStockRecord> 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<GaoProductStockRecord> selectByQuery(GaoProductStockRecordPage page);
|
||||||
|
}
|
||||||
@@ -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<GaoStockRecord, String> {
|
|
||||||
|
|
||||||
/// 按门店和请求号查询库存流水
|
|
||||||
List<GaoStockRecord> selectByRequestNo(@Param("storeId") String storeId, @Param("requestNo") String requestNo);
|
|
||||||
|
|
||||||
/// 按业务查询库存流水
|
|
||||||
List<GaoStockRecord> 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<GaoStockRecord> selectByQuery(GaoStockRecordPage page);
|
|
||||||
}
|
|
||||||
@@ -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<GaoProductStockRecord, String> {
|
||||||
|
|
||||||
|
/// 按条件查询库存流水
|
||||||
|
PageResult<GaoProductStockRecord> pageByQuery(GaoProductStockRecordPage page);
|
||||||
|
|
||||||
|
/// 查询库存流水详情
|
||||||
|
GaoProductStockRecord detail(String id);
|
||||||
|
|
||||||
|
/// 按来源业务查询库存流水
|
||||||
|
List<GaoProductStockRecord> listByBiz(GaoProductStockRecord.BizType bizType, String bizId);
|
||||||
|
|
||||||
|
/// 按门店和请求号查询库存流水
|
||||||
|
List<GaoProductStockRecord> 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<Attachment> attachmentList);
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package com.imyeyu.api.modules.gao.service;
|
package com.imyeyu.api.modules.gao.service;
|
||||||
|
|
||||||
import com.imyeyu.api.modules.gao.entity.GaoProductStock;
|
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.GaoProductStockChangeRequest;
|
||||||
|
import com.imyeyu.api.modules.gao.vo.GaoProductStockManualRevokeRequest;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoProductStockPage;
|
import com.imyeyu.api.modules.gao.vo.GaoProductStockPage;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoStockChangeItem;
|
import com.imyeyu.api.modules.gao.vo.GaoStockChangeItem;
|
||||||
import com.imyeyu.spring.bean.PageResult;
|
import com.imyeyu.spring.bean.PageResult;
|
||||||
@@ -30,6 +30,9 @@ public interface GaoProductStockService {
|
|||||||
/// 手工出库
|
/// 手工出库
|
||||||
GaoProductStock manualOut(GaoProductStockChangeRequest request);
|
GaoProductStock manualOut(GaoProductStockChangeRequest request);
|
||||||
|
|
||||||
|
/// 手工库存流水冲正
|
||||||
|
GaoProductStock manualRevoke(GaoProductStockManualRevokeRequest request);
|
||||||
|
|
||||||
/// 盘盈
|
/// 盘盈
|
||||||
GaoProductStock stocktakeGain(GaoProductStockChangeRequest request);
|
GaoProductStock stocktakeGain(GaoProductStockChangeRequest request);
|
||||||
|
|
||||||
|
|||||||
@@ -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<GaoStockRecord, String> {
|
|
||||||
|
|
||||||
/// 按条件查询库存流水
|
|
||||||
PageResult<GaoStockRecord> pageByQuery(GaoStockRecordPage page);
|
|
||||||
|
|
||||||
/// 按来源业务查询库存流水
|
|
||||||
List<GaoStockRecord> listByBiz(GaoStockRecord.BizType bizType, String bizId);
|
|
||||||
|
|
||||||
/// 按门店和请求号查询库存流水
|
|
||||||
List<GaoStockRecord> listByRequestNo(String storeId, String requestNo);
|
|
||||||
|
|
||||||
/// 查询指定业务和产品的库存流水
|
|
||||||
GaoStockRecord getByBizProduct(GaoStockRecord.BizType bizType, String bizId, String productId,
|
|
||||||
GaoStockRecord.OperationType operationType);
|
|
||||||
|
|
||||||
/// 保存库存操作流水及附件
|
|
||||||
void createOperationRecord(GaoStockRecord record, List<Attachment> attachmentList);
|
|
||||||
}
|
|
||||||
+6
-6
@@ -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.GaoProductUnit;
|
||||||
import com.imyeyu.api.modules.gao.entity.GaoSalesOrder;
|
import com.imyeyu.api.modules.gao.entity.GaoSalesOrder;
|
||||||
import com.imyeyu.api.modules.gao.entity.GaoSalesOrderItem;
|
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.GaoOrderFulfillment;
|
||||||
import com.imyeyu.api.modules.gao.entity.GaoOrderFulfillmentItem;
|
import com.imyeyu.api.modules.gao.entity.GaoOrderFulfillmentItem;
|
||||||
import com.imyeyu.api.modules.gao.mapper.GaoOrderFulfillmentItemMapper;
|
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.GaoProductStockService;
|
||||||
import com.imyeyu.api.modules.gao.service.GaoProductUnitService;
|
import com.imyeyu.api.modules.gao.service.GaoProductUnitService;
|
||||||
import com.imyeyu.api.modules.gao.service.GaoSalesOrderService;
|
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.service.GaoStoreScopeService;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoOrderFulfillmentActionRequest;
|
import com.imyeyu.api.modules.gao.vo.GaoOrderFulfillmentActionRequest;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoOrderFulfillmentCreateRequest;
|
import com.imyeyu.api.modules.gao.vo.GaoOrderFulfillmentCreateRequest;
|
||||||
@@ -74,7 +74,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
|
|||||||
private final GaoProductService productService;
|
private final GaoProductService productService;
|
||||||
private final GaoProductUnitService unitService;
|
private final GaoProductUnitService unitService;
|
||||||
private final GaoProductStockService productStockService;
|
private final GaoProductStockService productStockService;
|
||||||
private final GaoStockRecordService stockRecordService;
|
private final GaoProductStockRecordService stockRecordService;
|
||||||
private final GaoStoreScopeService storeScopeService;
|
private final GaoStoreScopeService storeScopeService;
|
||||||
private final RoleChecker roleChecker;
|
private final RoleChecker roleChecker;
|
||||||
private final UserLoginService userLoginService;
|
private final UserLoginService userLoginService;
|
||||||
@@ -127,7 +127,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
|
|||||||
TimiException.required(fulfillment, "not found fulfillment");
|
TimiException.required(fulfillment, "not found fulfillment");
|
||||||
storeScopeService.checkStoreId(fulfillment.getStoreId());
|
storeScopeService.checkStoreId(fulfillment.getStoreId());
|
||||||
fulfillment.setItemList(itemMapper.selectByFulfillmentId(fulfillment.getId()));
|
fulfillment.setItemList(itemMapper.selectByFulfillmentId(fulfillment.getId()));
|
||||||
fulfillment.setStockRecordList(stockRecordService.listByBiz(GaoStockRecord.BizType.ORDER_FULFILLMENT, fulfillment.getId()));
|
fulfillment.setStockRecordList(stockRecordService.listByBiz(GaoProductStockRecord.BizType.ORDER_FULFILLMENT, fulfillment.getId()));
|
||||||
return fulfillment;
|
return fulfillment;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,8 +351,8 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
|
|||||||
}
|
}
|
||||||
if (!stockItems.isEmpty()) {
|
if (!stockItems.isEmpty()) {
|
||||||
for (GaoStockChangeItem item : stockItems) {
|
for (GaoStockChangeItem item : stockItems) {
|
||||||
GaoStockRecord stockRecord = stockRecordService.getByBizProduct(GaoStockRecord.BizType.ORDER_FULFILLMENT,
|
GaoProductStockRecord stockRecord = stockRecordService.getByBizProduct(GaoProductStockRecord.BizType.ORDER_FULFILLMENT,
|
||||||
source.getId(), item.getProductId(), GaoStockRecord.OperationType.ORDER_OUT);
|
source.getId(), item.getProductId(), GaoProductStockRecord.OperationType.ORDER_OUT);
|
||||||
if (stockRecord != null) {
|
if (stockRecord != null) {
|
||||||
item.setSourceRecordId(stockRecord.getId());
|
item.setSourceRecordId(stockRecord.getId());
|
||||||
}
|
}
|
||||||
|
|||||||
-11
@@ -139,11 +139,9 @@ public class GaoProductServiceImplement extends AbstractEntityService<GaoProduct
|
|||||||
product.setCode(TimiJava.defaultIfNull(request.getCode(), product.getCode()));
|
product.setCode(TimiJava.defaultIfNull(request.getCode(), product.getCode()));
|
||||||
product.setName(TimiJava.defaultIfNull(request.getName(), product.getName()));
|
product.setName(TimiJava.defaultIfNull(request.getName(), product.getName()));
|
||||||
product.setProductType(TimiJava.defaultIfNull(request.getProductType(), product.getProductType()));
|
product.setProductType(TimiJava.defaultIfNull(request.getProductType(), product.getProductType()));
|
||||||
product.setBaseUnit(TimiJava.defaultIfNull(request.getBaseUnit(), product.getBaseUnit()));
|
|
||||||
product.setStockEnabled(TimiJava.defaultIfNull(request.getStockEnabled(), product.getStockEnabled()));
|
product.setStockEnabled(TimiJava.defaultIfNull(request.getStockEnabled(), product.getStockEnabled()));
|
||||||
product.setAllowDecimalQuantity(TimiJava.defaultIfNull(request.getAllowDecimalQuantity(), product.getAllowDecimalQuantity()));
|
product.setAllowDecimalQuantity(TimiJava.defaultIfNull(request.getAllowDecimalQuantity(), product.getAllowDecimalQuantity()));
|
||||||
product.setStatus(TimiJava.defaultIfNull(request.getStatus(), product.getStatus()));
|
product.setStatus(TimiJava.defaultIfNull(request.getStatus(), product.getStatus()));
|
||||||
product.setReferenceCost(request.getReferenceCost());
|
|
||||||
product.setMinStockQuantity(request.getMinStockQuantity());
|
product.setMinStockQuantity(request.getMinStockQuantity());
|
||||||
product.setSort(TimiJava.defaultIfNull(request.getSort(), product.getSort()));
|
product.setSort(TimiJava.defaultIfNull(request.getSort(), product.getSort()));
|
||||||
product.setRemark(request.getRemark());
|
product.setRemark(request.getRemark());
|
||||||
@@ -191,12 +189,10 @@ public class GaoProductServiceImplement extends AbstractEntityService<GaoProduct
|
|||||||
product.setCode(requiredText(product.getCode(), "产品编码不能为空"));
|
product.setCode(requiredText(product.getCode(), "产品编码不能为空"));
|
||||||
product.setName(requiredText(product.getName(), "产品名称不能为空"));
|
product.setName(requiredText(product.getName(), "产品名称不能为空"));
|
||||||
product.setProductType(TimiJava.defaultIfNull(product.getProductType(), GaoProduct.ProductType.GOODS));
|
product.setProductType(TimiJava.defaultIfNull(product.getProductType(), GaoProduct.ProductType.GOODS));
|
||||||
product.setBaseUnit(requiredText(product.getBaseUnit(), "基础单位不能为空"));
|
|
||||||
product.setStockEnabled(TimiJava.defaultIfNull(product.getStockEnabled(), product.getProductType() != GaoProduct.ProductType.SERVICE));
|
product.setStockEnabled(TimiJava.defaultIfNull(product.getStockEnabled(), product.getProductType() != GaoProduct.ProductType.SERVICE));
|
||||||
TimiException.requiredTrue(product.getProductType() != GaoProduct.ProductType.SERVICE || !product.getStockEnabled(), "SERVICE 产品不能启用库存");
|
TimiException.requiredTrue(product.getProductType() != GaoProduct.ProductType.SERVICE || !product.getStockEnabled(), "SERVICE 产品不能启用库存");
|
||||||
product.setAllowDecimalQuantity(TimiJava.defaultIfNull(product.getAllowDecimalQuantity(), false));
|
product.setAllowDecimalQuantity(TimiJava.defaultIfNull(product.getAllowDecimalQuantity(), false));
|
||||||
product.setStatus(TimiJava.defaultIfNull(product.getStatus(), GaoProduct.Status.ACTIVE));
|
product.setStatus(TimiJava.defaultIfNull(product.getStatus(), GaoProduct.Status.ACTIVE));
|
||||||
product.setReferenceCost(nonNegative(product.getReferenceCost(), "参考成本不能为负数"));
|
|
||||||
if (!Boolean.TRUE.equals(product.getStockEnabled())) {
|
if (!Boolean.TRUE.equals(product.getStockEnabled())) {
|
||||||
TimiException.requiredNull(product.getMinStockQuantity(), "未启用库存的产品不能填写最低库存");
|
TimiException.requiredNull(product.getMinStockQuantity(), "未启用库存的产品不能填写最低库存");
|
||||||
}
|
}
|
||||||
@@ -267,13 +263,6 @@ public class GaoProductServiceImplement extends AbstractEntityService<GaoProduct
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Long nonNegative(Long value, String message) {
|
|
||||||
if (value != null) {
|
|
||||||
TimiException.requiredTrue(0 <= value, message);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String requiredText(String value, String message) {
|
private String requiredText(String value, String message) {
|
||||||
TimiException.required(value, message);
|
TimiException.required(value, message);
|
||||||
String result = value.trim();
|
String result = value.trim();
|
||||||
|
|||||||
+44
-21
@@ -2,10 +2,10 @@ package com.imyeyu.api.modules.gao.service.implement;
|
|||||||
|
|
||||||
import com.imyeyu.api.modules.common.entity.Attachment;
|
import com.imyeyu.api.modules.common.entity.Attachment;
|
||||||
import com.imyeyu.api.modules.common.service.AttachmentService;
|
import com.imyeyu.api.modules.common.service.AttachmentService;
|
||||||
import com.imyeyu.api.modules.gao.entity.GaoStockRecord;
|
import com.imyeyu.api.modules.gao.entity.GaoProductStockRecord;
|
||||||
import com.imyeyu.api.modules.gao.mapper.GaoStockRecordMapper;
|
import com.imyeyu.api.modules.gao.mapper.GaoProductStockRecordMapper;
|
||||||
import com.imyeyu.api.modules.gao.service.GaoStockRecordService;
|
import com.imyeyu.api.modules.gao.service.GaoProductStockRecordService;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoStockRecordPage;
|
import com.imyeyu.api.modules.gao.vo.GaoProductStockRecordPage;
|
||||||
import com.imyeyu.api.modules.user.entity.User;
|
import com.imyeyu.api.modules.user.entity.User;
|
||||||
import com.imyeyu.api.modules.user.service.UserService;
|
import com.imyeyu.api.modules.user.service.UserService;
|
||||||
import com.imyeyu.java.bean.timi.TimiCode;
|
import com.imyeyu.java.bean.timi.TimiCode;
|
||||||
@@ -25,21 +25,21 @@ import java.util.Map;
|
|||||||
/// @since 2026-09-02
|
/// @since 2026-09-02
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class GaoStockRecordServiceImplement extends AbstractEntityService<GaoStockRecord, String> implements GaoStockRecordService {
|
public class GaoProductStockRecordServiceImplement extends AbstractEntityService<GaoProductStockRecord, String> implements GaoProductStockRecordService {
|
||||||
|
|
||||||
private final GaoStockRecordMapper mapper;
|
private final GaoProductStockRecordMapper mapper;
|
||||||
private final AttachmentService attachmentService;
|
private final AttachmentService attachmentService;
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected BaseMapper<GaoStockRecord, String> mapper() {
|
protected BaseMapper<GaoProductStockRecord, String> mapper() {
|
||||||
return mapper;
|
return mapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageResult<GaoStockRecord> pageByQuery(GaoStockRecordPage page) {
|
public PageResult<GaoProductStockRecord> pageByQuery(GaoProductStockRecordPage page) {
|
||||||
TimiException.required(page, "not found stock record page");
|
TimiException.required(page, "not found stock record page");
|
||||||
PageResult<GaoStockRecord> result = new PageResult<>();
|
PageResult<GaoProductStockRecord> result = new PageResult<>();
|
||||||
result.setTotal(mapper.countByQuery(page));
|
result.setTotal(mapper.countByQuery(page));
|
||||||
result.setList(mapper.selectByQuery(page));
|
result.setList(mapper.selectByQuery(page));
|
||||||
fillTransient(result.getList());
|
fillTransient(result.getList());
|
||||||
@@ -47,24 +47,47 @@ public class GaoStockRecordServiceImplement extends AbstractEntityService<GaoSto
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<GaoStockRecord> 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<GaoProductStockRecord> listByBiz(GaoProductStockRecord.BizType bizType, String bizId) {
|
||||||
TimiException.required(bizType, "not found stock bizType");
|
TimiException.required(bizType, "not found stock bizType");
|
||||||
TimiException.required(bizId, "not found stock bizId");
|
TimiException.required(bizId, "not found stock bizId");
|
||||||
List<GaoStockRecord> result = mapper.selectByBiz(bizType, bizId);
|
List<GaoProductStockRecord> result = mapper.selectByBiz(bizType, bizId);
|
||||||
fillTransient(result);
|
fillTransient(result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<GaoStockRecord> listByRequestNo(String storeId, String requestNo) {
|
public List<GaoProductStockRecord> listByRequestNo(String storeId, String requestNo) {
|
||||||
TimiException.required(storeId, "not found storeId");
|
TimiException.required(storeId, "not found storeId");
|
||||||
TimiException.required(requestNo, "not found requestNo");
|
TimiException.required(requestNo, "not found requestNo");
|
||||||
return mapper.selectByRequestNo(storeId, requestNo);
|
return mapper.selectByRequestNo(storeId, requestNo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GaoStockRecord getByBizProduct(GaoStockRecord.BizType bizType, String bizId, String productId,
|
public GaoProductStockRecord getForUpdate(String id) {
|
||||||
GaoStockRecord.OperationType operationType) {
|
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(bizType, "not found stock bizType");
|
||||||
TimiException.required(bizId, "not found stock bizId");
|
TimiException.required(bizId, "not found stock bizId");
|
||||||
TimiException.required(productId, "not found productId");
|
TimiException.required(productId, "not found productId");
|
||||||
@@ -73,7 +96,7 @@ public class GaoStockRecordServiceImplement extends AbstractEntityService<GaoSto
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createOperationRecord(GaoStockRecord record, List<Attachment> attachmentList) {
|
public void createOperationRecord(GaoProductStockRecord record, List<Attachment> attachmentList) {
|
||||||
TimiException.required(record, "not found stock record");
|
TimiException.required(record, "not found stock record");
|
||||||
mapper.insert(record);
|
mapper.insert(record);
|
||||||
if (attachmentList != null && !attachmentList.isEmpty()) {
|
if (attachmentList != null && !attachmentList.isEmpty()) {
|
||||||
@@ -82,12 +105,12 @@ public class GaoStockRecordServiceImplement extends AbstractEntityService<GaoSto
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void create(GaoStockRecord record) {
|
public void create(GaoProductStockRecord record) {
|
||||||
throw new TimiException(TimiCode.ERROR, "库存流水必须通过库存操作创建");
|
throw new TimiException(TimiCode.ERROR, "库存流水必须通过库存操作创建");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update(GaoStockRecord record) {
|
public void update(GaoProductStockRecord record) {
|
||||||
throw new TimiException(TimiCode.ERROR, "库存流水不允许修改");
|
throw new TimiException(TimiCode.ERROR, "库存流水不允许修改");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,16 +124,16 @@ public class GaoStockRecordServiceImplement extends AbstractEntityService<GaoSto
|
|||||||
throw new TimiException(TimiCode.ERROR, "库存流水不允许物理删除");
|
throw new TimiException(TimiCode.ERROR, "库存流水不允许物理删除");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillTransient(List<GaoStockRecord> recordList) {
|
private void fillTransient(List<GaoProductStockRecord> recordList) {
|
||||||
if (recordList == null || recordList.isEmpty()) {
|
if (recordList == null || recordList.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
List<String> idList = recordList.stream().map(GaoStockRecord::getId).toList();
|
List<String> idList = recordList.stream().map(GaoProductStockRecord::getId).toList();
|
||||||
Map<String, List<Attachment>> attachmentMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_STOCK_RECORD, idList);
|
Map<String, List<Attachment>> attachmentMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_STOCK_RECORD, idList);
|
||||||
List<String> userIdList = recordList.stream().map(GaoStockRecord::getOperatorUserId)
|
List<String> userIdList = recordList.stream().map(GaoProductStockRecord::getOperatorUserId)
|
||||||
.filter(value -> value != null && !value.isBlank()).distinct().toList();
|
.filter(value -> value != null && !value.isBlank()).distinct().toList();
|
||||||
Map<String, User> userMap = userIdList.isEmpty() ? Map.of() : userService.mapByIdList(userIdList);
|
Map<String, User> 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.setAttachmentList(attachmentMap.getOrDefault(record.getId(), List.of()));
|
||||||
record.setOperator(userMap.get(record.getOperatorUserId()));
|
record.setOperator(userMap.get(record.getOperatorUserId()));
|
||||||
}
|
}
|
||||||
+133
-40
@@ -2,17 +2,19 @@ package com.imyeyu.api.modules.gao.service.implement;
|
|||||||
|
|
||||||
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
|
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
|
||||||
import com.imyeyu.api.modules.common.entity.Attachment;
|
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.GaoProduct;
|
||||||
import com.imyeyu.api.modules.gao.entity.GaoProductStock;
|
import com.imyeyu.api.modules.gao.entity.GaoProductStock;
|
||||||
import com.imyeyu.api.modules.gao.entity.GaoProductUnit;
|
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.mapper.GaoProductStockMapper;
|
||||||
import com.imyeyu.api.modules.gao.service.GaoProductService;
|
import com.imyeyu.api.modules.gao.service.GaoProductService;
|
||||||
import com.imyeyu.api.modules.gao.service.GaoProductStockService;
|
import com.imyeyu.api.modules.gao.service.GaoProductStockService;
|
||||||
import com.imyeyu.api.modules.gao.service.GaoProductUnitService;
|
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.service.GaoStoreScopeService;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoProductStockChangeRequest;
|
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.GaoProductStockPage;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoProductUnitSelection;
|
import com.imyeyu.api.modules.gao.vo.GaoProductUnitSelection;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoStockChangeItem;
|
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 static final BigDecimal ZERO = BigDecimal.ZERO;
|
||||||
|
|
||||||
private final GaoProductStockMapper stockMapper;
|
private final GaoProductStockMapper stockMapper;
|
||||||
private final GaoStockRecordService recordService;
|
private final AttachmentService attachmentService;
|
||||||
|
private final GaoProductStockRecordService recordService;
|
||||||
private final GaoProductService productService;
|
private final GaoProductService productService;
|
||||||
private final GaoProductUnitService unitService;
|
private final GaoProductUnitService unitService;
|
||||||
private final GaoStoreScopeService storeScopeService;
|
private final GaoStoreScopeService storeScopeService;
|
||||||
@@ -62,8 +65,17 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
|||||||
PageResult<GaoProductStock> result = new PageResult<>();
|
PageResult<GaoProductStock> result = new PageResult<>();
|
||||||
result.setTotal(stockMapper.countByQuery(page));
|
result.setTotal(stockMapper.countByQuery(page));
|
||||||
List<GaoProductStock> list = stockMapper.selectByQuery(page);
|
List<GaoProductStock> list = stockMapper.selectByQuery(page);
|
||||||
|
Map<String, Attachment> 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) {
|
for (GaoProductStock stock : list) {
|
||||||
fillBalance(stock);
|
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);
|
result.setList(list);
|
||||||
return result;
|
return result;
|
||||||
@@ -93,31 +105,37 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
|||||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||||
@Override
|
@Override
|
||||||
public GaoProductStock initialIn(GaoProductStockChangeRequest request) {
|
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)
|
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||||
@Override
|
@Override
|
||||||
public GaoProductStock manualIn(GaoProductStockChangeRequest request) {
|
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)
|
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||||
@Override
|
@Override
|
||||||
public GaoProductStock manualOut(GaoProductStockChangeRequest request) {
|
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)
|
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||||
@Override
|
@Override
|
||||||
public GaoProductStock stocktakeGain(GaoProductStockChangeRequest request) {
|
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)
|
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||||
@Override
|
@Override
|
||||||
public GaoProductStock stocktakeLoss(GaoProductStockChangeRequest request) {
|
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
|
@Override
|
||||||
@@ -141,62 +159,130 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
|||||||
@Override
|
@Override
|
||||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||||
public List<GaoProductStock> reserveForFulfillment(String storeId, String bizId, List<GaoStockChangeItem> items, String requestNo, String operatorUserId) {
|
public List<GaoProductStock> reserveForFulfillment(String storeId, String bizId, List<GaoStockChangeItem> 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
|
@Override
|
||||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||||
public List<GaoProductStock> releaseForFulfillment(String storeId, String bizId, List<GaoStockChangeItem> items, String requestNo, String operatorUserId) {
|
public List<GaoProductStock> releaseForFulfillment(String storeId, String bizId, List<GaoStockChangeItem> 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
|
@Override
|
||||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||||
public List<GaoProductStock> outForFulfillment(String storeId, String bizId, List<GaoStockChangeItem> items, String requestNo, String operatorUserId) {
|
public List<GaoProductStock> outForFulfillment(String storeId, String bizId, List<GaoStockChangeItem> 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
|
@Override
|
||||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||||
public List<GaoProductStock> revokeForFulfillment(String storeId, String bizId, List<GaoStockChangeItem> items, String requestNo, String operatorUserId) {
|
public List<GaoProductStock> revokeForFulfillment(String storeId, String bizId, List<GaoStockChangeItem> 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");
|
TimiException.required(request, "not found stock change request");
|
||||||
String requestNo = requiredText(request.getRequestNo(), "not found requestNo");
|
String requestNo = requiredText(request.getRequestNo(), "not found requestNo");
|
||||||
String storeId = storeScopeService.resolveStoreId(request.getStoreId());
|
String storeId = storeScopeService.resolveStoreId(request.getStoreId());
|
||||||
TimiException.required(storeId, "not found storeId");
|
TimiException.required(storeId, "not found storeId");
|
||||||
boolean purchase = operationType == GaoStockRecord.OperationType.INITIAL_IN
|
boolean purchase = operationType == GaoProductStockRecord.OperationType.INITIAL_IN
|
||||||
|| operationType == GaoStockRecord.OperationType.MANUAL_IN
|
|| operationType == GaoProductStockRecord.OperationType.MANUAL_IN
|
||||||
|| operationType == GaoStockRecord.OperationType.STOCKTAKE_GAIN;
|
|| operationType == GaoProductStockRecord.OperationType.STOCKTAKE_GAIN;
|
||||||
GaoProductUnitSelection selection = productService.getProductUnit(storeId, request.getProductId(), request.getProductUnitId(), purchase);
|
GaoProductUnitSelection selection = productService.getProductUnit(storeId, request.getProductId(), request.getProductUnitId(), purchase);
|
||||||
GaoProduct product = selection.getProduct();
|
GaoProduct product = selection.getProduct();
|
||||||
GaoProductUnit unit = selection.getUnit();
|
GaoProductUnit unit = selection.getUnit();
|
||||||
TimiException.requiredTrue(Boolean.TRUE.equals(product.getStockEnabled()), "该产品未启用库存管理");
|
TimiException.requiredTrue(Boolean.TRUE.equals(product.getStockEnabled()), "该产品未启用库存管理");
|
||||||
BigDecimal quantity = validateQuantity(product, request.getQuantity());
|
BigDecimal quantity = validateQuantity(product, request.getQuantity());
|
||||||
BigDecimal baseQuantity = unitService.convertQuantityToBase(unit, quantity);
|
BigDecimal baseQuantity = unitService.convertQuantityToBase(unit, quantity);
|
||||||
List<GaoStockRecord> existingList = recordService.listByRequestNo(storeId, requestNo);
|
List<GaoProductStockRecord> existingList = recordService.listByRequestNo(storeId, requestNo);
|
||||||
if (!existingList.isEmpty()) {
|
if (!existingList.isEmpty()) {
|
||||||
GaoStockRecord existing = existingList.getFirst();
|
GaoProductStockRecord existing = existingList.getFirst();
|
||||||
verifyExisting(existing, operationType, product.getId(), unit.getId(), quantity, baseQuantity, request.getRemark(), request.getReason());
|
verifyExisting(existing, operationType, product.getId(), unit.getId(), quantity, baseQuantity, request.getRemark());
|
||||||
return detail(storeId, product.getId());
|
return detail(storeId, product.getId());
|
||||||
}
|
}
|
||||||
TimiException.requiredTrue(hasAttachment(request.getAttachmentList()), "库存操作必须上传照片");
|
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);
|
GaoStockChangeItem item = GaoStockChangeItem.of(product, unit, quantity, baseQuantity);
|
||||||
List<GaoProductStock> result = executeInternal(storeId, product.getId(), List.of(item), requestNo,
|
List<GaoProductStock> 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();
|
return result.getFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<GaoProductStock> executeInternal(String storeId, String bizId, List<GaoStockChangeItem> sourceList, String requestNo,
|
private GaoProductStock manualRevokeInternal(GaoProductStockManualRevokeRequest request) {
|
||||||
String operatorUserId, GaoStockRecord.OperationType operationType, String reason, String remark) {
|
TimiException.required(request, "not found stock manual revoke request");
|
||||||
return executeInternal(storeId, bizId, sourceList, requestNo, operatorUserId, operationType, reason, remark, null);
|
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<GaoProductStockRecord> 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<GaoProductStock> executeInternal(String storeId, String bizId, List<GaoStockChangeItem> sourceList, String requestNo,
|
private List<GaoProductStock> executeInternal(String storeId, String bizId, List<GaoStockChangeItem> sourceList, String requestNo,
|
||||||
String operatorUserId, GaoStockRecord.OperationType operationType, String reason, String remark, List<Attachment> attachmentList) {
|
String operatorUserId, GaoProductStockRecord.OperationType operationType, String remark) {
|
||||||
|
return executeInternal(storeId, bizId, sourceList, requestNo, operatorUserId, operationType, remark, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<GaoProductStock> executeInternal(String storeId, String bizId, List<GaoStockChangeItem> sourceList, String requestNo,
|
||||||
|
String operatorUserId, GaoProductStockRecord.OperationType operationType, String remark, List<Attachment> attachmentList) {
|
||||||
TimiException.required(storeId, "not found storeId");
|
TimiException.required(storeId, "not found storeId");
|
||||||
TimiException.required(bizId, "not found stock bizId");
|
TimiException.required(bizId, "not found stock bizId");
|
||||||
TimiException.requiredTrue(sourceList != null && !sourceList.isEmpty(), "库存变更至少需要一个产品");
|
TimiException.requiredTrue(sourceList != null && !sourceList.isEmpty(), "库存变更至少需要一个产品");
|
||||||
@@ -211,7 +297,7 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
|||||||
TimiException.required(item.getBaseQuantity(), "基础单位数量不能为空");
|
TimiException.required(item.getBaseQuantity(), "基础单位数量不能为空");
|
||||||
TimiException.requiredTrue(0 < item.getBaseQuantity().signum(), "基础单位数量必须大于 0");
|
TimiException.requiredTrue(0 < item.getBaseQuantity().signum(), "基础单位数量必须大于 0");
|
||||||
}
|
}
|
||||||
List<GaoStockRecord> existingList = recordService.listByRequestNo(storeId, normalizedRequestNo);
|
List<GaoProductStockRecord> existingList = recordService.listByRequestNo(storeId, normalizedRequestNo);
|
||||||
if (!existingList.isEmpty()) {
|
if (!existingList.isEmpty()) {
|
||||||
verifyExistingList(existingList, itemList, operationType, bizId);
|
verifyExistingList(existingList, itemList, operationType, bizId);
|
||||||
List<GaoProductStock> result = new ArrayList<>();
|
List<GaoProductStock> result = new ArrayList<>();
|
||||||
@@ -231,7 +317,7 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
|||||||
TimiException.requiredTrue(updated == 1, "库存余额已变化或库存数量不足");
|
TimiException.requiredTrue(updated == 1, "库存余额已变化或库存数量不足");
|
||||||
BigDecimal onHandAfter = onHandBefore.add(onHandDelta);
|
BigDecimal onHandAfter = onHandBefore.add(onHandDelta);
|
||||||
BigDecimal reservedAfter = reservedBefore.add(reservedDelta);
|
BigDecimal reservedAfter = reservedBefore.add(reservedDelta);
|
||||||
GaoStockRecord record = new GaoStockRecord();
|
GaoProductStockRecord record = new GaoProductStockRecord();
|
||||||
record.setStoreId(storeId);
|
record.setStoreId(storeId);
|
||||||
record.setProductId(item.getProductId());
|
record.setProductId(item.getProductId());
|
||||||
record.setProductUnitId(item.getProductUnitId());
|
record.setProductUnitId(item.getProductUnitId());
|
||||||
@@ -247,13 +333,12 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
|||||||
record.setOnHandAfter(onHandAfter);
|
record.setOnHandAfter(onHandAfter);
|
||||||
record.setReservedBefore(reservedBefore);
|
record.setReservedBefore(reservedBefore);
|
||||||
record.setReservedAfter(reservedAfter);
|
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.setBizId(bizId);
|
||||||
record.setSourceRecordId(item.getSourceRecordId());
|
record.setSourceRecordId(item.getSourceRecordId());
|
||||||
record.setRequestNo(normalizedRequestNo);
|
record.setRequestNo(normalizedRequestNo);
|
||||||
record.setOperatorUserId(operatorUserId);
|
record.setOperatorUserId(operatorUserId);
|
||||||
record.setOccurredAt(Time.now());
|
record.setOccurredAt(Time.now());
|
||||||
record.setReason(reason);
|
|
||||||
record.setRemark(remark);
|
record.setRemark(remark);
|
||||||
recordService.createOperationRecord(record, attachmentList);
|
recordService.createOperationRecord(record, attachmentList);
|
||||||
stock.setOnHandQuantity(onHandAfter);
|
stock.setOnHandQuantity(onHandAfter);
|
||||||
@@ -284,15 +369,16 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
|||||||
return stock;
|
return stock;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BigDecimal onHandDelta(GaoStockRecord.OperationType operationType, BigDecimal quantity) {
|
private BigDecimal onHandDelta(GaoProductStockRecord.OperationType operationType, BigDecimal quantity) {
|
||||||
return switch (operationType) {
|
return switch (operationType) {
|
||||||
case INITIAL_IN, MANUAL_IN, STOCKTAKE_GAIN, ORDER_REVOKE -> quantity;
|
case INITIAL_IN, MANUAL_IN, STOCKTAKE_GAIN, ORDER_REVOKE -> quantity;
|
||||||
case MANUAL_OUT, STOCKTAKE_LOSS, ORDER_OUT -> quantity.negate();
|
case MANUAL_OUT, STOCKTAKE_LOSS, ORDER_OUT -> quantity.negate();
|
||||||
case ORDER_RESERVE, ORDER_RELEASE -> ZERO;
|
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) {
|
return switch (operationType) {
|
||||||
case ORDER_RESERVE -> quantity;
|
case ORDER_RESERVE -> quantity;
|
||||||
case ORDER_RELEASE, ORDER_OUT -> quantity.negate();
|
case ORDER_RELEASE, ORDER_OUT -> quantity.negate();
|
||||||
@@ -310,26 +396,33 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
|||||||
return quantity;
|
return quantity;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyExisting(GaoStockRecord existing, GaoStockRecord.OperationType operationType, String productId, String unitId,
|
private void verifyExisting(GaoProductStockRecord existing, GaoProductStockRecord.OperationType operationType, String productId, String unitId,
|
||||||
BigDecimal quantity, BigDecimal baseQuantity, String remark, String reason) {
|
BigDecimal quantity, BigDecimal baseQuantity, String remark) {
|
||||||
TimiException.requiredTrue(existing.getOperationType() == operationType
|
TimiException.requiredTrue(existing.getOperationType() == operationType
|
||||||
&& productId.equals(existing.getProductId())
|
&& productId.equals(existing.getProductId())
|
||||||
&& unitId.equals(existing.getProductUnitId())
|
&& unitId.equals(existing.getProductUnitId())
|
||||||
&& quantity.compareTo(existing.getQuantity()) == 0
|
&& quantity.compareTo(existing.getQuantity()) == 0
|
||||||
&& baseQuantity.compareTo(existing.getBaseQuantity()) == 0
|
&& baseQuantity.compareTo(existing.getBaseQuantity()) == 0
|
||||||
&& sameText(existing.getRemark(), remark)
|
&& sameText(existing.getRemark(), remark), "requestNo 对应库存操作内容不一致");
|
||||||
&& sameText(existing.getReason(), reason), "requestNo 对应库存操作内容不一致");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyExistingList(List<GaoStockRecord> existingList, List<GaoStockChangeItem> itemList, GaoStockRecord.OperationType operationType, String bizId) {
|
private void verifyManualRevokeExisting(List<GaoProductStockRecord> existingList, String sourceRecordId, String remark) {
|
||||||
Map<String, GaoStockRecord> recordMap = new HashMap<>();
|
TimiException.requiredTrue(existingList.size() == 1, "requestNo 对应库存操作内容不一致");
|
||||||
for (GaoStockRecord record : existingList) {
|
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<GaoProductStockRecord> existingList, List<GaoStockChangeItem> itemList, GaoProductStockRecord.OperationType operationType, String bizId) {
|
||||||
|
Map<String, GaoProductStockRecord> recordMap = new HashMap<>();
|
||||||
|
for (GaoProductStockRecord record : existingList) {
|
||||||
TimiException.requiredTrue(record.getOperationType() == operationType && bizId.equals(record.getBizId()), "requestNo 已被其他库存操作使用");
|
TimiException.requiredTrue(record.getOperationType() == operationType && bizId.equals(record.getBizId()), "requestNo 已被其他库存操作使用");
|
||||||
recordMap.put(record.getProductId(), record);
|
recordMap.put(record.getProductId(), record);
|
||||||
}
|
}
|
||||||
TimiException.requiredTrue(recordMap.size() == itemList.size(), "requestNo 对应库存操作内容不一致");
|
TimiException.requiredTrue(recordMap.size() == itemList.size(), "requestNo 对应库存操作内容不一致");
|
||||||
for (GaoStockChangeItem item : itemList) {
|
for (GaoStockChangeItem item : itemList) {
|
||||||
GaoStockRecord record = recordMap.get(item.getProductId());
|
GaoProductStockRecord record = recordMap.get(item.getProductId());
|
||||||
TimiException.required(record, "requestNo 对应库存操作内容不一致");
|
TimiException.required(record, "requestNo 对应库存操作内容不一致");
|
||||||
TimiException.requiredTrue(item.getBaseQuantity().compareTo(record.getBaseQuantity()) == 0, "requestNo 对应库存操作内容不一致");
|
TimiException.requiredTrue(item.getBaseQuantity().compareTo(record.getBaseQuantity()) == 0, "requestNo 对应库存操作内容不一致");
|
||||||
}
|
}
|
||||||
@@ -353,7 +446,7 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasReason(String value) {
|
private boolean hasText(String value) {
|
||||||
return value != null && !value.trim().isEmpty();
|
return value != null && !value.trim().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-22
@@ -26,16 +26,13 @@ public class GaoProductUnitServiceImplement implements GaoProductUnitService {
|
|||||||
@Override
|
@Override
|
||||||
public List<GaoProductUnit> validateAndNormalize(GaoProduct product, List<GaoProductUnit> sourceList) {
|
public List<GaoProductUnit> validateAndNormalize(GaoProduct product, List<GaoProductUnit> sourceList) {
|
||||||
TimiException.required(product, "not found product");
|
TimiException.required(product, "not found product");
|
||||||
String baseUnit = requiredText(product.getBaseUnit(), "基础单位不能为空");
|
TimiException.requiredTrue(sourceList != null && !sourceList.isEmpty(), "产品至少需要一个单位");
|
||||||
product.setBaseUnit(baseUnit);
|
|
||||||
List<GaoProductUnit> inputList = sourceList == null || sourceList.isEmpty()
|
|
||||||
? List.of(createBaseUnit(product)) : sourceList;
|
|
||||||
List<GaoProductUnit> result = new ArrayList<>();
|
List<GaoProductUnit> result = new ArrayList<>();
|
||||||
Set<String> nameSet = new HashSet<>();
|
Set<String> nameSet = new HashSet<>();
|
||||||
Set<String> idSet = new HashSet<>();
|
Set<String> idSet = new HashSet<>();
|
||||||
GaoProductUnit base = null;
|
int baseCount = 0;
|
||||||
for (int i = 0; i < inputList.size(); i++) {
|
for (int i = 0; i < sourceList.size(); i++) {
|
||||||
GaoProductUnit source = inputList.get(i);
|
GaoProductUnit source = sourceList.get(i);
|
||||||
TimiException.required(source, "产品单位不能为空");
|
TimiException.required(source, "产品单位不能为空");
|
||||||
GaoProductUnit unit = new GaoProductUnit();
|
GaoProductUnit unit = new GaoProductUnit();
|
||||||
unit.setId(source.getId());
|
unit.setId(source.getId());
|
||||||
@@ -53,14 +50,13 @@ public class GaoProductUnitServiceImplement implements GaoProductUnitService {
|
|||||||
unit.setAllowSale(TimiJava.defaultIfNull(source.getAllowSale(), true));
|
unit.setAllowSale(TimiJava.defaultIfNull(source.getAllowSale(), true));
|
||||||
unit.setSort(TimiJava.defaultIfNull(source.getSort(), i));
|
unit.setSort(TimiJava.defaultIfNull(source.getSort(), i));
|
||||||
if (unit.getIsBase()) {
|
if (unit.getIsBase()) {
|
||||||
TimiException.requiredNull(base, "一个产品只能有一个基础单位");
|
baseCount++;
|
||||||
|
TimiException.requiredTrue(1 >= baseCount, "一个产品只能有一个基础单位");
|
||||||
TimiException.requiredTrue(unit.getBaseQuantityPerUnit().compareTo(ONE) == 0, "基础单位数量系数必须为 1");
|
TimiException.requiredTrue(unit.getBaseQuantityPerUnit().compareTo(ONE) == 0, "基础单位数量系数必须为 1");
|
||||||
TimiException.requiredTrue(baseUnit.equals(unit.getName()), "基础单位名称必须与产品基础单位一致");
|
|
||||||
base = unit;
|
|
||||||
}
|
}
|
||||||
result.add(unit);
|
result.add(unit);
|
||||||
}
|
}
|
||||||
TimiException.required(base, "产品必须有一个基础单位");
|
TimiException.requiredTrue(0 < baseCount, "产品必须有一个基础单位");
|
||||||
TimiException.requiredTrue(product.getProductType() != GaoProduct.ProductType.SERVICE || !Boolean.TRUE.equals(product.getStockEnabled()), "SERVICE 产品不能启用库存");
|
TimiException.requiredTrue(product.getProductType() != GaoProduct.ProductType.SERVICE || !Boolean.TRUE.equals(product.getStockEnabled()), "SERVICE 产品不能启用库存");
|
||||||
return result;
|
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) {
|
private String requiredText(String value, String message) {
|
||||||
TimiException.required(value, message);
|
TimiException.required(value, message);
|
||||||
String result = value.trim();
|
String result = value.trim();
|
||||||
|
|||||||
+1
-1
@@ -260,7 +260,7 @@ public class GaoStoreServiceImplement extends AbstractEntityService<GaoStore, St
|
|||||||
|| event.getBizType() != GaoEvent.BizType.GENERAL
|
|| event.getBizType() != GaoEvent.BizType.GENERAL
|
||||||
|| event.getValueType() != GaoEvent.ValueType.NUMBER
|
|| event.getValueType() != GaoEvent.ValueType.NUMBER
|
||||||
|| event.getValueUnit() != GaoEvent.ValueUnit.CENT
|
|| event.getValueUnit() != GaoEvent.ValueUnit.CENT
|
||||||
|| Boolean.TRUE.equals(event.getRequireVerification())) {
|
|| Boolean.TRUE.equals(event.getRequireVerification())) { // 消费事件为 true,订单要求为 false,检查是否设计冲突
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (var group : TimiJava.safeIterable(event.getOptionGroupList())) {
|
for (var group : TimiJava.safeIterable(event.getOptionGroupList())) {
|
||||||
|
|||||||
@@ -28,9 +28,6 @@ public class GaoProductStockChangeRequest {
|
|||||||
/// 业务数量
|
/// 业务数量
|
||||||
private BigDecimal quantity;
|
private BigDecimal quantity;
|
||||||
|
|
||||||
/// 原因
|
|
||||||
private String reason;
|
|
||||||
|
|
||||||
/// 备注
|
/// 备注
|
||||||
private String remark;
|
private String remark;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.imyeyu.api.modules.gao.vo;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.common.entity.Attachment;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/// GAO 手工库存流水冲正请求
|
||||||
|
///
|
||||||
|
/// @author Codex
|
||||||
|
/// @since 2026-09-03
|
||||||
|
@Data
|
||||||
|
public class GaoProductStockManualRevokeRequest {
|
||||||
|
|
||||||
|
/// 原手工库存流水 ID
|
||||||
|
private String sourceRecordId;
|
||||||
|
|
||||||
|
/// 客户端幂等请求号
|
||||||
|
private String requestNo;
|
||||||
|
|
||||||
|
/// 冲正备注
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/// 冲正照片
|
||||||
|
private List<Attachment> attachmentList;
|
||||||
|
}
|
||||||
+7
-4
@@ -1,6 +1,6 @@
|
|||||||
package com.imyeyu.api.modules.gao.vo;
|
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 com.imyeyu.java.bean.BasePage;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
@@ -11,7 +11,7 @@ import lombok.EqualsAndHashCode;
|
|||||||
/// @since 2026-09-02
|
/// @since 2026-09-02
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class GaoStockRecordPage extends BasePage {
|
public class GaoProductStockRecordPage extends BasePage {
|
||||||
|
|
||||||
/// 门店 ID
|
/// 门店 ID
|
||||||
private String storeId;
|
private String storeId;
|
||||||
@@ -19,14 +19,17 @@ public class GaoStockRecordPage extends BasePage {
|
|||||||
/// 产品 ID
|
/// 产品 ID
|
||||||
private String productId;
|
private String productId;
|
||||||
|
|
||||||
|
/// 产品名称或编码关键词
|
||||||
|
private String keyword;
|
||||||
|
|
||||||
/// 来源业务类型
|
/// 来源业务类型
|
||||||
private GaoStockRecord.BizType bizType;
|
private GaoProductStockRecord.BizType bizType;
|
||||||
|
|
||||||
/// 来源业务 ID
|
/// 来源业务 ID
|
||||||
private String bizId;
|
private String bizId;
|
||||||
|
|
||||||
/// 操作类型
|
/// 操作类型
|
||||||
private GaoStockRecord.OperationType operationType;
|
private GaoProductStockRecord.OperationType operationType;
|
||||||
|
|
||||||
/// 起始发生时间
|
/// 起始发生时间
|
||||||
private Long beginAt;
|
private Long beginAt;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE `gao_product`
|
||||||
|
DROP COLUMN `base_unit`;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE `gao_product`
|
||||||
|
DROP COLUMN `reference_cost`;
|
||||||
+12
@@ -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`);
|
||||||
+21
-10
@@ -1,18 +1,28 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
|
||||||
<mapper namespace="com.imyeyu.api.modules.gao.mapper.GaoStockRecordMapper">
|
<mapper namespace="com.imyeyu.api.modules.gao.mapper.GaoProductStockRecordMapper">
|
||||||
<select id="selectByRequestNo" resultType="com.imyeyu.api.modules.gao.entity.GaoStockRecord">
|
<select id="selectByRequestNo" resultType="com.imyeyu.api.modules.gao.entity.GaoProductStockRecord">
|
||||||
SELECT * FROM `gao_stock_record`
|
SELECT * FROM `gao_product_stock_record`
|
||||||
WHERE `store_id` = #{storeId} AND `request_no` = #{requestNo}
|
WHERE `store_id` = #{storeId} AND `request_no` = #{requestNo}
|
||||||
ORDER BY `product_id`, `id`
|
ORDER BY `product_id`, `id`
|
||||||
</select>
|
</select>
|
||||||
<select id="selectByBiz" resultType="com.imyeyu.api.modules.gao.entity.GaoStockRecord">
|
<select id="selectForUpdate" resultType="com.imyeyu.api.modules.gao.entity.GaoProductStockRecord">
|
||||||
SELECT * FROM `gao_stock_record`
|
SELECT * FROM `gao_product_stock_record`
|
||||||
|
WHERE `id` = #{id}
|
||||||
|
FOR UPDATE
|
||||||
|
</select>
|
||||||
|
<select id="selectRevokeBySourceRecordId" resultType="com.imyeyu.api.modules.gao.entity.GaoProductStockRecord">
|
||||||
|
SELECT * FROM `gao_product_stock_record`
|
||||||
|
WHERE `source_record_id` = #{sourceRecordId}
|
||||||
|
LIMIT 1
|
||||||
|
</select>
|
||||||
|
<select id="selectByBiz" resultType="com.imyeyu.api.modules.gao.entity.GaoProductStockRecord">
|
||||||
|
SELECT * FROM `gao_product_stock_record`
|
||||||
WHERE `biz_type` = #{bizType} AND `biz_id` = #{bizId}
|
WHERE `biz_type` = #{bizType} AND `biz_id` = #{bizId}
|
||||||
ORDER BY `occurred_at`, `id`
|
ORDER BY `occurred_at`, `id`
|
||||||
</select>
|
</select>
|
||||||
<select id="selectByBizProduct" resultType="com.imyeyu.api.modules.gao.entity.GaoStockRecord">
|
<select id="selectByBizProduct" resultType="com.imyeyu.api.modules.gao.entity.GaoProductStockRecord">
|
||||||
SELECT * FROM `gao_stock_record`
|
SELECT * FROM `gao_product_stock_record`
|
||||||
WHERE `biz_type` = #{bizType} AND `biz_id` = #{bizId} AND `product_id` = #{productId} AND `operation_type` = #{operationType}
|
WHERE `biz_type` = #{bizType} AND `biz_id` = #{bizId} AND `product_id` = #{productId} AND `operation_type` = #{operationType}
|
||||||
ORDER BY `occurred_at`, `id`
|
ORDER BY `occurred_at`, `id`
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
@@ -21,6 +31,7 @@
|
|||||||
1 = 1
|
1 = 1
|
||||||
<if test="storeId != null and storeId != ''">AND r.`store_id` = #{storeId}</if>
|
<if test="storeId != null and storeId != ''">AND r.`store_id` = #{storeId}</if>
|
||||||
<if test="productId != null and productId != ''">AND r.`product_id` = #{productId}</if>
|
<if test="productId != null and productId != ''">AND r.`product_id` = #{productId}</if>
|
||||||
|
<if test="keyword != null and keyword.trim() != ''">AND (r.`product_name_snapshot` LIKE CONCAT('%', #{keyword}, '%') OR p.`name` LIKE CONCAT('%', #{keyword}, '%') OR p.`code` LIKE CONCAT('%', #{keyword}, '%'))</if>
|
||||||
<if test="bizType != null">AND r.`biz_type` = #{bizType}</if>
|
<if test="bizType != null">AND r.`biz_type` = #{bizType}</if>
|
||||||
<if test="bizId != null and bizId != ''">AND r.`biz_id` = #{bizId}</if>
|
<if test="bizId != null and bizId != ''">AND r.`biz_id` = #{bizId}</if>
|
||||||
<if test="operationType != null">AND r.`operation_type` = #{operationType}</if>
|
<if test="operationType != null">AND r.`operation_type` = #{operationType}</if>
|
||||||
@@ -28,10 +39,10 @@
|
|||||||
<if test="endAt != null">AND r.`occurred_at` <= #{endAt}</if>
|
<if test="endAt != null">AND r.`occurred_at` <= #{endAt}</if>
|
||||||
</sql>
|
</sql>
|
||||||
<select id="countByQuery" resultType="long">
|
<select id="countByQuery" resultType="long">
|
||||||
SELECT COUNT(1) FROM `gao_stock_record` r WHERE <include refid="queryWhere"/>
|
SELECT COUNT(1) 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 <include refid="queryWhere"/>
|
||||||
</select>
|
</select>
|
||||||
<select id="selectByQuery" resultType="com.imyeyu.api.modules.gao.entity.GaoStockRecord">
|
<select id="selectByQuery" resultType="com.imyeyu.api.modules.gao.entity.GaoProductStockRecord">
|
||||||
SELECT r.* FROM `gao_stock_record` r WHERE <include refid="queryWhere"/>
|
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 <include refid="queryWhere"/>
|
||||||
ORDER BY r.`occurred_at` DESC, r.`id` DESC
|
ORDER BY r.`occurred_at` DESC, r.`id` DESC
|
||||||
LIMIT #{offset}, #{limit}
|
LIMIT #{offset}, #{limit}
|
||||||
</select>
|
</select>
|
||||||
Reference in New Issue
Block a user