rename GaoStockRecord to GaoProductStockRecord

This commit is contained in:
Timi
2026-09-04 10:37:43 +08:00
parent 913dc3d278
commit df16ac3c8e
23 changed files with 377 additions and 197 deletions
@@ -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),
@@ -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<GaoStockRecord> recordList(@RequestBody GaoStockRecordPage page) {
public PageResult<GaoProductStockRecord> 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()));
@@ -106,7 +106,7 @@ public class GaoOrderFulfillment extends UUIDEntity {
/// 库存处理流水
@Transient
private List<GaoStockRecord> stockRecordList;
private List<GaoProductStockRecord> stockRecordList;
/// 附件
@Transient
@@ -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;
@@ -17,7 +17,7 @@ import java.util.List;
/// @author Codex
/// @since 2026-09-02
@Data
public class GaoStockRecord implements IDEntity<String>, Creatable {
public class GaoProductStockRecord implements IDEntity<String>, Creatable {
/// 库存流水业务类型
public enum BizType {
@@ -41,6 +41,9 @@ public class GaoStockRecord implements IDEntity<String>, Creatable {
/// 手工出库
MANUAL_OUT,
/// 手工流水冲正
MANUAL_REVOKE,
/// 履约预占
ORDER_RESERVE,
@@ -118,6 +121,10 @@ public class GaoStockRecord implements IDEntity<String>, Creatable {
/// 冲正来源流水 ID
private String sourceRecordId;
/// 关联的冲正流水 ID
@Transient
private String revokeRecordId;
/// 幂等请求号
private String requestNo;
@@ -127,9 +134,6 @@ public class GaoStockRecord implements IDEntity<String>, Creatable {
/// 发生时间
private Long occurredAt;
/// 原因
private String reason;
/// 备注
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;
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);
@@ -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);
}
@@ -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<G
private final GaoProductService productService;
private final GaoProductUnitService unitService;
private final GaoProductStockService productStockService;
private final GaoStockRecordService stockRecordService;
private final GaoProductStockRecordService stockRecordService;
private final GaoStoreScopeService storeScopeService;
private final RoleChecker roleChecker;
private final UserLoginService userLoginService;
@@ -127,7 +127,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
TimiException.required(fulfillment, "not found fulfillment");
storeScopeService.checkStoreId(fulfillment.getStoreId());
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;
}
@@ -351,8 +351,8 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
}
if (!stockItems.isEmpty()) {
for (GaoStockChangeItem item : stockItems) {
GaoStockRecord stockRecord = stockRecordService.getByBizProduct(GaoStockRecord.BizType.ORDER_FULFILLMENT,
source.getId(), item.getProductId(), GaoStockRecord.OperationType.ORDER_OUT);
GaoProductStockRecord stockRecord = stockRecordService.getByBizProduct(GaoProductStockRecord.BizType.ORDER_FULFILLMENT,
source.getId(), item.getProductId(), GaoProductStockRecord.OperationType.ORDER_OUT);
if (stockRecord != null) {
item.setSourceRecordId(stockRecord.getId());
}
@@ -139,11 +139,9 @@ public class GaoProductServiceImplement extends AbstractEntityService<GaoProduct
product.setCode(TimiJava.defaultIfNull(request.getCode(), product.getCode()));
product.setName(TimiJava.defaultIfNull(request.getName(), product.getName()));
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.setAllowDecimalQuantity(TimiJava.defaultIfNull(request.getAllowDecimalQuantity(), product.getAllowDecimalQuantity()));
product.setStatus(TimiJava.defaultIfNull(request.getStatus(), product.getStatus()));
product.setReferenceCost(request.getReferenceCost());
product.setMinStockQuantity(request.getMinStockQuantity());
product.setSort(TimiJava.defaultIfNull(request.getSort(), product.getSort()));
product.setRemark(request.getRemark());
@@ -191,12 +189,10 @@ public class GaoProductServiceImplement extends AbstractEntityService<GaoProduct
product.setCode(requiredText(product.getCode(), "产品编码不能为空"));
product.setName(requiredText(product.getName(), "产品名称不能为空"));
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));
TimiException.requiredTrue(product.getProductType() != GaoProduct.ProductType.SERVICE || !product.getStockEnabled(), "SERVICE 产品不能启用库存");
product.setAllowDecimalQuantity(TimiJava.defaultIfNull(product.getAllowDecimalQuantity(), false));
product.setStatus(TimiJava.defaultIfNull(product.getStatus(), GaoProduct.Status.ACTIVE));
product.setReferenceCost(nonNegative(product.getReferenceCost(), "参考成本不能为负数"));
if (!Boolean.TRUE.equals(product.getStockEnabled())) {
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) {
TimiException.required(value, message);
String result = value.trim();
@@ -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.service.AttachmentService;
import com.imyeyu.api.modules.gao.entity.GaoStockRecord;
import com.imyeyu.api.modules.gao.mapper.GaoStockRecordMapper;
import com.imyeyu.api.modules.gao.service.GaoStockRecordService;
import com.imyeyu.api.modules.gao.vo.GaoStockRecordPage;
import com.imyeyu.api.modules.gao.entity.GaoProductStockRecord;
import com.imyeyu.api.modules.gao.mapper.GaoProductStockRecordMapper;
import com.imyeyu.api.modules.gao.service.GaoProductStockRecordService;
import com.imyeyu.api.modules.gao.vo.GaoProductStockRecordPage;
import com.imyeyu.api.modules.user.entity.User;
import com.imyeyu.api.modules.user.service.UserService;
import com.imyeyu.java.bean.timi.TimiCode;
@@ -25,21 +25,21 @@ import java.util.Map;
/// @since 2026-09-02
@Service
@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 UserService userService;
@Override
protected BaseMapper<GaoStockRecord, String> mapper() {
protected BaseMapper<GaoProductStockRecord, String> mapper() {
return mapper;
}
@Override
public PageResult<GaoStockRecord> pageByQuery(GaoStockRecordPage page) {
public PageResult<GaoProductStockRecord> pageByQuery(GaoProductStockRecordPage 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.setList(mapper.selectByQuery(page));
fillTransient(result.getList());
@@ -47,24 +47,47 @@ public class GaoStockRecordServiceImplement extends AbstractEntityService<GaoSto
}
@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(bizId, "not found stock bizId");
List<GaoStockRecord> result = mapper.selectByBiz(bizType, bizId);
List<GaoProductStockRecord> result = mapper.selectByBiz(bizType, bizId);
fillTransient(result);
return result;
}
@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(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<GaoSto
}
@Override
public void createOperationRecord(GaoStockRecord record, List<Attachment> attachmentList) {
public void createOperationRecord(GaoProductStockRecord record, List<Attachment> 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<GaoSto
}
@Override
public void create(GaoStockRecord record) {
public void create(GaoProductStockRecord record) {
throw new TimiException(TimiCode.ERROR, "库存流水必须通过库存操作创建");
}
@Override
public void update(GaoStockRecord record) {
public void update(GaoProductStockRecord record) {
throw new TimiException(TimiCode.ERROR, "库存流水不允许修改");
}
@@ -101,16 +124,16 @@ public class GaoStockRecordServiceImplement extends AbstractEntityService<GaoSto
throw new TimiException(TimiCode.ERROR, "库存流水不允许物理删除");
}
private void fillTransient(List<GaoStockRecord> recordList) {
private void fillTransient(List<GaoProductStockRecord> recordList) {
if (recordList == null || recordList.isEmpty()) {
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);
List<String> userIdList = recordList.stream().map(GaoStockRecord::getOperatorUserId)
List<String> userIdList = recordList.stream().map(GaoProductStockRecord::getOperatorUserId)
.filter(value -> value != null && !value.isBlank()).distinct().toList();
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.setOperator(userMap.get(record.getOperatorUserId()));
}
@@ -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<GaoProductStock> result = new PageResult<>();
result.setTotal(stockMapper.countByQuery(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) {
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<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
@Transactional(TimiServerDBConfig.ROLLBACKER)
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
@Transactional(TimiServerDBConfig.ROLLBACKER)
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
@Transactional(TimiServerDBConfig.ROLLBACKER)
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");
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<GaoStockRecord> existingList = recordService.listByRequestNo(storeId, requestNo);
List<GaoProductStockRecord> 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<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();
}
private List<GaoProductStock> executeInternal(String storeId, String bizId, List<GaoStockChangeItem> 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<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,
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(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<GaoStockRecord> existingList = recordService.listByRequestNo(storeId, normalizedRequestNo);
List<GaoProductStockRecord> existingList = recordService.listByRequestNo(storeId, normalizedRequestNo);
if (!existingList.isEmpty()) {
verifyExistingList(existingList, itemList, operationType, bizId);
List<GaoProductStock> 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<GaoStockRecord> existingList, List<GaoStockChangeItem> itemList, GaoStockRecord.OperationType operationType, String bizId) {
Map<String, GaoStockRecord> recordMap = new HashMap<>();
for (GaoStockRecord record : existingList) {
private void verifyManualRevokeExisting(List<GaoProductStockRecord> 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<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 已被其他库存操作使用");
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();
}
@@ -26,16 +26,13 @@ public class GaoProductUnitServiceImplement implements GaoProductUnitService {
@Override
public List<GaoProductUnit> validateAndNormalize(GaoProduct product, List<GaoProductUnit> sourceList) {
TimiException.required(product, "not found product");
String baseUnit = requiredText(product.getBaseUnit(), "基础单位不能为空");
product.setBaseUnit(baseUnit);
List<GaoProductUnit> inputList = sourceList == null || sourceList.isEmpty()
? List.of(createBaseUnit(product)) : sourceList;
TimiException.requiredTrue(sourceList != null && !sourceList.isEmpty(), "产品至少需要一个单位");
List<GaoProductUnit> result = new ArrayList<>();
Set<String> nameSet = new HashSet<>();
Set<String> 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();
@@ -260,7 +260,7 @@ public class GaoStoreServiceImplement extends AbstractEntityService<GaoStore, St
|| event.getBizType() != GaoEvent.BizType.GENERAL
|| event.getValueType() != GaoEvent.ValueType.NUMBER
|| event.getValueUnit() != GaoEvent.ValueUnit.CENT
|| Boolean.TRUE.equals(event.getRequireVerification())) {
|| Boolean.TRUE.equals(event.getRequireVerification())) { // 消费事件为 true,订单要求为 false,检查是否设计冲突
return false;
}
for (var group : TimiJava.safeIterable(event.getOptionGroupList())) {
@@ -28,9 +28,6 @@ public class GaoProductStockChangeRequest {
/// 业务数量
private BigDecimal quantity;
/// 原因
private String reason;
/// 备注
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;
}
@@ -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;