add gao product SKU
This commit is contained in:
@@ -40,6 +40,8 @@ public class Attachment extends UUIDEntity {
|
||||
|
||||
GAO_PRODUCT,
|
||||
|
||||
GAO_PRODUCT_SKU,
|
||||
|
||||
GAO_EVENT_RECORD,
|
||||
|
||||
GAO_FUND_RECORD,
|
||||
|
||||
@@ -4,10 +4,10 @@ import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
|
||||
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoPackageConfig;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoPackageConfigItem;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProduct;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSku;
|
||||
import com.imyeyu.api.modules.gao.service.GaoEventService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoPackageConfigService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductSkuService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoPackageConfigPage;
|
||||
import com.imyeyu.api.modules.user.entity.User;
|
||||
@@ -37,7 +37,7 @@ public class GaoPackageConfigController {
|
||||
|
||||
private final GaoPackageConfigService service;
|
||||
private final GaoEventService eventService;
|
||||
private final GaoProductService productService;
|
||||
private final GaoProductSkuService skuService;
|
||||
private final GaoStoreScopeService storeScopeService;
|
||||
private final UserService userService;
|
||||
|
||||
@@ -92,20 +92,20 @@ public class GaoPackageConfigController {
|
||||
}
|
||||
|
||||
private void fillProductDetails(List<GaoPackageConfig> configList) {
|
||||
List<String> productIdList = configList.stream()
|
||||
List<String> skuIdList = configList.stream()
|
||||
.flatMap(config -> config.getItemList().stream())
|
||||
.map(GaoPackageConfigItem::getProductId)
|
||||
.map(GaoPackageConfigItem::getSkuId)
|
||||
.filter(value -> value != null && !value.isBlank())
|
||||
.distinct()
|
||||
.toList();
|
||||
if (productIdList.isEmpty()) {
|
||||
if (skuIdList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<String, GaoProduct> productMap = productService.listByIdList(productIdList).stream()
|
||||
.collect(java.util.stream.Collectors.toMap(GaoProduct::getId, item -> item));
|
||||
Map<String, GaoProductSku> skuMap = skuIdList.stream()
|
||||
.collect(java.util.stream.Collectors.toMap(item -> item, skuService::detail));
|
||||
for (GaoPackageConfig config : configList) {
|
||||
for (GaoPackageConfigItem item : config.getItemList()) {
|
||||
item.setProduct(productMap.get(item.getProductId()));
|
||||
item.setSku(skuMap.get(item.getSkuId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,13 @@ import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
|
||||
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProduct;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductCategory;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSku;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductCategoryService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductSkuService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductSpecService;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSpec;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSpecValue;
|
||||
import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductCategoryPage;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductPage;
|
||||
@@ -33,6 +38,8 @@ public class GaoProductController {
|
||||
|
||||
private final GaoProductCategoryService categoryService;
|
||||
private final GaoProductService productService;
|
||||
private final GaoProductSkuService skuService;
|
||||
private final GaoProductSpecService specService;
|
||||
private final GaoStoreScopeService storeScopeService;
|
||||
|
||||
@AOPLog
|
||||
@@ -142,6 +149,93 @@ public class GaoProductController {
|
||||
productService.disable(id);
|
||||
}
|
||||
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@RequireGaoPermission(GaoPermissionCode.PRODUCT_READ)
|
||||
@PostMapping("/sku/detail")
|
||||
public GaoProductSku skuDetail(@RequestParam String id) {
|
||||
GaoProductSku result = skuService.detail(id);
|
||||
storeScopeService.checkStoreId(result.getStoreId());
|
||||
return result;
|
||||
}
|
||||
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@RequireGaoPermission(GaoPermissionCode.PRODUCT_CREATE)
|
||||
@PostMapping("/sku/create")
|
||||
public String skuCreate(@RequestBody GaoProductSku sku) {
|
||||
TimiException.required(sku, "not found product sku");
|
||||
GaoProduct product = productService.get(sku.getProductId());
|
||||
TimiException.required(product, "not found product");
|
||||
storeScopeService.checkStoreId(product.getStoreId());
|
||||
skuService.create(sku);
|
||||
return sku.getId();
|
||||
}
|
||||
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@RequireGaoPermission(GaoPermissionCode.PRODUCT_UPDATE)
|
||||
@PostMapping("/sku/update")
|
||||
public void skuUpdate(@RequestBody GaoProductSku request) {
|
||||
TimiException.required(request, "not found product sku");
|
||||
TimiException.required(request.getId(), "not found product sku.id");
|
||||
GaoProductSku sku = skuService.get(request.getId());
|
||||
TimiException.required(sku, "not found product sku");
|
||||
storeScopeService.checkStoreId(sku.getStoreId());
|
||||
request.setProductId(sku.getProductId());
|
||||
skuService.update(request);
|
||||
}
|
||||
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@RequireGaoPermission(GaoPermissionCode.PRODUCT_UPDATE)
|
||||
@PostMapping("/sku/disable")
|
||||
public void skuDisable(@RequestParam String id) {
|
||||
GaoProductSku sku = skuService.get(id);
|
||||
TimiException.required(sku, "not found product sku");
|
||||
storeScopeService.checkStoreId(sku.getStoreId());
|
||||
skuService.disable(id);
|
||||
}
|
||||
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@RequireGaoPermission(GaoPermissionCode.PRODUCT_READ)
|
||||
@PostMapping("/spec/list")
|
||||
public List<GaoProductSpec> specList(@RequestParam String productId) {
|
||||
GaoProduct product = productService.get(productId);
|
||||
TimiException.required(product, "not found product");
|
||||
storeScopeService.checkStoreId(product.getStoreId());
|
||||
return specService.listByProductId(productId);
|
||||
}
|
||||
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@RequireGaoPermission(GaoPermissionCode.PRODUCT_UPDATE)
|
||||
@PostMapping("/spec/create")
|
||||
public String specCreate(@RequestBody GaoProductSpec spec) {
|
||||
TimiException.required(spec, "not found product spec");
|
||||
GaoProduct product = productService.get(spec.getProductId());
|
||||
TimiException.required(product, "not found product");
|
||||
storeScopeService.checkStoreId(product.getStoreId());
|
||||
specService.createSpec(spec);
|
||||
return spec.getId();
|
||||
}
|
||||
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@RequireGaoPermission(GaoPermissionCode.PRODUCT_UPDATE)
|
||||
@PostMapping("/spec/value/create")
|
||||
public String specValueCreate(@RequestBody GaoProductSpecValue value) {
|
||||
TimiException.required(value, "not found product spec value");
|
||||
String productId = specService.getProductIdBySpecId(value.getSpecId());
|
||||
TimiException.required(productId, "规格项不存在");
|
||||
GaoProduct product = productService.get(productId);
|
||||
TimiException.required(product, "not found product");
|
||||
storeScopeService.checkStoreId(product.getStoreId());
|
||||
specService.createValue(value);
|
||||
return value.getId();
|
||||
}
|
||||
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@RequireGaoPermission(GaoPermissionCode.PRODUCT_READ)
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
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.service.GaoProductSkuChartService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductChartReq;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuFulfillmentDailyStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuFulfillmentRankView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuSalesDailyStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuSalesRankView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuStockHealthView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuStockMovementDailyStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSalesRankView;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.spring.annotation.AOPLog;
|
||||
import com.imyeyu.spring.annotation.RequestRateLimit;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/// GAO SKU 图表接口
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-07
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/gao/product/sku/chart")
|
||||
public class GaoProductSkuChartController {
|
||||
|
||||
private final GaoProductSkuChartService service;
|
||||
private final GaoStoreScopeService storeScopeService;
|
||||
|
||||
/// 查询 SKU 每日销售统计
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@RequireGaoPermission(GaoPermissionCode.STAT_READ)
|
||||
@PostMapping("/sales/daily")
|
||||
public List<GaoProductSkuSalesDailyStatView> salesDaily(@RequestBody GaoProductChartReq req) {
|
||||
return service.salesDaily(normalize(req));
|
||||
}
|
||||
|
||||
/// 查询 SKU 销售排行
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@RequireGaoPermission(GaoPermissionCode.STAT_READ)
|
||||
@PostMapping("/sales/rank")
|
||||
public List<GaoProductSkuSalesRankView> salesRank(@RequestBody GaoProductChartReq req) {
|
||||
return service.salesRank(normalize(req));
|
||||
}
|
||||
|
||||
/// 查询 SKU 单位销售排行
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@RequireGaoPermission(GaoPermissionCode.STAT_READ)
|
||||
@PostMapping("/sales/unit/rank")
|
||||
public List<GaoProductSkuUnitSalesRankView> unitSalesRank(@RequestBody GaoProductChartReq req) {
|
||||
return service.unitSalesRank(normalize(req));
|
||||
}
|
||||
|
||||
/// 查询 SKU 每日履约统计
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@RequireGaoPermission(GaoPermissionCode.STAT_READ)
|
||||
@PostMapping("/fulfillment/daily")
|
||||
public List<GaoProductSkuFulfillmentDailyStatView> fulfillmentDaily(@RequestBody GaoProductChartReq req) {
|
||||
return service.fulfillmentDaily(normalize(req));
|
||||
}
|
||||
|
||||
/// 查询 SKU 履约进度排行
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@RequireGaoPermission(GaoPermissionCode.STAT_READ)
|
||||
@PostMapping("/fulfillment/rank")
|
||||
public List<GaoProductSkuFulfillmentRankView> fulfillmentRank(@RequestBody GaoProductChartReq req) {
|
||||
return service.fulfillmentRank(normalize(req));
|
||||
}
|
||||
|
||||
/// 查询 SKU 每日库存变动统计
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@RequireGaoPermission(GaoPermissionCode.STAT_READ)
|
||||
@PostMapping("/stock/movement/daily")
|
||||
public List<GaoProductSkuStockMovementDailyStatView> stockMovementDaily(@RequestBody GaoProductChartReq req) {
|
||||
return service.stockMovementDaily(normalize(req));
|
||||
}
|
||||
|
||||
/// 查询 SKU 库存健康度
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@RequireGaoPermission(GaoPermissionCode.STAT_READ)
|
||||
@PostMapping("/stock/health")
|
||||
public List<GaoProductSkuStockHealthView> stockHealth(@RequestBody GaoProductChartReq req) {
|
||||
return service.stockHealth(normalize(req));
|
||||
}
|
||||
|
||||
private GaoProductChartReq normalize(GaoProductChartReq req) {
|
||||
TimiException.required(req, "SKU 图表请求不能为空");
|
||||
req.setStoreId(storeScopeService.resolveStoreId(req.getStoreId()));
|
||||
return req;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ 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.GaoProductStockProductView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductStockRecordPage;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.spring.annotation.AOPLog;
|
||||
@@ -45,12 +46,22 @@ public class GaoProductStockController {
|
||||
return stockService.pageByQuery(page);
|
||||
}
|
||||
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_READ)
|
||||
@PostMapping("/product-list")
|
||||
public PageResult<GaoProductStockProductView> productList(@RequestBody GaoProductStockPage page) {
|
||||
TimiException.required(page, "not found stock product page");
|
||||
page.setStoreId(storeScopeService.resolveStoreId(page.getStoreId()));
|
||||
return stockService.pageProductByQuery(page);
|
||||
}
|
||||
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_READ)
|
||||
@PostMapping("/detail")
|
||||
public GaoProductStock detail(@RequestParam(required = false) String storeId, @RequestParam String productId) {
|
||||
return stockService.detail(storeScopeService.resolveStoreId(storeId), productId);
|
||||
public GaoProductStock detail(@RequestParam(required = false) String storeId, @RequestParam String skuId) {
|
||||
return stockService.detail(storeScopeService.resolveStoreId(storeId), skuId);
|
||||
}
|
||||
|
||||
@AOPLog
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.imyeyu.api.modules.gao.service.GaoSalesOrderChartService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerOrderProductRankView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerOrderRankView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerOrderSkuRankView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerRepeatDailyStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerRepeatIntervalStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoSalesOrderChartReq;
|
||||
@@ -84,6 +85,15 @@ public class GaoSalesOrderChartController {
|
||||
return service.customerProductRank(normalize(req));
|
||||
}
|
||||
|
||||
/// 查询指定客户订单 SKU 排行
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@RequireGaoPermission(GaoPermissionCode.STAT_READ)
|
||||
@PostMapping("/customer/sku/rank")
|
||||
public List<GaoCustomerOrderSkuRankView> customerSkuRank(@RequestBody GaoSalesOrderChartReq req) {
|
||||
return service.customerSkuRank(normalize(req));
|
||||
}
|
||||
|
||||
/// 查询销售订单状态统计
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
|
||||
@@ -25,11 +25,11 @@ public class GaoOrderFulfillmentItem implements IDEntity<String>, Creatable {
|
||||
/// 履约单 ID
|
||||
private String fulfillmentId;
|
||||
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
/// SKU ID
|
||||
private String skuId;
|
||||
|
||||
/// 产品单位 ID
|
||||
private String productUnitId;
|
||||
/// SKU 单位 ID
|
||||
private String skuUnitId;
|
||||
|
||||
/// 产品图片附件
|
||||
@Transient
|
||||
@@ -59,6 +59,18 @@ public class GaoOrderFulfillmentItem implements IDEntity<String>, Creatable {
|
||||
/// 产品名称快照
|
||||
private String productNameSnapshot;
|
||||
|
||||
/// 产品 ID 快照
|
||||
private String productIdSnapshot;
|
||||
|
||||
/// SKU 编码快照
|
||||
private String skuCodeSnapshot;
|
||||
|
||||
/// SKU 名称快照
|
||||
private String skuNameSnapshot;
|
||||
|
||||
/// 规格摘要快照
|
||||
private String specSummarySnapshot;
|
||||
|
||||
/// 单位快照
|
||||
private String unitSnapshot;
|
||||
|
||||
|
||||
@@ -18,15 +18,27 @@ public class GaoPackageConfigItem extends UUIDEntity {
|
||||
/// 套餐配置 ID
|
||||
private String packageConfigId;
|
||||
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
/// SKU ID
|
||||
private String skuId;
|
||||
|
||||
/// 产品单位 ID
|
||||
private String productUnitId;
|
||||
/// SKU 单位 ID
|
||||
private String skuUnitId;
|
||||
|
||||
/// 产品名称快照
|
||||
private String productNameSnapshot;
|
||||
|
||||
/// 产品 ID 快照
|
||||
private String productIdSnapshot;
|
||||
|
||||
/// SKU 编码快照
|
||||
private String skuCodeSnapshot;
|
||||
|
||||
/// SKU 名称快照
|
||||
private String skuNameSnapshot;
|
||||
|
||||
/// 规格摘要快照
|
||||
private String specSummarySnapshot;
|
||||
|
||||
/// 单位快照
|
||||
private String unitSnapshot;
|
||||
|
||||
@@ -42,7 +54,7 @@ public class GaoPackageConfigItem extends UUIDEntity {
|
||||
/// 备注
|
||||
private String remark;
|
||||
|
||||
/// 产品详情
|
||||
/// SKU 详情
|
||||
@Transient
|
||||
private GaoProduct product;
|
||||
private GaoProductSku sku;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import com.imyeyu.spring.entity.UUIDEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/// GAO 产品
|
||||
@@ -68,15 +67,9 @@ public class GaoProduct extends UUIDEntity {
|
||||
/// true 为启用库存管理
|
||||
private Boolean stockEnabled;
|
||||
|
||||
/// true 为允许业务数量保留小数
|
||||
private Boolean allowDecimalQuantity;
|
||||
|
||||
/// 状态
|
||||
private Status status;
|
||||
|
||||
/// 最低库存数量,基础单位
|
||||
private BigDecimal minStockQuantity;
|
||||
|
||||
/// 排序值
|
||||
private Integer sort;
|
||||
|
||||
@@ -90,9 +83,13 @@ public class GaoProduct extends UUIDEntity {
|
||||
@Transient
|
||||
private GaoProductCategory category;
|
||||
|
||||
/// 产品单位列表
|
||||
/// 产品规格列表
|
||||
@Transient
|
||||
private List<GaoProductUnit> unitList;
|
||||
private List<GaoProductSpec> specList;
|
||||
|
||||
/// 产品 SKU 列表
|
||||
@Transient
|
||||
private List<GaoProductSku> skuList;
|
||||
|
||||
/// 产品附件列表
|
||||
@Transient
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.imyeyu.api.modules.gao.entity;
|
||||
|
||||
import com.imyeyu.api.modules.common.entity.Attachment;
|
||||
import com.imyeyu.spring.annotation.table.Transient;
|
||||
import com.imyeyu.spring.entity.UUIDEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/// GAO 产品 SKU
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-06
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class GaoProductSku extends UUIDEntity {
|
||||
|
||||
/// SKU 状态
|
||||
public enum Status {
|
||||
|
||||
/// 启用
|
||||
ACTIVE,
|
||||
|
||||
/// 停用
|
||||
INACTIVE
|
||||
}
|
||||
|
||||
/// 门店 ID
|
||||
private String storeId;
|
||||
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
|
||||
/// 门店内 SKU 编码
|
||||
private String code;
|
||||
|
||||
/// 条码
|
||||
private String barcode;
|
||||
|
||||
/// SKU 展示名称
|
||||
private String name;
|
||||
|
||||
/// 规格摘要
|
||||
private String specSummary;
|
||||
|
||||
/// 规格值组合键
|
||||
private String specValueKey;
|
||||
|
||||
/// true 为启用库存管理
|
||||
private Boolean stockEnabled;
|
||||
|
||||
/// true 为允许业务数量保留小数
|
||||
private Boolean allowDecimalQuantity;
|
||||
|
||||
/// 最低库存数量,基础单位
|
||||
private BigDecimal minStockQuantity;
|
||||
|
||||
/// 状态
|
||||
private Status status;
|
||||
|
||||
/// 排序值
|
||||
private Integer sort;
|
||||
|
||||
/// 备注
|
||||
private String remark;
|
||||
|
||||
/// 最后修改用户 ID
|
||||
private String updatedBy;
|
||||
|
||||
/// 规格值 ID 列表
|
||||
@Transient
|
||||
private List<String> specValueIdList;
|
||||
|
||||
/// SKU 单位列表
|
||||
@Transient
|
||||
private List<GaoProductSkuUnit> unitList;
|
||||
|
||||
/// SKU 附件列表
|
||||
@Transient
|
||||
private List<Attachment> attachmentList;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.imyeyu.api.modules.gao.entity;
|
||||
|
||||
import com.imyeyu.spring.annotation.table.AutoUUID;
|
||||
import com.imyeyu.spring.annotation.table.Id;
|
||||
import com.imyeyu.spring.entity.Creatable;
|
||||
import com.imyeyu.spring.entity.IDEntity;
|
||||
import com.imyeyu.spring.entity.UUIDEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/// GAO SKU 规格值关联
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-06
|
||||
@Data
|
||||
public class GaoProductSkuSpecValue implements IDEntity<String>, Creatable {
|
||||
|
||||
@Id
|
||||
@AutoUUID
|
||||
private String id;
|
||||
|
||||
/// SKU ID
|
||||
private String skuId;
|
||||
|
||||
/// 规格值 ID
|
||||
private String specValueId;
|
||||
|
||||
private Long createdAt;
|
||||
}
|
||||
+5
-5
@@ -6,16 +6,16 @@ import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/// GAO 产品业务单位
|
||||
/// GAO 产品 SKU 业务单位
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-02
|
||||
/// @since 2026-09-06
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class GaoProductUnit extends UUIDEntity {
|
||||
public class GaoProductSkuUnit extends UUIDEntity {
|
||||
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
/// SKU ID
|
||||
private String skuId;
|
||||
|
||||
/// 单位名称
|
||||
private String name;
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.imyeyu.api.modules.gao.entity;
|
||||
|
||||
import com.imyeyu.spring.annotation.table.Transient;
|
||||
import com.imyeyu.spring.entity.UUIDEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/// GAO 产品规格项
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-06
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class GaoProductSpec extends UUIDEntity {
|
||||
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
|
||||
/// 规格名称
|
||||
private String name;
|
||||
|
||||
/// 排序值
|
||||
private Integer sort;
|
||||
|
||||
/// 规格值列表
|
||||
@Transient
|
||||
private List<GaoProductSpecValue> valueList;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.imyeyu.api.modules.gao.entity;
|
||||
|
||||
import com.imyeyu.spring.entity.UUIDEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/// GAO 产品规格值
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-06
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class GaoProductSpecValue extends UUIDEntity {
|
||||
|
||||
/// 规格项 ID
|
||||
private String specId;
|
||||
|
||||
/// 规格值名称
|
||||
private String name;
|
||||
|
||||
/// true 为启用
|
||||
private Boolean enabled;
|
||||
|
||||
/// 排序值
|
||||
private Integer sort;
|
||||
}
|
||||
@@ -18,8 +18,8 @@ public class GaoProductStock extends UUIDEntity {
|
||||
/// 门店 ID
|
||||
private String storeId;
|
||||
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
/// SKU ID
|
||||
private String skuId;
|
||||
|
||||
/// 实际库存,基础单位
|
||||
private BigDecimal onHandQuantity;
|
||||
@@ -30,9 +30,9 @@ public class GaoProductStock extends UUIDEntity {
|
||||
/// 最后操作人
|
||||
private String updatedBy;
|
||||
|
||||
/// 产品
|
||||
/// SKU
|
||||
@Transient
|
||||
private GaoProduct product;
|
||||
private GaoProductSku sku;
|
||||
|
||||
/// 可用库存,基础单位
|
||||
@Transient
|
||||
|
||||
@@ -70,15 +70,27 @@ public class GaoProductStockRecord implements IDEntity<String>, Creatable {
|
||||
/// 门店 ID
|
||||
private String storeId;
|
||||
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
/// SKU ID
|
||||
private String skuId;
|
||||
|
||||
/// 产品单位 ID
|
||||
private String productUnitId;
|
||||
/// SKU 单位 ID
|
||||
private String skuUnitId;
|
||||
|
||||
/// 产品名称快照
|
||||
private String productNameSnapshot;
|
||||
|
||||
/// 产品 ID 快照
|
||||
private String productIdSnapshot;
|
||||
|
||||
/// SKU 编码快照
|
||||
private String skuCodeSnapshot;
|
||||
|
||||
/// SKU 名称快照
|
||||
private String skuNameSnapshot;
|
||||
|
||||
/// 规格摘要快照
|
||||
private String specSummarySnapshot;
|
||||
|
||||
/// 单位名称快照
|
||||
private String unitSnapshot;
|
||||
|
||||
|
||||
@@ -33,11 +33,11 @@ public class GaoSalesOrderItem extends UUIDEntity {
|
||||
/// 订单项类型
|
||||
private ItemType itemType;
|
||||
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
/// SKU ID
|
||||
private String skuId;
|
||||
|
||||
/// 产品单位 ID
|
||||
private String productUnitId;
|
||||
/// SKU 单位 ID
|
||||
private String skuUnitId;
|
||||
|
||||
/// 产品图片附件
|
||||
@Transient
|
||||
@@ -46,6 +46,18 @@ public class GaoSalesOrderItem extends UUIDEntity {
|
||||
/// 产品名称快照
|
||||
private String productNameSnapshot;
|
||||
|
||||
/// 产品 ID 快照
|
||||
private String productIdSnapshot;
|
||||
|
||||
/// SKU 编码快照
|
||||
private String skuCodeSnapshot;
|
||||
|
||||
/// SKU 名称快照
|
||||
private String skuNameSnapshot;
|
||||
|
||||
/// 规格摘要快照
|
||||
private String specSummarySnapshot;
|
||||
|
||||
/// 单位快照
|
||||
private String unitSnapshot;
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.imyeyu.api.modules.gao.mapper;
|
||||
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductChartReq;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuFulfillmentDailyStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuFulfillmentRankView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuSalesDailyStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuSalesRankView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuStockHealthView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuStockMovementDailyStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSalesRankView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/// GAO SKU 图表 Mapper
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-07
|
||||
public interface GaoProductSkuChartMapper {
|
||||
|
||||
/// 查询 SKU 每日销售统计
|
||||
List<GaoProductSkuSalesDailyStatView> selectSalesDaily(GaoProductChartReq req);
|
||||
|
||||
/// 查询 SKU 销售排行
|
||||
List<GaoProductSkuSalesRankView> selectSalesRank(GaoProductChartReq req);
|
||||
|
||||
/// 查询 SKU 单位销售排行
|
||||
List<GaoProductSkuUnitSalesRankView> selectUnitSalesRank(GaoProductChartReq req);
|
||||
|
||||
/// 查询 SKU 每日履约统计
|
||||
List<GaoProductSkuFulfillmentDailyStatView> selectFulfillmentDaily(GaoProductChartReq req);
|
||||
|
||||
/// 查询 SKU 履约进度排行
|
||||
List<GaoProductSkuFulfillmentRankView> selectFulfillmentRank(GaoProductChartReq req);
|
||||
|
||||
/// 查询 SKU 每日库存变动统计
|
||||
List<GaoProductSkuStockMovementDailyStatView> selectStockMovementDaily(GaoProductChartReq req);
|
||||
|
||||
/// 查询 SKU 库存健康度
|
||||
List<GaoProductSkuStockHealthView> selectStockHealth(GaoProductChartReq req);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.imyeyu.api.modules.gao.mapper;
|
||||
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSku;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/// GAO 产品 SKU Mapper
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-06
|
||||
public interface GaoProductSkuMapper extends BaseMapper<GaoProductSku, String> {
|
||||
|
||||
/// 按产品查询 SKU
|
||||
List<GaoProductSku> selectByProductId(@Param("productId") String productId);
|
||||
|
||||
/// 批量按产品查询 SKU
|
||||
List<GaoProductSku> selectByProductIdList(@Param("productIdList") Collection<String> productIdList);
|
||||
|
||||
/// 查询 SKU 编码
|
||||
GaoProductSku selectByStoreIdAndCode(@Param("storeId") String storeId, @Param("code") String code, @Param("excludeId") String excludeId);
|
||||
|
||||
/// 查询 SKU 条码
|
||||
GaoProductSku selectByStoreIdAndBarcode(@Param("storeId") String storeId, @Param("barcode") String barcode, @Param("excludeId") String excludeId);
|
||||
|
||||
/// 查询产品下相同规格值组合的 SKU
|
||||
GaoProductSku selectByProductIdAndSpecValueKey(@Param("productId") String productId, @Param("specValueKey") String specValueKey, @Param("excludeId") String excludeId);
|
||||
|
||||
/// 锁定 SKU
|
||||
GaoProductSku selectForUpdate(@Param("id") String id);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.imyeyu.api.modules.gao.mapper;
|
||||
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSkuSpecValue;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/// GAO SKU 规格值关联 Mapper
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-06
|
||||
public interface GaoProductSkuSpecValueMapper extends BaseMapper<GaoProductSkuSpecValue, String> {
|
||||
|
||||
/// 批量查询 SKU 规格值关联
|
||||
List<GaoProductSkuSpecValue> selectBySkuIdList(@Param("skuIdList") Collection<String> skuIdList);
|
||||
|
||||
/// 删除 SKU 的规格值关联
|
||||
void deleteBySkuId(@Param("skuId") String skuId);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.imyeyu.api.modules.gao.mapper;
|
||||
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSkuUnit;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/// GAO 产品 SKU 单位 Mapper
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-06
|
||||
public interface GaoProductSkuUnitMapper extends BaseMapper<GaoProductSkuUnit, String> {
|
||||
|
||||
/// 按 SKU 查询有效单位
|
||||
List<GaoProductSkuUnit> selectBySkuId(@Param("skuId") String skuId);
|
||||
|
||||
/// 批量查询 SKU 有效单位
|
||||
List<GaoProductSkuUnit> selectBySkuIdList(@Param("skuIdList") Collection<String> skuIdList);
|
||||
|
||||
/// 锁定单位
|
||||
GaoProductSkuUnit selectForUpdate(@Param("id") String id);
|
||||
|
||||
/// 更新有效单位
|
||||
int updateUnit(GaoProductSkuUnit unit);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.imyeyu.api.modules.gao.mapper;
|
||||
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSpec;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/// GAO 产品规格项 Mapper
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-06
|
||||
public interface GaoProductSpecMapper extends BaseMapper<GaoProductSpec, String> {
|
||||
|
||||
/// 按产品查询规格项
|
||||
List<GaoProductSpec> selectByProductId(@Param("productId") String productId);
|
||||
|
||||
/// 批量按产品查询规格项
|
||||
List<GaoProductSpec> selectByProductIdList(@Param("productIdList") Collection<String> productIdList);
|
||||
|
||||
/// 查询规格值所属产品
|
||||
String selectProductIdBySpecValueId(@Param("specValueId") String specValueId);
|
||||
|
||||
/// 查询规格项所属产品
|
||||
String selectProductIdBySpecId(@Param("specId") String specId);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.imyeyu.api.modules.gao.mapper;
|
||||
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSpecValue;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/// GAO 产品规格值 Mapper
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-06
|
||||
public interface GaoProductSpecValueMapper extends BaseMapper<GaoProductSpecValue, String> {
|
||||
|
||||
/// 按 ID 查询有效规格值
|
||||
GaoProductSpecValue selectById(@Param("id") String id);
|
||||
|
||||
/// 按规格项查询规格值
|
||||
List<GaoProductSpecValue> selectBySpecIdList(@Param("specIdList") Collection<String> specIdList);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.imyeyu.api.modules.gao.mapper;
|
||||
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductStock;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductStockPage;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductStockProductView;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@@ -14,7 +15,7 @@ import java.util.List;
|
||||
public interface GaoProductStockMapper extends BaseMapper<GaoProductStock, String> {
|
||||
|
||||
/// 锁定库存余额
|
||||
GaoProductStock selectForUpdate(@Param("storeId") String storeId, @Param("productId") String productId);
|
||||
GaoProductStock selectForUpdate(@Param("storeId") String storeId, @Param("skuId") String skuId);
|
||||
|
||||
/// 按条件统计库存余额
|
||||
long countByQuery(GaoProductStockPage page);
|
||||
@@ -22,8 +23,14 @@ public interface GaoProductStockMapper extends BaseMapper<GaoProductStock, Strin
|
||||
/// 按条件查询库存余额
|
||||
List<GaoProductStock> selectByQuery(GaoProductStockPage page);
|
||||
|
||||
/// 按产品汇总库存数量
|
||||
long countProductByQuery(GaoProductStockPage page);
|
||||
|
||||
/// 按产品分页查询库存汇总
|
||||
List<GaoProductStockProductView> selectProductByQuery(GaoProductStockPage page);
|
||||
|
||||
/// 按门店和产品查询库存余额
|
||||
GaoProductStock selectByStoreIdAndProductId(@Param("storeId") String storeId, @Param("productId") String productId);
|
||||
GaoProductStock selectByStoreIdAndSkuId(@Param("storeId") String storeId, @Param("skuId") String skuId);
|
||||
|
||||
/// 条件更新库存余额
|
||||
int updateQuantity(@Param("id") String id, @Param("onHandDelta") java.math.BigDecimal onHandDelta, @Param("reservedDelta") java.math.BigDecimal reservedDelta, @Param("updatedBy") String updatedBy);
|
||||
|
||||
@@ -26,7 +26,7 @@ public interface GaoProductStockRecordMapper extends BaseMapper<GaoProductStockR
|
||||
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);
|
||||
GaoProductStockRecord selectByBizSku(@Param("bizType") GaoProductStockRecord.BizType bizType, @Param("bizId") String bizId, @Param("skuId") String skuId, @Param("operationType") GaoProductStockRecord.OperationType operationType);
|
||||
|
||||
/// 按条件统计库存流水
|
||||
long countByQuery(GaoProductStockRecordPage page);
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.imyeyu.api.modules.gao.mapper;
|
||||
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductUnit;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/// GAO 产品单位 Mapper
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-02
|
||||
public interface GaoProductUnitMapper extends BaseMapper<GaoProductUnit, String> {
|
||||
|
||||
/// 按产品查询有效单位
|
||||
List<GaoProductUnit> selectByProductId(@Param("productId") String productId);
|
||||
|
||||
/// 批量查询产品有效单位
|
||||
List<GaoProductUnit> selectByProductIdList(@Param("productIdList") Collection<String> productIdList);
|
||||
|
||||
/// 锁定单位
|
||||
GaoProductUnit selectForUpdate(@Param("id") String id);
|
||||
|
||||
/// 更新有效单位
|
||||
int updateUnit(GaoProductUnit unit);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.imyeyu.api.modules.gao.mapper;
|
||||
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerOrderProductRankView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerOrderRankView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerOrderSkuRankView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerRepeatDailyStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerRepeatIntervalStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoSalesOrderChartReq;
|
||||
@@ -36,6 +37,9 @@ public interface GaoSalesOrderChartMapper {
|
||||
/// 查询客户订单产品排行
|
||||
List<GaoCustomerOrderProductRankView> selectCustomerProductRank(GaoSalesOrderChartReq req);
|
||||
|
||||
/// 查询客户订单 SKU 排行
|
||||
List<GaoCustomerOrderSkuRankView> selectCustomerSkuRank(GaoSalesOrderChartReq req);
|
||||
|
||||
/// 查询销售订单状态统计
|
||||
List<GaoSalesOrderStatusStatView> selectStatus(GaoSalesOrderChartReq req);
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package com.imyeyu.api.modules.gao.service;
|
||||
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProduct;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductUnit;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductPage;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductUnitSelection;
|
||||
import com.imyeyu.spring.bean.PageResult;
|
||||
import com.imyeyu.spring.service.BaseService;
|
||||
|
||||
@@ -28,21 +26,6 @@ public interface GaoProductService extends BaseService<GaoProduct, String> {
|
||||
/// 查询启用且允许销售的产品单位
|
||||
List<GaoProduct> listSellable(GaoProductPage page);
|
||||
|
||||
/// 查询并校验可销售产品单位
|
||||
GaoProductUnitSelection getSellableProductUnit(String storeId, String productId, String productUnitId);
|
||||
|
||||
/// 查询当前门店的有效产品单位
|
||||
///
|
||||
/// @param storeId 门店 ID
|
||||
/// @param productId 产品 ID
|
||||
/// @param productUnitId 产品单位 ID
|
||||
/// @param purchase true 为按采购入库用途校验
|
||||
/// @return 已校验的产品单位
|
||||
GaoProductUnitSelection getProductUnit(String storeId, String productId, String productUnitId, boolean purchase);
|
||||
|
||||
/// 查询产品单位
|
||||
List<GaoProductUnit> listUnitByProductId(String productId);
|
||||
|
||||
/// 停用产品
|
||||
void disable(String id);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.imyeyu.api.modules.gao.service;
|
||||
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductChartReq;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuFulfillmentDailyStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuFulfillmentRankView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuSalesDailyStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuSalesRankView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuStockHealthView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuStockMovementDailyStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSalesRankView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/// GAO SKU 图表服务
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-07
|
||||
public interface GaoProductSkuChartService {
|
||||
|
||||
/// 查询 SKU 每日销售统计
|
||||
List<GaoProductSkuSalesDailyStatView> salesDaily(GaoProductChartReq req);
|
||||
|
||||
/// 查询 SKU 销售排行
|
||||
List<GaoProductSkuSalesRankView> salesRank(GaoProductChartReq req);
|
||||
|
||||
/// 查询 SKU 单位销售排行
|
||||
List<GaoProductSkuUnitSalesRankView> unitSalesRank(GaoProductChartReq req);
|
||||
|
||||
/// 查询 SKU 每日履约统计
|
||||
List<GaoProductSkuFulfillmentDailyStatView> fulfillmentDaily(GaoProductChartReq req);
|
||||
|
||||
/// 查询 SKU 履约进度排行
|
||||
List<GaoProductSkuFulfillmentRankView> fulfillmentRank(GaoProductChartReq req);
|
||||
|
||||
/// 查询 SKU 每日库存变动统计
|
||||
List<GaoProductSkuStockMovementDailyStatView> stockMovementDaily(GaoProductChartReq req);
|
||||
|
||||
/// 查询 SKU 库存健康度
|
||||
List<GaoProductSkuStockHealthView> stockHealth(GaoProductChartReq req);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.imyeyu.api.modules.gao.service;
|
||||
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSku;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSkuUnit;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSelection;
|
||||
import com.imyeyu.spring.service.BaseService;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/// GAO 产品 SKU 服务
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-06
|
||||
public interface GaoProductSkuService extends BaseService<GaoProductSku, String> {
|
||||
|
||||
/// 查询 SKU 详情
|
||||
GaoProductSku detail(String id);
|
||||
|
||||
/// 按产品查询 SKU
|
||||
List<GaoProductSku> listByProductId(String productId);
|
||||
|
||||
/// 批量按产品查询 SKU
|
||||
List<GaoProductSku> listByProductIdList(Collection<String> productIdList);
|
||||
|
||||
/// 查询并校验可销售 SKU 单位
|
||||
GaoProductSkuUnitSelection getSellableSkuUnit(String storeId, String skuId, String skuUnitId);
|
||||
|
||||
/// 查询并校验 SKU 单位
|
||||
GaoProductSkuUnitSelection getSkuUnit(String storeId, String skuId, String skuUnitId, boolean purchase);
|
||||
|
||||
/// 查询 SKU 单位
|
||||
List<GaoProductSkuUnit> listUnitBySkuId(String skuId);
|
||||
|
||||
/// 停用 SKU
|
||||
void disable(String id);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.imyeyu.api.modules.gao.service;
|
||||
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSpec;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSpecValue;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/// GAO 产品规格服务
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-06
|
||||
public interface GaoProductSpecService {
|
||||
|
||||
/// 查询产品规格
|
||||
List<GaoProductSpec> listByProductId(String productId);
|
||||
|
||||
/// 查询规格项所属产品 ID
|
||||
String getProductIdBySpecId(String specId);
|
||||
|
||||
/// 创建规格项
|
||||
void createSpec(GaoProductSpec spec);
|
||||
|
||||
/// 创建规格值
|
||||
void createValue(GaoProductSpecValue value);
|
||||
}
|
||||
@@ -33,7 +33,7 @@ public interface GaoProductStockRecordService extends BaseService<GaoProductStoc
|
||||
GaoProductStockRecord getRevokeBySourceRecordId(String sourceRecordId);
|
||||
|
||||
/// 查询指定业务和产品的库存流水
|
||||
GaoProductStockRecord getByBizProduct(GaoProductStockRecord.BizType bizType, String bizId, String productId,
|
||||
GaoProductStockRecord getByBizSku(GaoProductStockRecord.BizType bizType, String bizId, String skuId,
|
||||
GaoProductStockRecord.OperationType operationType);
|
||||
|
||||
/// 保存库存操作流水及附件
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.imyeyu.api.modules.gao.entity.GaoProductStock;
|
||||
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.GaoProductStockProductView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoStockChangeItem;
|
||||
import com.imyeyu.spring.bean.PageResult;
|
||||
|
||||
@@ -18,8 +19,11 @@ public interface GaoProductStockService {
|
||||
/// 查询库存余额
|
||||
PageResult<GaoProductStock> pageByQuery(GaoProductStockPage page);
|
||||
|
||||
/// 按产品汇总查询库存
|
||||
PageResult<GaoProductStockProductView> pageProductByQuery(GaoProductStockPage page);
|
||||
|
||||
/// 查询库存余额详情
|
||||
GaoProductStock detail(String storeId, String productId);
|
||||
GaoProductStock detail(String storeId, String skuId);
|
||||
|
||||
/// 期初入库
|
||||
GaoProductStock initialIn(GaoProductStockChangeRequest request);
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.imyeyu.api.modules.gao.service;
|
||||
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProduct;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductUnit;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/// GAO 产品单位服务
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-02
|
||||
public interface GaoProductUnitService {
|
||||
|
||||
/// 校验并规范化产品单位
|
||||
///
|
||||
/// @param product 产品
|
||||
/// @param unitList 单位列表
|
||||
/// @return 规范化后的单位列表
|
||||
List<GaoProductUnit> validateAndNormalize(GaoProduct product, List<GaoProductUnit> unitList);
|
||||
|
||||
/// 按基础单位数量系数换算业务数量
|
||||
///
|
||||
/// @param unit 单位
|
||||
/// @param quantity 业务数量
|
||||
/// @return 基础单位数量
|
||||
BigDecimal convertQuantityToBase(GaoProductUnit unit, BigDecimal quantity);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.imyeyu.api.modules.gao.service;
|
||||
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerOrderProductRankView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerOrderRankView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerOrderSkuRankView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerRepeatDailyStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerRepeatIntervalStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoSalesOrderChartReq;
|
||||
@@ -36,6 +37,9 @@ public interface GaoSalesOrderChartService {
|
||||
/// 查询客户订单产品排行
|
||||
List<GaoCustomerOrderProductRankView> customerProductRank(GaoSalesOrderChartReq req);
|
||||
|
||||
/// 查询客户订单 SKU 排行
|
||||
List<GaoCustomerOrderSkuRankView> customerSkuRank(GaoSalesOrderChartReq req);
|
||||
|
||||
/// 查询销售订单状态统计
|
||||
List<GaoSalesOrderStatusStatView> status(GaoSalesOrderChartReq req);
|
||||
|
||||
|
||||
+32
-20
@@ -15,7 +15,7 @@ import com.imyeyu.api.modules.gao.service.GaoSalesOrderService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoCustomerService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoCustomerPackageService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoPackageConfigService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductSkuService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerPackageCancelRequest;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerPackagePage;
|
||||
@@ -55,7 +55,7 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
|
||||
|
||||
private final GaoCustomerPackageMapper mapper;
|
||||
private final GaoPackageConfigService packageConfigService;
|
||||
private final GaoProductService productService;
|
||||
private final GaoProductSkuService skuService;
|
||||
private final GaoCustomerService customerService;
|
||||
private final GaoSalesOrderService salesOrderService;
|
||||
private final GaoStoreScopeService storeScopeService;
|
||||
@@ -237,9 +237,13 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
|
||||
private List<GaoCustomerPackagePurchaseItem> defaultPurchaseItems(List<GaoPackageConfigItem> configItemList) {
|
||||
return configItemList.stream().map(configItem -> {
|
||||
GaoCustomerPackagePurchaseItem item = new GaoCustomerPackagePurchaseItem();
|
||||
item.setProductId(configItem.getProductId());
|
||||
item.setProductUnitId(configItem.getProductUnitId());
|
||||
item.setSkuId(configItem.getSkuId());
|
||||
item.setSkuUnitId(configItem.getSkuUnitId());
|
||||
item.setProductNameSnapshot(configItem.getProductNameSnapshot());
|
||||
item.setProductIdSnapshot(configItem.getProductIdSnapshot());
|
||||
item.setSkuCodeSnapshot(configItem.getSkuCodeSnapshot());
|
||||
item.setSkuNameSnapshot(configItem.getSkuNameSnapshot());
|
||||
item.setSpecSummarySnapshot(configItem.getSpecSummarySnapshot());
|
||||
item.setUnitSnapshot(configItem.getUnitSnapshot());
|
||||
item.setBaseQuantityPerUnitSnapshot(configItem.getBaseQuantityPerUnitSnapshot());
|
||||
item.setTotalQuantity(configItem.getDefaultQuantity());
|
||||
@@ -252,9 +256,13 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
|
||||
private GaoSalesOrderItem toOrderItem(GaoCustomerPackagePurchaseItem source) {
|
||||
GaoSalesOrderItem item = new GaoSalesOrderItem();
|
||||
item.setItemType(GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT);
|
||||
item.setProductId(source.getProductId());
|
||||
item.setProductUnitId(source.getProductUnitId());
|
||||
item.setSkuId(source.getSkuId());
|
||||
item.setSkuUnitId(source.getSkuUnitId());
|
||||
item.setProductNameSnapshot(source.getProductNameSnapshot());
|
||||
item.setProductIdSnapshot(source.getProductIdSnapshot());
|
||||
item.setSkuCodeSnapshot(source.getSkuCodeSnapshot());
|
||||
item.setSkuNameSnapshot(source.getSkuNameSnapshot());
|
||||
item.setSpecSummarySnapshot(source.getSpecSummarySnapshot());
|
||||
item.setUnitSnapshot(source.getUnitSnapshot());
|
||||
item.setBaseQuantityPerUnitSnapshot(source.getBaseQuantityPerUnitSnapshot());
|
||||
item.setQuantity(source.getTotalQuantity());
|
||||
@@ -273,33 +281,37 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
|
||||
for (int i = 0; i < itemList.size(); i++) {
|
||||
GaoCustomerPackagePurchaseItem item = itemList.get(i);
|
||||
TimiException.required(item, "购买产品明细不能为空");
|
||||
TimiException.required(item.getProductId(), "购买产品不能为空");
|
||||
TimiException.required(item.getProductUnitId(), "购买产品单位不能为空");
|
||||
TimiException.required(item.getSkuId(), "购买 SKU 不能为空");
|
||||
TimiException.required(item.getSkuUnitId(), "购买 SKU 单位不能为空");
|
||||
TimiException.required(item.getTotalQuantity(), "购买产品数量不能为空");
|
||||
TimiException.requiredTrue(0 < item.getTotalQuantity().signum(), "购买产品数量必须大于 0");
|
||||
TimiException.requiredTrue(3 >= item.getTotalQuantity().stripTrailingZeros().scale(), "购买产品数量最多保留三位小数");
|
||||
String key = item.getProductId() + "|" + item.getProductUnitId();
|
||||
TimiException.requiredTrue(keySet.add(key), "购买套餐内产品和单位不能重复");
|
||||
String key = item.getSkuId() + "|" + item.getSkuUnitId();
|
||||
TimiException.requiredTrue(keySet.add(key), "购买套餐内 SKU 和单位不能重复");
|
||||
item.setSort(TimiJava.defaultIfNull(item.getSort(), i));
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyConfigSellableItems(String storeId, List<GaoPackageConfigItem> itemList) {
|
||||
for (GaoPackageConfigItem item : itemList) {
|
||||
TimiException.required(item.getProductId(), "套餐配置缺少产品");
|
||||
TimiException.required(item.getProductUnitId(), "套餐配置缺少产品单位");
|
||||
// 通过产品库唯一选品入口重新校验产品和单位仍处于可销售状态
|
||||
productService.getSellableProductUnit(storeId, item.getProductId(), item.getProductUnitId());
|
||||
TimiException.required(item.getSkuId(), "套餐配置缺少 SKU");
|
||||
TimiException.required(item.getSkuUnitId(), "套餐配置缺少 SKU 单位");
|
||||
// 通过 SKU 服务唯一选品入口重新校验 SKU 和单位仍处于可销售状态
|
||||
skuService.getSellableSkuUnit(storeId, item.getSkuId(), item.getSkuUnitId());
|
||||
}
|
||||
}
|
||||
|
||||
private void verifySameStructureAsConfig(List<GaoCustomerPackagePurchaseItem> itemList, List<GaoPackageConfigItem> configItemList) {
|
||||
TimiException.requiredTrue(itemList.size() == configItemList.size(), "套餐权益项目必须与套餐配置一致");
|
||||
Map<String, GaoCustomerPackagePurchaseItem> itemMap = itemList.stream().collect(Collectors.toMap(item -> item.getProductId() + "|" + item.getProductUnitId(), Function.identity()));
|
||||
Map<String, GaoCustomerPackagePurchaseItem> itemMap = itemList.stream().collect(Collectors.toMap(item -> item.getSkuId() + "|" + item.getSkuUnitId(), Function.identity()));
|
||||
for (GaoPackageConfigItem configItem : configItemList) {
|
||||
GaoCustomerPackagePurchaseItem item = itemMap.get(configItem.getProductId() + "|" + configItem.getProductUnitId());
|
||||
GaoCustomerPackagePurchaseItem item = itemMap.get(configItem.getSkuId() + "|" + configItem.getSkuUnitId());
|
||||
TimiException.required(item, "套餐权益项目必须与套餐配置一致");
|
||||
item.setProductNameSnapshot(configItem.getProductNameSnapshot());
|
||||
item.setProductIdSnapshot(configItem.getProductIdSnapshot());
|
||||
item.setSkuCodeSnapshot(configItem.getSkuCodeSnapshot());
|
||||
item.setSkuNameSnapshot(configItem.getSkuNameSnapshot());
|
||||
item.setSpecSummarySnapshot(configItem.getSpecSummarySnapshot());
|
||||
item.setUnitSnapshot(configItem.getUnitSnapshot());
|
||||
item.setBaseQuantityPerUnitSnapshot(configItem.getBaseQuantityPerUnitSnapshot());
|
||||
TimiException.requiredTrue(item.getSort().equals(configItem.getSort())
|
||||
@@ -308,9 +320,9 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
|
||||
}
|
||||
|
||||
private void verifySameQuantityAsConfig(List<GaoCustomerPackagePurchaseItem> itemList, List<GaoPackageConfigItem> configItemList) {
|
||||
Map<String, GaoCustomerPackagePurchaseItem> itemMap = itemList.stream().collect(Collectors.toMap(item -> item.getProductId() + "|" + item.getProductUnitId(), Function.identity()));
|
||||
Map<String, GaoCustomerPackagePurchaseItem> itemMap = itemList.stream().collect(Collectors.toMap(item -> item.getSkuId() + "|" + item.getSkuUnitId(), Function.identity()));
|
||||
for (GaoPackageConfigItem configItem : configItemList) {
|
||||
GaoCustomerPackagePurchaseItem item = itemMap.get(configItem.getProductId() + "|" + configItem.getProductUnitId());
|
||||
GaoCustomerPackagePurchaseItem item = itemMap.get(configItem.getSkuId() + "|" + configItem.getSkuUnitId());
|
||||
TimiException.requiredTrue(item.getTotalQuantity().compareTo(configItem.getDefaultQuantity()) == 0, "普通员工不能修改套餐权益配额");
|
||||
}
|
||||
}
|
||||
@@ -327,9 +339,9 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
|
||||
List<GaoCustomerPackagePurchaseItem> requestItemList = TimiJava.defaultIfNull(request.getItemList(), List.of());
|
||||
verifyPurchaseItems(requestItemList);
|
||||
TimiException.requiredTrue(existingItemList.size() == requestItemList.size(), "requestNo 对应购买请求内容不一致");
|
||||
Map<String, GaoSalesOrderItem> existingItemMap = existingItemList.stream().collect(Collectors.toMap(item -> item.getProductId() + "|" + item.getProductUnitId(), Function.identity()));
|
||||
Map<String, GaoSalesOrderItem> existingItemMap = existingItemList.stream().collect(Collectors.toMap(item -> item.getSkuId() + "|" + item.getSkuUnitId(), Function.identity()));
|
||||
for (GaoCustomerPackagePurchaseItem item : requestItemList) {
|
||||
GaoSalesOrderItem existingItem = existingItemMap.get(item.getProductId() + "|" + item.getProductUnitId());
|
||||
GaoSalesOrderItem existingItem = existingItemMap.get(item.getSkuId() + "|" + item.getSkuUnitId());
|
||||
TimiException.required(existingItem, "requestNo 对应购买请求内容不一致");
|
||||
TimiException.requiredTrue(existingItem.getItemType() == GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT
|
||||
&& existingItem.getQuantity().compareTo(item.getTotalQuantity()) == 0
|
||||
|
||||
+50
-38
@@ -6,7 +6,8 @@ import com.imyeyu.api.modules.common.entity.Attachment;
|
||||
import com.imyeyu.api.modules.common.service.AttachmentService;
|
||||
import com.imyeyu.api.modules.gao.bean.GaoRoleCode;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProduct;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductUnit;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSku;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSkuUnit;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoSalesOrder;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoSalesOrderItem;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductStockRecord;
|
||||
@@ -17,7 +18,7 @@ import com.imyeyu.api.modules.gao.mapper.GaoOrderFulfillmentMapper;
|
||||
import com.imyeyu.api.modules.gao.service.GaoOrderFulfillmentService;
|
||||
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.GaoProductSkuService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoSalesOrderService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductStockRecordService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
|
||||
@@ -68,7 +69,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
|
||||
private final GaoOrderFulfillmentItemMapper itemMapper;
|
||||
private final GaoSalesOrderService salesOrderService;
|
||||
private final GaoProductService productService;
|
||||
private final GaoProductUnitService unitService;
|
||||
private final GaoProductSkuService skuService;
|
||||
private final GaoProductStockService productStockService;
|
||||
private final GaoProductStockRecordService stockRecordService;
|
||||
private final GaoStoreScopeService storeScopeService;
|
||||
@@ -359,8 +360,8 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
|
||||
}
|
||||
if (!stockItems.isEmpty()) {
|
||||
for (GaoStockChangeItem item : stockItems) {
|
||||
GaoProductStockRecord stockRecord = stockRecordService.getByBizProduct(GaoProductStockRecord.BizType.ORDER_FULFILLMENT,
|
||||
source.getId(), item.getProductId(), GaoProductStockRecord.OperationType.ORDER_OUT);
|
||||
GaoProductStockRecord stockRecord = stockRecordService.getByBizSku(GaoProductStockRecord.BizType.ORDER_FULFILLMENT,
|
||||
source.getId(), item.getSkuId(), GaoProductStockRecord.OperationType.ORDER_OUT);
|
||||
if (stockRecord != null) {
|
||||
item.setSourceRecordId(stockRecord.getId());
|
||||
}
|
||||
@@ -444,21 +445,24 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
|
||||
}
|
||||
|
||||
private SourceEntry toSource(GaoSalesOrder order, GaoSalesOrderItem source, BigDecimal quantity, boolean checkAvailable) {
|
||||
GaoProduct product = productService.get(source.getProductId());
|
||||
GaoProductSku sku = skuService.get(source.getSkuId());
|
||||
TimiException.required(sku, "履约来源 SKU 不存在");
|
||||
GaoProduct product = productService.get(sku.getProductId());
|
||||
TimiException.required(product, "履约来源产品不存在");
|
||||
TimiException.requiredTrue(order.getStoreId().equals(product.getStoreId()), "履约来源产品不属于当前门店");
|
||||
BigDecimal baseQuantityPerUnit = requiredBaseQuantityPerUnit(source.getBaseQuantityPerUnitSnapshot());
|
||||
GaoProductUnit unit = snapshotUnit(source.getProductUnitId(), source.getUnitSnapshot(), baseQuantityPerUnit);
|
||||
return buildSource(order, source.getId(), source.getProductId(), source.getProductUnitId(), source.getProductNameSnapshot(),
|
||||
GaoProductSkuUnit unit = snapshotUnit(source.getSkuUnitId(), source.getUnitSnapshot(), baseQuantityPerUnit);
|
||||
return buildSource(order, source.getId(), source.getSkuId(), source.getSkuUnitId(), source.getProductIdSnapshot(), source.getProductNameSnapshot(), source.getSkuCodeSnapshot(), source.getSkuNameSnapshot(), source.getSpecSummarySnapshot(),
|
||||
source.getUnitSnapshot(), baseQuantityPerUnit, quantity, source.getQuantity(), source.getReservedQuantity(), source.getFulfilledQuantity(),
|
||||
product, unit, checkAvailable);
|
||||
}
|
||||
|
||||
private SourceEntry buildSource(GaoSalesOrder order, String orderItemId, String productId, String unitId,
|
||||
String productName, String unitName, BigDecimal baseQuantityPerUnit, BigDecimal quantity, BigDecimal totalQuantity,
|
||||
BigDecimal reservedQuantity, BigDecimal fulfilledQuantity, GaoProduct product, GaoProductUnit unit,
|
||||
private SourceEntry buildSource(GaoSalesOrder order, String orderItemId, String skuId, String unitId, String productId,
|
||||
String productName, String skuCode, String skuName, String specSummary, String unitName, BigDecimal baseQuantityPerUnit, BigDecimal quantity, BigDecimal totalQuantity,
|
||||
BigDecimal reservedQuantity, BigDecimal fulfilledQuantity, GaoProduct product, GaoProductSkuUnit unit,
|
||||
boolean checkAvailable) {
|
||||
if (!Boolean.TRUE.equals(product.getAllowDecimalQuantity())) {
|
||||
GaoProductSku sku = skuService.get(skuId);
|
||||
if (!Boolean.TRUE.equals(sku.getAllowDecimalQuantity())) {
|
||||
TimiException.requiredTrue(quantity.stripTrailingZeros().scale() <= 0, "该产品不允许小数数量");
|
||||
}
|
||||
BigDecimal reservedBefore = decimal(reservedQuantity);
|
||||
@@ -467,30 +471,30 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
|
||||
BigDecimal available = decimal(totalQuantity).subtract(reservedBefore).subtract(fulfilledBefore);
|
||||
TimiException.requiredTrue(0 <= available.compareTo(quantity), "来源产品可履约数量不足");
|
||||
}
|
||||
return new SourceEntry(orderItemId, productId, unitId, productName, unitName, baseQuantityPerUnit,
|
||||
quantity, decimal(totalQuantity), reservedBefore, fulfilledBefore, product, unit);
|
||||
return new SourceEntry(orderItemId, skuId, unitId, productId, productName, skuCode, skuName, specSummary, unitName, baseQuantityPerUnit,
|
||||
quantity, decimal(totalQuantity), reservedBefore, fulfilledBefore, product, sku, unit);
|
||||
}
|
||||
|
||||
private List<GaoStockChangeItem> buildStockItems(String storeId, List<SourceEntry> sourceList) {
|
||||
Map<String, GaoStockChangeItem> itemMap = new LinkedHashMap<>();
|
||||
for (SourceEntry source : sourceList) {
|
||||
if (!Boolean.TRUE.equals(source.product().getStockEnabled())) {
|
||||
if (!Boolean.TRUE.equals(source.sku().getStockEnabled())) {
|
||||
continue;
|
||||
}
|
||||
GaoProductUnit baseUnit = productService.listUnitByProductId(source.product().getId()).stream()
|
||||
GaoProductSkuUnit baseUnit = skuService.listUnitBySkuId(source.sku().getId()).stream()
|
||||
.filter(unit -> Boolean.TRUE.equals(unit.getIsBase())).findFirst().orElse(null);
|
||||
TimiException.required(baseUnit, "实物产品缺少基础库存单位");
|
||||
BigDecimal baseQuantity = unitService.convertQuantityToBase(source.unit(), source.quantity());
|
||||
GaoStockChangeItem item = itemMap.get(source.product().getId());
|
||||
BigDecimal baseQuantity = source.quantity().multiply(source.unit().getBaseQuantityPerUnit());
|
||||
GaoStockChangeItem item = itemMap.get(source.sku().getId());
|
||||
if (item == null) {
|
||||
item = GaoStockChangeItem.of(source.product(), baseUnit, baseQuantity, baseQuantity);
|
||||
itemMap.put(source.product().getId(), item);
|
||||
item = GaoStockChangeItem.of(source.product(), source.sku(), baseUnit, baseQuantity, baseQuantity);
|
||||
itemMap.put(source.sku().getId(), item);
|
||||
} else {
|
||||
item.setQuantity(item.getQuantity().add(baseQuantity));
|
||||
item.setBaseQuantity(item.getBaseQuantity().add(baseQuantity));
|
||||
}
|
||||
}
|
||||
return itemMap.values().stream().sorted(Comparator.comparing(GaoStockChangeItem::getProductId)).toList();
|
||||
return itemMap.values().stream().sorted(Comparator.comparing(GaoStockChangeItem::getSkuId)).toList();
|
||||
}
|
||||
|
||||
private GaoOrderFulfillment lockFulfillment(String fulfillmentId, GaoSalesOrder order) {
|
||||
@@ -517,19 +521,23 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
|
||||
|
||||
private GaoOrderFulfillmentItem copyFulfillmentItem(GaoOrderFulfillmentItem source) {
|
||||
GaoOrderFulfillmentItem result = new GaoOrderFulfillmentItem();
|
||||
result.setProductId(source.getProductId());
|
||||
result.setProductUnitId(source.getProductUnitId());
|
||||
result.setSkuId(source.getSkuId());
|
||||
result.setSkuUnitId(source.getSkuUnitId());
|
||||
result.setProductIdSnapshot(source.getProductIdSnapshot());
|
||||
result.setSourceOrderItemId(source.getSourceOrderItemId());
|
||||
result.setQuantity(source.getQuantity());
|
||||
result.setBaseQuantity(source.getBaseQuantity());
|
||||
result.setProductNameSnapshot(source.getProductNameSnapshot());
|
||||
result.setSkuCodeSnapshot(source.getSkuCodeSnapshot());
|
||||
result.setSkuNameSnapshot(source.getSkuNameSnapshot());
|
||||
result.setSpecSummarySnapshot(source.getSpecSummarySnapshot());
|
||||
result.setUnitSnapshot(source.getUnitSnapshot());
|
||||
result.setBaseQuantityPerUnitSnapshot(source.getBaseQuantityPerUnitSnapshot());
|
||||
return result;
|
||||
}
|
||||
|
||||
private GaoProductUnit snapshotUnit(String id, String name, BigDecimal baseQuantityPerUnit) {
|
||||
GaoProductUnit unit = new GaoProductUnit();
|
||||
private GaoProductSkuUnit snapshotUnit(String id, String name, BigDecimal baseQuantityPerUnit) {
|
||||
GaoProductSkuUnit unit = new GaoProductSkuUnit();
|
||||
unit.setId(id);
|
||||
unit.setName(name);
|
||||
unit.setBaseQuantityPerUnit(baseQuantityPerUnit);
|
||||
@@ -619,27 +627,27 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
|
||||
if (itemList == null || itemList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<String> productIdList = itemList.stream()
|
||||
.map(GaoOrderFulfillmentItem::getProductId)
|
||||
List<String> skuIdList = itemList.stream()
|
||||
.map(GaoOrderFulfillmentItem::getSkuId)
|
||||
.filter(value -> value != null && !value.isBlank())
|
||||
.distinct()
|
||||
.toList();
|
||||
if (productIdList.isEmpty()) {
|
||||
if (skuIdList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<String, List<Attachment>> attachmentMap = attachmentService.mapByBizIdList(
|
||||
Attachment.BizType.GAO_PRODUCT,
|
||||
productIdList,
|
||||
GaoProduct.AttachType.PHOTO.name());
|
||||
Attachment.BizType.GAO_PRODUCT_SKU,
|
||||
skuIdList);
|
||||
for (GaoOrderFulfillmentItem item : itemList) {
|
||||
item.setAttachmentList(attachmentMap.getOrDefault(item.getProductId(), List.of()));
|
||||
item.setAttachmentList(attachmentMap.getOrDefault(item.getSkuId(), List.of()));
|
||||
}
|
||||
}
|
||||
|
||||
private record SourceEntry(String sourceOrderItemId, String productId, String productUnitId,
|
||||
String productNameSnapshot, String unitSnapshot, BigDecimal baseQuantityPerUnitSnapshot, BigDecimal quantity,
|
||||
BigDecimal totalQuantity, BigDecimal reservedBefore, BigDecimal fulfilledBefore, GaoProduct product,
|
||||
GaoProductUnit unit) {
|
||||
private record SourceEntry(String sourceOrderItemId, String skuId, String skuUnitId, String productIdSnapshot,
|
||||
String productNameSnapshot, String skuCodeSnapshot, String skuNameSnapshot, String specSummarySnapshot,
|
||||
String unitSnapshot, BigDecimal baseQuantityPerUnitSnapshot, BigDecimal quantity,
|
||||
BigDecimal totalQuantity, BigDecimal reservedBefore, BigDecimal fulfilledBefore, GaoProduct product, GaoProductSku sku,
|
||||
GaoProductSkuUnit unit) {
|
||||
|
||||
private String sourceId() {
|
||||
return sourceOrderItemId;
|
||||
@@ -648,8 +656,9 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
|
||||
private GaoOrderFulfillmentItem toFulfillmentItem(String fulfillmentId) {
|
||||
GaoOrderFulfillmentItem result = new GaoOrderFulfillmentItem();
|
||||
result.setFulfillmentId(fulfillmentId);
|
||||
result.setProductId(productId);
|
||||
result.setProductUnitId(productUnitId);
|
||||
result.setSkuId(skuId);
|
||||
result.setSkuUnitId(skuUnitId);
|
||||
result.setProductIdSnapshot(productIdSnapshot);
|
||||
result.setSourceOrderItemId(sourceOrderItemId);
|
||||
result.setQuantity(quantity);
|
||||
result.setBaseQuantity(quantity.multiply(baseQuantityPerUnitSnapshot).setScale(3));
|
||||
@@ -658,6 +667,9 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
|
||||
result.setFulfilledBefore(fulfilledBefore);
|
||||
result.setFulfilledAfter(fulfilledBefore);
|
||||
result.setProductNameSnapshot(productNameSnapshot);
|
||||
result.setSkuCodeSnapshot(skuCodeSnapshot);
|
||||
result.setSkuNameSnapshot(skuNameSnapshot);
|
||||
result.setSpecSummarySnapshot(specSummarySnapshot);
|
||||
result.setUnitSnapshot(unitSnapshot);
|
||||
result.setBaseQuantityPerUnitSnapshot(baseQuantityPerUnitSnapshot);
|
||||
return result;
|
||||
|
||||
+25
-17
@@ -4,16 +4,16 @@ import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoPackageConfig;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoPackageConfigItem;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProduct;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductUnit;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSku;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSkuUnit;
|
||||
import com.imyeyu.api.modules.system.entity.Logger;
|
||||
import com.imyeyu.api.modules.system.service.LoggerService;
|
||||
import com.imyeyu.api.modules.gao.mapper.GaoPackageConfigItemMapper;
|
||||
import com.imyeyu.api.modules.gao.mapper.GaoPackageConfigMapper;
|
||||
import com.imyeyu.api.modules.gao.service.GaoPackageConfigService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductUnitService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductSkuService;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoPackageConfigPage;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductUnitSelection;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSelection;
|
||||
import com.imyeyu.api.modules.user.service.UserLoginService;
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
@@ -42,8 +42,7 @@ public class GaoPackageConfigServiceImplement extends AbstractEntityService<GaoP
|
||||
|
||||
private final GaoPackageConfigMapper mapper;
|
||||
private final GaoPackageConfigItemMapper itemMapper;
|
||||
private final GaoProductService productService;
|
||||
private final GaoProductUnitService unitService;
|
||||
private final GaoProductSkuService skuService;
|
||||
private final UserLoginService userLoginService;
|
||||
private final LoggerService loggerService;
|
||||
|
||||
@@ -184,22 +183,27 @@ public class GaoPackageConfigServiceImplement extends AbstractEntityService<GaoP
|
||||
for (int i = 0; i < itemList.size(); i++) {
|
||||
GaoPackageConfigItem item = itemList.get(i);
|
||||
TimiException.required(item, "套餐产品明细不能为空");
|
||||
TimiException.required(item.getProductId(), "产品不能为空");
|
||||
TimiException.required(item.getProductUnitId(), "产品单位不能为空");
|
||||
GaoProductUnitSelection selection = productService.getSellableProductUnit(storeId, item.getProductId(), item.getProductUnitId());
|
||||
TimiException.required(item.getSkuId(), "SKU 不能为空");
|
||||
TimiException.required(item.getSkuUnitId(), "SKU 单位不能为空");
|
||||
GaoProductSkuUnitSelection selection = skuService.getSellableSkuUnit(storeId, item.getSkuId(), item.getSkuUnitId());
|
||||
GaoProduct product = selection.getProduct();
|
||||
GaoProductUnit unitEntity = selection.getUnit();
|
||||
GaoProductSku sku = selection.getSku();
|
||||
GaoProductSkuUnit unitEntity = selection.getUnit();
|
||||
BigDecimal quantity = item.getDefaultQuantity();
|
||||
TimiException.required(quantity, "产品默认数量不能为空");
|
||||
TimiException.requiredTrue(0 < quantity.signum(), "产品默认数量必须大于 0");
|
||||
TimiException.requiredTrue(3 >= quantity.stripTrailingZeros().scale(), "产品数量最多保留三位小数");
|
||||
if (!Boolean.TRUE.equals(product.getAllowDecimalQuantity())) {
|
||||
TimiException.requiredTrue(quantity.stripTrailingZeros().scale() <= 0, "该产品不允许小数数量");
|
||||
if (!Boolean.TRUE.equals(sku.getAllowDecimalQuantity())) {
|
||||
TimiException.requiredTrue(quantity.stripTrailingZeros().scale() <= 0, "该 SKU 不允许小数数量");
|
||||
}
|
||||
unitService.convertQuantityToBase(unitEntity, quantity);
|
||||
String key = item.getProductId() + "|" + item.getProductUnitId();
|
||||
TimiException.requiredTrue(keySet.add(key), "同一套餐内产品和单位不能重复");
|
||||
quantity.multiply(unitEntity.getBaseQuantityPerUnit());
|
||||
String key = item.getSkuId() + "|" + item.getSkuUnitId();
|
||||
TimiException.requiredTrue(keySet.add(key), "同一套餐内 SKU 和单位不能重复");
|
||||
item.setProductNameSnapshot(product.getName());
|
||||
item.setProductIdSnapshot(product.getId());
|
||||
item.setSkuCodeSnapshot(sku.getCode());
|
||||
item.setSkuNameSnapshot(sku.getName());
|
||||
item.setSpecSummarySnapshot(sku.getSpecSummary());
|
||||
item.setUnitSnapshot(unitEntity.getName());
|
||||
item.setBaseQuantityPerUnitSnapshot(unitEntity.getBaseQuantityPerUnit());
|
||||
item.setSort(TimiJava.defaultIfNull(item.getSort(), i));
|
||||
@@ -210,9 +214,13 @@ public class GaoPackageConfigServiceImplement extends AbstractEntityService<GaoP
|
||||
for (GaoPackageConfigItem source : TimiJava.safeIterable(itemList)) {
|
||||
GaoPackageConfigItem item = new GaoPackageConfigItem();
|
||||
item.setPackageConfigId(configId);
|
||||
item.setProductId(source.getProductId());
|
||||
item.setProductUnitId(source.getProductUnitId());
|
||||
item.setSkuId(source.getSkuId());
|
||||
item.setSkuUnitId(source.getSkuUnitId());
|
||||
item.setProductNameSnapshot(source.getProductNameSnapshot());
|
||||
item.setProductIdSnapshot(source.getProductIdSnapshot());
|
||||
item.setSkuCodeSnapshot(source.getSkuCodeSnapshot());
|
||||
item.setSkuNameSnapshot(source.getSkuNameSnapshot());
|
||||
item.setSpecSummarySnapshot(source.getSpecSummarySnapshot());
|
||||
item.setUnitSnapshot(source.getUnitSnapshot());
|
||||
item.setBaseQuantityPerUnitSnapshot(source.getBaseQuantityPerUnitSnapshot());
|
||||
item.setDefaultQuantity(source.getDefaultQuantity());
|
||||
|
||||
+26
-86
@@ -5,14 +5,12 @@ 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.GaoProductCategory;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductUnit;
|
||||
import com.imyeyu.api.modules.gao.mapper.GaoProductMapper;
|
||||
import com.imyeyu.api.modules.gao.mapper.GaoProductUnitMapper;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductCategoryService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductUnitService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductSkuService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductSpecService;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductPage;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductUnitSelection;
|
||||
import com.imyeyu.api.modules.system.entity.Logger;
|
||||
import com.imyeyu.api.modules.system.service.LoggerService;
|
||||
import com.imyeyu.api.modules.user.service.UserLoginService;
|
||||
@@ -45,10 +43,10 @@ import java.util.stream.Collectors;
|
||||
public class GaoProductServiceImplement extends AbstractEntityService<GaoProduct, String> implements GaoProductService {
|
||||
|
||||
private final GaoProductMapper mapper;
|
||||
private final GaoProductUnitMapper unitMapper;
|
||||
private final AttachmentService attachmentService;
|
||||
private final ObjectProvider<GaoProductCategoryService> categoryServiceProvider;
|
||||
private final GaoProductUnitService unitService;
|
||||
private final ObjectProvider<GaoProductSkuService> skuServiceProvider;
|
||||
private final ObjectProvider<GaoProductSpecService> specServiceProvider;
|
||||
private final UserLoginService userLoginService;
|
||||
private final LoggerService loggerService;
|
||||
|
||||
@@ -93,37 +91,9 @@ public class GaoProductServiceImplement extends AbstractEntityService<GaoProduct
|
||||
List<GaoProduct> list = mapper.selectByQuery(page);
|
||||
fillTransient(list);
|
||||
for (GaoProduct product : list) {
|
||||
product.setUnitList(product.getUnitList().stream().filter(unit -> Boolean.TRUE.equals(unit.getAllowSale())).toList());
|
||||
product.setSkuList(product.getSkuList().stream().filter(sku -> sku.getStatus() == com.imyeyu.api.modules.gao.entity.GaoProductSku.Status.ACTIVE && sku.getUnitList().stream().anyMatch(unit -> Boolean.TRUE.equals(unit.getAllowSale()))).toList());
|
||||
}
|
||||
return list.stream().filter(product -> !product.getUnitList().isEmpty()).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GaoProductUnitSelection getSellableProductUnit(String storeId, String productId, String productUnitId) {
|
||||
return getProductUnit(storeId, productId, productUnitId, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GaoProductUnitSelection getProductUnit(String storeId, String productId, String productUnitId, boolean purchase) {
|
||||
TimiException.required(storeId, "not found storeId");
|
||||
TimiException.required(productId, "not found productId");
|
||||
TimiException.required(productUnitId, "not found productUnitId");
|
||||
GaoProduct product = get(productId);
|
||||
TimiException.required(product, "not found product");
|
||||
TimiException.requiredTrue(storeId.equals(product.getStoreId()), "产品不属于当前门店");
|
||||
TimiException.requiredTrue(product.getStatus() == GaoProduct.Status.ACTIVE, "产品已停用");
|
||||
GaoProductUnit unit = unitMapper.selectForUpdate(productUnitId);
|
||||
TimiException.required(unit, "not found product unit");
|
||||
TimiException.requiredTrue(productId.equals(unit.getProductId()), "产品单位不属于当前产品");
|
||||
TimiException.requiredTrue(!purchase || Boolean.TRUE.equals(unit.getAllowPurchase()), "产品单位不允许采购入库");
|
||||
TimiException.requiredTrue(purchase || Boolean.TRUE.equals(unit.getAllowSale()), "产品单位不允许销售");
|
||||
return new GaoProductUnitSelection(product, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GaoProductUnit> listUnitByProductId(String productId) {
|
||||
TimiException.required(productId, "not found productId");
|
||||
return unitMapper.selectByProductId(productId);
|
||||
return list.stream().filter(product -> !product.getSkuList().isEmpty()).toList();
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@@ -132,7 +102,7 @@ public class GaoProductServiceImplement extends AbstractEntityService<GaoProduct
|
||||
loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_CREATE", product, () -> {
|
||||
prepare(product, null);
|
||||
super.create(product);
|
||||
saveUnits(product.getId(), product.getUnitList(), List.of());
|
||||
saveSkus(product, product.getSkuList());
|
||||
attachmentService.updateByBizId(Attachment.BizType.GAO_PRODUCT, product.getId(), product.getAttachmentList());
|
||||
return product;
|
||||
});
|
||||
@@ -151,17 +121,14 @@ public class GaoProductServiceImplement extends AbstractEntityService<GaoProduct
|
||||
product.setName(TimiJava.defaultIfNull(request.getName(), product.getName()));
|
||||
product.setProductType(TimiJava.defaultIfNull(request.getProductType(), product.getProductType()));
|
||||
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.setMinStockQuantity(request.getMinStockQuantity());
|
||||
product.setSort(TimiJava.defaultIfNull(request.getSort(), product.getSort()));
|
||||
product.setRemark(request.getRemark());
|
||||
List<GaoProductUnit> existingUnitList = unitMapper.selectByProductId(product.getId());
|
||||
product.setUnitList(request.getUnitList() == null ? existingUnitList : request.getUnitList());
|
||||
product.setSkuList(request.getSkuList() == null ? skuServiceProvider.getObject().listByProductId(product.getId()) : request.getSkuList());
|
||||
prepare(product, product.getId());
|
||||
super.update(product);
|
||||
if (request.getUnitList() != null) {
|
||||
saveUnits(product.getId(), product.getUnitList(), existingUnitList);
|
||||
if (request.getSkuList() != null) {
|
||||
saveSkus(product, request.getSkuList());
|
||||
}
|
||||
if (request.getAttachmentList() != null) {
|
||||
attachmentService.updateByBizId(Attachment.BizType.GAO_PRODUCT, product.getId(), request.getAttachmentList());
|
||||
@@ -176,6 +143,7 @@ public class GaoProductServiceImplement extends AbstractEntityService<GaoProduct
|
||||
loggerService.execute(Logger.Module.GAO, "GAO_PRODUCT_DISABLE", id, () -> {
|
||||
GaoProduct product = get(id);
|
||||
TimiException.required(product, "not found product");
|
||||
TimiException.requiredTrue(skuServiceProvider.getObject().listByProductId(id).stream().noneMatch(sku -> sku.getStatus() == com.imyeyu.api.modules.gao.entity.GaoProductSku.Status.ACTIVE), "请先停用产品的全部 SKU");
|
||||
product.setStatus(GaoProduct.Status.INACTIVE);
|
||||
product.setUpdatedBy(userLoginService.getRequireLoginUserId());
|
||||
product.setUpdatedAt(Time.now());
|
||||
@@ -202,15 +170,7 @@ public class GaoProductServiceImplement extends AbstractEntityService<GaoProduct
|
||||
product.setProductType(TimiJava.defaultIfNull(product.getProductType(), GaoProduct.ProductType.GOODS));
|
||||
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));
|
||||
if (!Boolean.TRUE.equals(product.getStockEnabled())) {
|
||||
TimiException.requiredNull(product.getMinStockQuantity(), "未启用库存的产品不能填写最低库存");
|
||||
}
|
||||
if (product.getMinStockQuantity() != null) {
|
||||
TimiException.requiredTrue(0 <= product.getMinStockQuantity().signum(), "最低库存不能为负数");
|
||||
TimiException.requiredTrue(3 >= product.getMinStockQuantity().scale(), "最低库存最多保留三位小数");
|
||||
}
|
||||
product.setSort(TimiJava.defaultIfNull(product.getSort(), 0));
|
||||
TimiException.requiredNull(mapper.selectByStoreIdAndCode(product.getStoreId(), product.getCode(), excludeId), "门店内产品编码不能重复");
|
||||
if (product.getCategoryId() != null) {
|
||||
@@ -218,42 +178,21 @@ public class GaoProductServiceImplement extends AbstractEntityService<GaoProduct
|
||||
TimiException.required(category, "产品分类不存在");
|
||||
TimiException.requiredTrue(product.getStoreId().equals(category.getStoreId()), "产品分类不属于当前门店");
|
||||
}
|
||||
product.setUnitList(unitService.validateAndNormalize(product, product.getUnitList()));
|
||||
product.setUpdatedBy(userLoginService.getRequireLoginUserId());
|
||||
}
|
||||
|
||||
private void saveUnits(String productId, List<GaoProductUnit> unitList, List<GaoProductUnit> existingUnitList) {
|
||||
Map<String, GaoProductUnit> existingById = existingUnitList.stream()
|
||||
.collect(Collectors.toMap(GaoProductUnit::getId, item -> item));
|
||||
Map<String, GaoProductUnit> existingByName = existingUnitList.stream()
|
||||
.collect(Collectors.toMap(GaoProductUnit::getName, item -> item));
|
||||
Set<String> retainedIdSet = new HashSet<>();
|
||||
for (GaoProductUnit source : unitList) {
|
||||
GaoProductUnit existing = source.getId() == null ? existingByName.get(source.getName()) : existingById.get(source.getId());
|
||||
if (source.getId() != null && existing == null && !existingUnitList.isEmpty()) {
|
||||
throw new TimiException(TimiCode.ERROR, "产品单位不存在或不属于当前产品");
|
||||
}
|
||||
GaoProductUnit unit = new GaoProductUnit();
|
||||
unit.setId(existing == null ? null : existing.getId());
|
||||
unit.setProductId(productId);
|
||||
unit.setName(source.getName());
|
||||
unit.setBaseQuantityPerUnit(source.getBaseQuantityPerUnit());
|
||||
unit.setSalePrice(source.getSalePrice());
|
||||
unit.setIsBase(source.getIsBase());
|
||||
unit.setAllowPurchase(source.getAllowPurchase());
|
||||
unit.setAllowSale(source.getAllowSale());
|
||||
unit.setSort(source.getSort());
|
||||
if (existing == null) {
|
||||
unitMapper.insert(unit);
|
||||
} else {
|
||||
retainedIdSet.add(existing.getId());
|
||||
TimiException.requiredTrue(1 == unitMapper.updateUnit(unit), "产品单位更新失败");
|
||||
}
|
||||
private void saveSkus(GaoProduct product, List<com.imyeyu.api.modules.gao.entity.GaoProductSku> skuList) {
|
||||
List<com.imyeyu.api.modules.gao.entity.GaoProductSku> sourceList = skuList;
|
||||
if (sourceList == null || sourceList.isEmpty()) {
|
||||
com.imyeyu.api.modules.gao.entity.GaoProductSku sku = new com.imyeyu.api.modules.gao.entity.GaoProductSku();
|
||||
sku.setProductId(product.getId()); sku.setCode(product.getCode()); sku.setName(product.getName());
|
||||
com.imyeyu.api.modules.gao.entity.GaoProductSkuUnit unit = new com.imyeyu.api.modules.gao.entity.GaoProductSkuUnit();
|
||||
unit.setName("件"); unit.setIsBase(true); unit.setBaseQuantityPerUnit(BigDecimal.ONE);
|
||||
sku.setUnitList(List.of(unit)); sourceList = List.of(sku);
|
||||
}
|
||||
for (GaoProductUnit existing : existingUnitList) {
|
||||
if (!retainedIdSet.contains(existing.getId())) {
|
||||
unitMapper.delete(existing.getId());
|
||||
}
|
||||
for (com.imyeyu.api.modules.gao.entity.GaoProductSku source : sourceList) {
|
||||
source.setProductId(product.getId());
|
||||
if (source.getId() == null) skuServiceProvider.getObject().create(source); else skuServiceProvider.getObject().update(source);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,11 +201,12 @@ public class GaoProductServiceImplement extends AbstractEntityService<GaoProduct
|
||||
return;
|
||||
}
|
||||
List<String> productIdList = list.stream().map(GaoProduct::getId).toList();
|
||||
Map<String, List<GaoProductUnit>> unitMap = unitMapper.selectByProductIdList(productIdList).stream()
|
||||
.collect(Collectors.groupingBy(GaoProductUnit::getProductId));
|
||||
Map<String, List<Attachment>> attachmentMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_PRODUCT, productIdList);
|
||||
Map<String, List<com.imyeyu.api.modules.gao.entity.GaoProductSku>> skuMap = skuServiceProvider.getObject().listByProductIdList(productIdList).stream()
|
||||
.collect(Collectors.groupingBy(com.imyeyu.api.modules.gao.entity.GaoProductSku::getProductId));
|
||||
for (GaoProduct product : list) {
|
||||
product.setUnitList(unitMap.getOrDefault(product.getId(), List.of()));
|
||||
product.setSkuList(skuMap.getOrDefault(product.getId(), List.of()));
|
||||
product.setSpecList(specServiceProvider.getObject().listByProductId(product.getId()));
|
||||
product.setAttachmentList(attachmentMap.getOrDefault(product.getId(), List.of()));
|
||||
if (product.getCategoryId() != null) {
|
||||
product.setCategory(categoryServiceProvider.getObject().get(product.getCategoryId()));
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
package com.imyeyu.api.modules.gao.service.implement;
|
||||
|
||||
import com.imyeyu.api.modules.gao.mapper.GaoProductSkuChartMapper;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductSkuChartService;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductChartReq;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuFulfillmentDailyStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuFulfillmentRankView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuSalesDailyStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuSalesRankView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuStockHealthView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuStockMovementDailyStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSalesRankView;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/// GAO SKU 图表服务实现
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-07
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class GaoProductSkuChartServiceImplement implements GaoProductSkuChartService {
|
||||
|
||||
private static final int DEFAULT_TOP_N = 20;
|
||||
private static final int MAX_TOP_N = 100;
|
||||
|
||||
private final GaoProductSkuChartMapper mapper;
|
||||
|
||||
@Override
|
||||
public List<GaoProductSkuSalesDailyStatView> salesDaily(GaoProductChartReq req) {
|
||||
checkRangeReq(req);
|
||||
return mapper.selectSalesDaily(req);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GaoProductSkuSalesRankView> salesRank(GaoProductChartReq req) {
|
||||
checkRangeReq(req);
|
||||
normalizeTopN(req);
|
||||
return mapper.selectSalesRank(req);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GaoProductSkuUnitSalesRankView> unitSalesRank(GaoProductChartReq req) {
|
||||
checkRangeReq(req);
|
||||
normalizeTopN(req);
|
||||
return mapper.selectUnitSalesRank(req);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GaoProductSkuFulfillmentDailyStatView> fulfillmentDaily(GaoProductChartReq req) {
|
||||
checkRangeReq(req);
|
||||
return mapper.selectFulfillmentDaily(req);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GaoProductSkuFulfillmentRankView> fulfillmentRank(GaoProductChartReq req) {
|
||||
checkRangeReq(req);
|
||||
normalizeTopN(req);
|
||||
return mapper.selectFulfillmentRank(req);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GaoProductSkuStockMovementDailyStatView> stockMovementDaily(GaoProductChartReq req) {
|
||||
checkRangeReq(req);
|
||||
return mapper.selectStockMovementDaily(req);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GaoProductSkuStockHealthView> stockHealth(GaoProductChartReq req) {
|
||||
checkStoreReq(req);
|
||||
normalizeTopN(req);
|
||||
return mapper.selectStockHealth(req);
|
||||
}
|
||||
|
||||
private void checkRangeReq(GaoProductChartReq req) {
|
||||
checkStoreReq(req);
|
||||
TimiException.required(req.getBeginAt(), "SKU 图表开始时间不能为空");
|
||||
TimiException.required(req.getEndAt(), "SKU 图表结束时间不能为空");
|
||||
TimiException.requiredTrue(req.getBeginAt() <= req.getEndAt(), "SKU 图表结束时间早于开始时间");
|
||||
}
|
||||
|
||||
private void checkStoreReq(GaoProductChartReq req) {
|
||||
TimiException.required(req, "SKU 图表请求不能为空");
|
||||
TimiException.required(req.getStoreId(), "SKU 图表门店不能为空");
|
||||
}
|
||||
|
||||
private void normalizeTopN(GaoProductChartReq req) {
|
||||
int topN = req.getTopN() == null ? DEFAULT_TOP_N : req.getTopN();
|
||||
TimiException.requiredTrue(0 < topN && topN < MAX_TOP_N + 1, "SKU 图表排行数量无效");
|
||||
req.setTopN(topN);
|
||||
}
|
||||
}
|
||||
+349
@@ -0,0 +1,349 @@
|
||||
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.GaoProductSku;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSkuSpecValue;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSkuUnit;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSpec;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSpecValue;
|
||||
import com.imyeyu.api.modules.gao.mapper.GaoProductSkuMapper;
|
||||
import com.imyeyu.api.modules.gao.mapper.GaoProductSkuSpecValueMapper;
|
||||
import com.imyeyu.api.modules.gao.mapper.GaoProductSkuUnitMapper;
|
||||
import com.imyeyu.api.modules.gao.mapper.GaoProductSpecMapper;
|
||||
import com.imyeyu.api.modules.gao.mapper.GaoProductSpecValueMapper;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductSkuService;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSelection;
|
||||
import com.imyeyu.api.modules.user.service.UserLoginService;
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import com.imyeyu.java.bean.timi.TimiCode;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import com.imyeyu.spring.service.AbstractEntityService;
|
||||
import com.imyeyu.utils.Time;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/// GAO 产品 SKU 服务实现
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-06
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class GaoProductSkuServiceImplement extends AbstractEntityService<GaoProductSku, String> implements GaoProductSkuService {
|
||||
|
||||
private static final BigDecimal ONE = BigDecimal.ONE;
|
||||
|
||||
private final GaoProductSkuMapper mapper;
|
||||
private final GaoProductSkuUnitMapper unitMapper;
|
||||
private final GaoProductSkuSpecValueMapper skuSpecValueMapper;
|
||||
private final GaoProductSpecMapper specMapper;
|
||||
private final GaoProductSpecValueMapper valueMapper;
|
||||
private final AttachmentService attachmentService;
|
||||
private final ObjectProvider<GaoProductService> productServiceProvider;
|
||||
private final UserLoginService userLoginService;
|
||||
|
||||
@Override
|
||||
protected BaseMapper<GaoProductSku, String> mapper() {
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GaoProductSku detail(String id) {
|
||||
GaoProductSku sku = get(id);
|
||||
TimiException.required(sku, "not found product sku");
|
||||
fillTransient(List.of(sku));
|
||||
return sku;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GaoProductSku> listByProductId(String productId) {
|
||||
TimiException.required(productId, "not found productId");
|
||||
List<GaoProductSku> result = mapper.selectByProductId(productId);
|
||||
fillTransient(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GaoProductSku> listByProductIdList(Collection<String> productIdList) {
|
||||
if (productIdList == null || productIdList.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
List<GaoProductSku> result = mapper.selectByProductIdList(productIdList);
|
||||
fillTransient(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GaoProductSkuUnitSelection getSellableSkuUnit(String storeId, String skuId, String skuUnitId) {
|
||||
return getSkuUnit(storeId, skuId, skuUnitId, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GaoProductSkuUnitSelection getSkuUnit(String storeId, String skuId, String skuUnitId, boolean purchase) {
|
||||
TimiException.required(storeId, "not found storeId");
|
||||
TimiException.required(skuId, "not found skuId");
|
||||
TimiException.required(skuUnitId, "not found skuUnitId");
|
||||
GaoProductSku sku = mapper.selectForUpdate(skuId);
|
||||
TimiException.required(sku, "not found product sku");
|
||||
TimiException.requiredTrue(storeId.equals(sku.getStoreId()), "SKU 不属于当前门店");
|
||||
TimiException.requiredTrue(sku.getStatus() == GaoProductSku.Status.ACTIVE, "SKU 已停用");
|
||||
GaoProduct product = productServiceProvider.getObject().get(sku.getProductId());
|
||||
TimiException.required(product, "not found product");
|
||||
TimiException.requiredTrue(product.getStatus() == GaoProduct.Status.ACTIVE, "产品已停用");
|
||||
GaoProductSkuUnit unit = unitMapper.selectForUpdate(skuUnitId);
|
||||
TimiException.required(unit, "not found product sku unit");
|
||||
TimiException.requiredTrue(skuId.equals(unit.getSkuId()), "SKU 单位不属于当前 SKU");
|
||||
TimiException.requiredTrue(!purchase || Boolean.TRUE.equals(unit.getAllowPurchase()), "SKU 单位不允许采购入库");
|
||||
TimiException.requiredTrue(purchase || Boolean.TRUE.equals(unit.getAllowSale()), "SKU 单位不允许销售");
|
||||
return new GaoProductSkuUnitSelection(product, sku, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GaoProductSkuUnit> listUnitBySkuId(String skuId) {
|
||||
TimiException.required(skuId, "not found skuId");
|
||||
return unitMapper.selectBySkuId(skuId);
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public void create(GaoProductSku sku) {
|
||||
prepare(sku, null);
|
||||
super.create(sku);
|
||||
saveSpecValues(sku.getId(), sku.getSpecValueIdList());
|
||||
saveUnits(sku.getId(), sku.getUnitList(), List.of());
|
||||
attachmentService.updateByBizId(Attachment.BizType.GAO_PRODUCT_SKU, sku.getId(), sku.getAttachmentList());
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public void update(GaoProductSku request) {
|
||||
TimiException.required(request, "not found product sku");
|
||||
TimiException.required(request.getId(), "not found product sku.id");
|
||||
GaoProductSku sku = get(request.getId());
|
||||
TimiException.required(sku, "not found product sku");
|
||||
sku.setCode(TimiJava.defaultIfNull(request.getCode(), sku.getCode()));
|
||||
sku.setBarcode(request.getBarcode());
|
||||
sku.setName(TimiJava.defaultIfNull(request.getName(), sku.getName()));
|
||||
sku.setSpecSummary(TimiJava.defaultIfNull(request.getSpecSummary(), sku.getSpecSummary()));
|
||||
sku.setStockEnabled(TimiJava.defaultIfNull(request.getStockEnabled(), sku.getStockEnabled()));
|
||||
sku.setAllowDecimalQuantity(TimiJava.defaultIfNull(request.getAllowDecimalQuantity(), sku.getAllowDecimalQuantity()));
|
||||
sku.setMinStockQuantity(request.getMinStockQuantity());
|
||||
sku.setStatus(TimiJava.defaultIfNull(request.getStatus(), sku.getStatus()));
|
||||
sku.setSort(TimiJava.defaultIfNull(request.getSort(), sku.getSort()));
|
||||
sku.setRemark(request.getRemark());
|
||||
sku.setSpecValueIdList(request.getSpecValueIdList() == null ? currentSpecValueIds(sku.getId()) : request.getSpecValueIdList());
|
||||
sku.setUnitList(request.getUnitList() == null ? unitMapper.selectBySkuId(sku.getId()) : request.getUnitList());
|
||||
prepare(sku, sku.getId());
|
||||
super.update(sku);
|
||||
if (request.getSpecValueIdList() != null) {
|
||||
saveSpecValues(sku.getId(), sku.getSpecValueIdList());
|
||||
}
|
||||
if (request.getUnitList() != null) {
|
||||
saveUnits(sku.getId(), sku.getUnitList(), unitMapper.selectBySkuId(sku.getId()));
|
||||
}
|
||||
if (request.getAttachmentList() != null) {
|
||||
attachmentService.updateByBizId(Attachment.BizType.GAO_PRODUCT_SKU, sku.getId(), request.getAttachmentList());
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public void disable(String id) {
|
||||
GaoProductSku sku = get(id);
|
||||
TimiException.required(sku, "not found product sku");
|
||||
sku.setStatus(GaoProductSku.Status.INACTIVE);
|
||||
sku.setUpdatedBy(userLoginService.getRequireLoginUserId());
|
||||
sku.setUpdatedAt(Time.now());
|
||||
super.update(sku);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String id) {
|
||||
disable(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy(String id) {
|
||||
throw new TimiException(TimiCode.ERROR, "已建立 SKU 的产品不允许物理删除");
|
||||
}
|
||||
|
||||
private void prepare(GaoProductSku sku, String excludeId) {
|
||||
TimiException.required(sku, "not found product sku");
|
||||
GaoProduct product = productServiceProvider.getObject().get(sku.getProductId());
|
||||
TimiException.required(product, "产品不存在");
|
||||
sku.setStoreId(product.getStoreId());
|
||||
sku.setCode(requiredText(sku.getCode(), "SKU 编码不能为空"));
|
||||
sku.setName(requiredText(sku.getName(), "SKU 名称不能为空"));
|
||||
sku.setBarcode(trimToNull(sku.getBarcode()));
|
||||
sku.setSpecSummary(trimToNull(sku.getSpecSummary()));
|
||||
sku.setStockEnabled(TimiJava.defaultIfNull(sku.getStockEnabled(), product.getStockEnabled()));
|
||||
TimiException.requiredTrue(product.getProductType() != GaoProduct.ProductType.SERVICE || !sku.getStockEnabled(), "SERVICE 产品的 SKU 不能启用库存");
|
||||
sku.setAllowDecimalQuantity(TimiJava.defaultIfNull(sku.getAllowDecimalQuantity(), false));
|
||||
sku.setStatus(TimiJava.defaultIfNull(sku.getStatus(), GaoProductSku.Status.ACTIVE));
|
||||
if (!Boolean.TRUE.equals(sku.getStockEnabled())) {
|
||||
TimiException.requiredNull(sku.getMinStockQuantity(), "未启用库存的 SKU 不能填写最低库存");
|
||||
}
|
||||
if (sku.getMinStockQuantity() != null) {
|
||||
TimiException.requiredTrue(0 <= sku.getMinStockQuantity().signum(), "最低库存不能为负数");
|
||||
TimiException.requiredTrue(3 >= sku.getMinStockQuantity().scale(), "最低库存最多保留三位小数");
|
||||
}
|
||||
sku.setSort(TimiJava.defaultIfNull(sku.getSort(), 0));
|
||||
TimiException.requiredNull(mapper.selectByStoreIdAndCode(sku.getStoreId(), sku.getCode(), excludeId), "门店内 SKU 编码不能重复");
|
||||
if (sku.getBarcode() != null) {
|
||||
TimiException.requiredNull(mapper.selectByStoreIdAndBarcode(sku.getStoreId(), sku.getBarcode(), excludeId), "门店内 SKU 条码不能重复");
|
||||
}
|
||||
validateSpecValues(sku);
|
||||
sku.setSpecValueKey(buildSpecValueKey(sku.getSpecValueIdList()));
|
||||
TimiException.requiredNull(mapper.selectByProductIdAndSpecValueKey(sku.getProductId(), sku.getSpecValueKey(), excludeId), "同一产品的 SKU 规格组合不能重复");
|
||||
sku.setUnitList(validateAndNormalizeUnits(sku.getUnitList()));
|
||||
sku.setUpdatedBy(userLoginService.getRequireLoginUserId());
|
||||
}
|
||||
|
||||
private void validateSpecValues(GaoProductSku sku) {
|
||||
List<String> specValueIdList = sku.getSpecValueIdList() == null ? List.of() : sku.getSpecValueIdList();
|
||||
TimiException.requiredTrue(specValueIdList.size() == new HashSet<>(specValueIdList).size(), "SKU 规格值不能重复");
|
||||
Set<String> specIdSet = new HashSet<>();
|
||||
for (String specValueId : specValueIdList) {
|
||||
GaoProductSpecValue value = valueMapper.selectById(specValueId);
|
||||
TimiException.required(value, "SKU 规格值不存在");
|
||||
TimiException.requiredTrue(Boolean.TRUE.equals(value.getEnabled()), "SKU 规格值已停用");
|
||||
TimiException.requiredTrue(sku.getProductId().equals(specMapper.selectProductIdBySpecValueId(specValueId)), "SKU 规格值不属于当前产品");
|
||||
TimiException.requiredTrue(specIdSet.add(value.getSpecId()), "同一规格项只能选择一个规格值");
|
||||
}
|
||||
Set<String> requiredSpecIdSet = specMapper.selectByProductId(sku.getProductId()).stream().map(GaoProductSpec::getId).collect(Collectors.toSet());
|
||||
TimiException.requiredTrue(requiredSpecIdSet.equals(specIdSet), "SKU 必须为每个规格项选择一个规格值");
|
||||
}
|
||||
|
||||
private String buildSpecValueKey(List<String> specValueIdList) {
|
||||
if (specValueIdList == null || specValueIdList.isEmpty()) {
|
||||
return "DEFAULT";
|
||||
}
|
||||
return specValueIdList.stream().sorted().collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
private List<GaoProductSkuUnit> validateAndNormalizeUnits(List<GaoProductSkuUnit> sourceList) {
|
||||
TimiException.requiredTrue(sourceList != null && !sourceList.isEmpty(), "SKU 至少需要一个单位");
|
||||
List<GaoProductSkuUnit> result = new ArrayList<>();
|
||||
Set<String> nameSet = new HashSet<>();
|
||||
Set<String> idSet = new HashSet<>();
|
||||
int baseCount = 0;
|
||||
for (int i = 0; i < sourceList.size(); i++) {
|
||||
GaoProductSkuUnit source = sourceList.get(i);
|
||||
TimiException.required(source, "SKU 单位不能为空");
|
||||
GaoProductSkuUnit unit = new GaoProductSkuUnit();
|
||||
unit.setId(source.getId());
|
||||
if (unit.getId() != null) {
|
||||
TimiException.requiredTrue(idSet.add(unit.getId()), "SKU 单位 ID 不能重复");
|
||||
}
|
||||
unit.setName(requiredText(source.getName(), "SKU 单位名称不能为空"));
|
||||
TimiException.requiredTrue(nameSet.add(unit.getName()), "同一 SKU 的单位名称不能重复");
|
||||
unit.setBaseQuantityPerUnit(TimiJava.defaultIfNull(source.getBaseQuantityPerUnit(), ONE));
|
||||
TimiException.requiredTrue(0 < unit.getBaseQuantityPerUnit().signum(), "基础单位数量系数必须大于 0");
|
||||
TimiException.requiredTrue(6 >= unit.getBaseQuantityPerUnit().scale(), "基础单位数量系数最多保留六位小数");
|
||||
if (source.getSalePrice() != null) {
|
||||
TimiException.requiredTrue(0 <= source.getSalePrice(), "单位建议售价不能为负数");
|
||||
}
|
||||
unit.setSalePrice(source.getSalePrice());
|
||||
unit.setIsBase(Boolean.TRUE.equals(source.getIsBase()));
|
||||
unit.setAllowPurchase(TimiJava.defaultIfNull(source.getAllowPurchase(), true));
|
||||
unit.setAllowSale(TimiJava.defaultIfNull(source.getAllowSale(), true));
|
||||
unit.setSort(TimiJava.defaultIfNull(source.getSort(), i));
|
||||
if (unit.getIsBase()) {
|
||||
baseCount++;
|
||||
TimiException.requiredTrue(1 >= baseCount, "一个 SKU 只能有一个基础单位");
|
||||
TimiException.requiredTrue(ONE.compareTo(unit.getBaseQuantityPerUnit()) == 0, "基础单位数量系数必须为 1");
|
||||
}
|
||||
result.add(unit);
|
||||
}
|
||||
TimiException.requiredTrue(0 < baseCount, "SKU 必须有一个基础单位");
|
||||
return result;
|
||||
}
|
||||
|
||||
private void saveUnits(String skuId, List<GaoProductSkuUnit> unitList, List<GaoProductSkuUnit> existingUnitList) {
|
||||
Map<String, GaoProductSkuUnit> existingById = existingUnitList.stream().collect(Collectors.toMap(GaoProductSkuUnit::getId, item -> item));
|
||||
Set<String> retainedIdSet = new HashSet<>();
|
||||
for (GaoProductSkuUnit source : unitList) {
|
||||
GaoProductSkuUnit existing = source.getId() == null ? null : existingById.get(source.getId());
|
||||
if (source.getId() != null && existing == null) {
|
||||
throw new TimiException(TimiCode.ERROR, "SKU 单位不存在或不属于当前 SKU");
|
||||
}
|
||||
GaoProductSkuUnit unit = new GaoProductSkuUnit();
|
||||
unit.setId(existing == null ? null : existing.getId());
|
||||
unit.setSkuId(skuId);
|
||||
unit.setName(source.getName());
|
||||
unit.setBaseQuantityPerUnit(source.getBaseQuantityPerUnit());
|
||||
unit.setSalePrice(source.getSalePrice());
|
||||
unit.setIsBase(source.getIsBase());
|
||||
unit.setAllowPurchase(source.getAllowPurchase());
|
||||
unit.setAllowSale(source.getAllowSale());
|
||||
unit.setSort(source.getSort());
|
||||
if (existing == null) {
|
||||
unitMapper.insert(unit);
|
||||
} else {
|
||||
retainedIdSet.add(existing.getId());
|
||||
TimiException.requiredTrue(1 == unitMapper.updateUnit(unit), "SKU 单位更新失败");
|
||||
}
|
||||
}
|
||||
for (GaoProductSkuUnit existing : existingUnitList) {
|
||||
if (!retainedIdSet.contains(existing.getId())) {
|
||||
unitMapper.delete(existing.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveSpecValues(String skuId, List<String> specValueIdList) {
|
||||
skuSpecValueMapper.deleteBySkuId(skuId);
|
||||
for (String specValueId : specValueIdList == null ? List.<String>of() : specValueIdList) {
|
||||
GaoProductSkuSpecValue relation = new GaoProductSkuSpecValue();
|
||||
relation.setSkuId(skuId);
|
||||
relation.setSpecValueId(specValueId);
|
||||
skuSpecValueMapper.insert(relation);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> currentSpecValueIds(String skuId) {
|
||||
return skuSpecValueMapper.selectBySkuIdList(List.of(skuId)).stream().map(GaoProductSkuSpecValue::getSpecValueId).toList();
|
||||
}
|
||||
|
||||
private void fillTransient(List<GaoProductSku> skuList) {
|
||||
if (skuList.isEmpty()) return;
|
||||
List<String> skuIdList = skuList.stream().map(GaoProductSku::getId).toList();
|
||||
Map<String, List<GaoProductSkuUnit>> unitMap = unitMapper.selectBySkuIdList(skuIdList).stream().collect(Collectors.groupingBy(GaoProductSkuUnit::getSkuId));
|
||||
Map<String, List<String>> specValueIdMap = skuSpecValueMapper.selectBySkuIdList(skuIdList)
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(GaoProductSkuSpecValue::getSkuId, Collectors.mapping(GaoProductSkuSpecValue::getSpecValueId, Collectors.toList())));
|
||||
Map<String, List<Attachment>> attachmentMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_PRODUCT_SKU, skuIdList);
|
||||
for (GaoProductSku sku : skuList) {
|
||||
sku.setUnitList(unitMap.getOrDefault(sku.getId(), List.of()));
|
||||
sku.setSpecValueIdList(specValueIdMap.getOrDefault(sku.getId(), List.of()));
|
||||
sku.setAttachmentList(attachmentMap.getOrDefault(sku.getId(), List.of()));
|
||||
}
|
||||
}
|
||||
|
||||
private String requiredText(String value, String message) {
|
||||
TimiException.required(value, message);
|
||||
String result = value.trim();
|
||||
TimiException.requiredTrue(!result.isEmpty(), message);
|
||||
return result;
|
||||
}
|
||||
|
||||
private String trimToNull(String value) {
|
||||
return value == null || value.trim().isEmpty() ? null : value.trim();
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package com.imyeyu.api.modules.gao.service.implement;
|
||||
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProduct;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSpec;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSpecValue;
|
||||
import com.imyeyu.api.modules.gao.mapper.GaoProductSpecMapper;
|
||||
import com.imyeyu.api.modules.gao.mapper.GaoProductSpecValueMapper;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductSpecService;
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/// GAO 产品规格服务实现
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-06
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class GaoProductSpecServiceImplement implements GaoProductSpecService {
|
||||
|
||||
private final GaoProductSpecMapper specMapper;
|
||||
private final GaoProductSpecValueMapper valueMapper;
|
||||
private final GaoProductService productService;
|
||||
|
||||
@Override
|
||||
public List<GaoProductSpec> listByProductId(String productId) {
|
||||
List<GaoProductSpec> specList = specMapper.selectByProductId(productId);
|
||||
Map<String, List<GaoProductSpecValue>> valueMap = valueMapper.selectBySpecIdList(specList.stream().map(GaoProductSpec::getId).toList()).stream().collect(Collectors.groupingBy(GaoProductSpecValue::getSpecId));
|
||||
for (GaoProductSpec spec : specList) spec.setValueList(valueMap.getOrDefault(spec.getId(), List.of()));
|
||||
return specList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProductIdBySpecId(String specId) {
|
||||
return specMapper.selectProductIdBySpecId(specId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createSpec(GaoProductSpec spec) {
|
||||
TimiException.required(spec, "not found product spec");
|
||||
GaoProduct product = productService.get(spec.getProductId());
|
||||
TimiException.required(product, "产品不存在");
|
||||
spec.setName(requiredText(spec.getName(), "规格名称不能为空"));
|
||||
spec.setSort(TimiJava.defaultIfNull(spec.getSort(), 0));
|
||||
specMapper.insert(spec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createValue(GaoProductSpecValue value) {
|
||||
TimiException.required(value, "not found product spec value");
|
||||
TimiException.required(specMapper.selectProductIdBySpecId(value.getSpecId()), "规格项不存在");
|
||||
value.setName(requiredText(value.getName(), "规格值名称不能为空"));
|
||||
value.setEnabled(TimiJava.defaultIfNull(value.getEnabled(), true));
|
||||
value.setSort(TimiJava.defaultIfNull(value.getSort(), 0));
|
||||
valueMapper.insert(value);
|
||||
}
|
||||
|
||||
private String requiredText(String value, String message) { TimiException.required(value, message); String result = value.trim(); TimiException.requiredTrue(!result.isEmpty(), message); return result; }
|
||||
}
|
||||
+3
-3
@@ -86,13 +86,13 @@ public class GaoProductStockRecordServiceImplement extends AbstractEntityService
|
||||
}
|
||||
|
||||
@Override
|
||||
public GaoProductStockRecord getByBizProduct(GaoProductStockRecord.BizType bizType, String bizId, String productId,
|
||||
public GaoProductStockRecord getByBizSku(GaoProductStockRecord.BizType bizType, String bizId, String skuId,
|
||||
GaoProductStockRecord.OperationType operationType) {
|
||||
TimiException.required(bizType, "not found stock bizType");
|
||||
TimiException.required(bizId, "not found stock bizId");
|
||||
TimiException.required(productId, "not found productId");
|
||||
TimiException.required(skuId, "not found skuId");
|
||||
TimiException.required(operationType, "not found operationType");
|
||||
return mapper.selectByBizProduct(bizType, bizId, productId, operationType);
|
||||
return mapper.selectByBizSku(bizType, bizId, skuId, operationType);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+73
-54
@@ -5,18 +5,20 @@ 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.GaoProductSku;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSkuUnit;
|
||||
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.GaoProductSkuService;
|
||||
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.GaoProductStockProductView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSelection;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoStockChangeItem;
|
||||
import com.imyeyu.api.modules.system.entity.Logger;
|
||||
import com.imyeyu.api.modules.system.service.LoggerService;
|
||||
@@ -54,7 +56,7 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
||||
private final AttachmentService attachmentService;
|
||||
private final GaoProductStockRecordService recordService;
|
||||
private final GaoProductService productService;
|
||||
private final GaoProductUnitService unitService;
|
||||
private final GaoProductSkuService skuService;
|
||||
private final GaoStoreScopeService storeScopeService;
|
||||
private final UserLoginService userLoginService;
|
||||
private final LoggerService loggerService;
|
||||
@@ -66,15 +68,14 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
||||
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()
|
||||
Attachment.BizType.GAO_PRODUCT_SKU,
|
||||
list.stream().map(GaoProductStock::getSkuId).filter(id -> id != null && !id.isBlank()).toList()
|
||||
);
|
||||
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));
|
||||
if (stock.getSku() != null) {
|
||||
Attachment photo = photoMap.get(stock.getSkuId());
|
||||
stock.getSku().setAttachmentList(photo == null ? List.of() : List.of(photo));
|
||||
}
|
||||
}
|
||||
result.setList(list);
|
||||
@@ -82,22 +83,31 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public GaoProductStock detail(String storeId, String productId) {
|
||||
public PageResult<GaoProductStockProductView> pageProductByQuery(GaoProductStockPage page) {
|
||||
TimiException.required(page, "not found stock product page");
|
||||
PageResult<GaoProductStockProductView> result = new PageResult<>();
|
||||
result.setTotal(stockMapper.countProductByQuery(page));
|
||||
result.setList(stockMapper.selectProductByQuery(page));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GaoProductStock detail(String storeId, String skuId) {
|
||||
TimiException.required(storeId, "not found storeId");
|
||||
TimiException.required(productId, "not found productId");
|
||||
TimiException.required(skuId, "not found skuId");
|
||||
storeScopeService.checkStoreId(storeId);
|
||||
GaoProduct product = productService.get(productId);
|
||||
TimiException.required(product, "not found product");
|
||||
TimiException.requiredTrue(storeId.equals(product.getStoreId()), "产品不属于当前门店");
|
||||
GaoProductStock stock = stockMapper.selectByStoreIdAndProductId(storeId, productId);
|
||||
GaoProductSku sku = skuService.get(skuId);
|
||||
TimiException.required(sku, "not found sku");
|
||||
TimiException.requiredTrue(storeId.equals(sku.getStoreId()), "SKU 不属于当前门店");
|
||||
GaoProductStock stock = stockMapper.selectByStoreIdAndSkuId(storeId, skuId);
|
||||
if (stock == null) {
|
||||
stock = new GaoProductStock();
|
||||
stock.setStoreId(storeId);
|
||||
stock.setProductId(productId);
|
||||
stock.setSkuId(skuId);
|
||||
stock.setOnHandQuantity(ZERO);
|
||||
stock.setReservedQuantity(ZERO);
|
||||
}
|
||||
stock.setProduct(product);
|
||||
stock.setSku(sku);
|
||||
fillBalance(stock);
|
||||
return stock;
|
||||
}
|
||||
@@ -146,9 +156,9 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
||||
return true;
|
||||
}
|
||||
List<GaoStockChangeItem> sortedItems = new ArrayList<>(items);
|
||||
sortedItems.sort(Comparator.comparing(GaoStockChangeItem::getProductId));
|
||||
sortedItems.sort(Comparator.comparing(GaoStockChangeItem::getSkuId));
|
||||
for (GaoStockChangeItem item : sortedItems) {
|
||||
GaoProductStock stock = stockMapper.selectForUpdate(storeId, item.getProductId());
|
||||
GaoProductStock stock = stockMapper.selectForUpdate(storeId, item.getSkuId());
|
||||
if (stock == null || decimal(stock.getOnHandQuantity()).subtract(decimal(stock.getReservedQuantity())).compareTo(item.getBaseQuantity()) < 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -188,22 +198,23 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
||||
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);
|
||||
GaoProductSkuUnitSelection selection = skuService.getSkuUnit(storeId, request.getSkuId(), request.getSkuUnitId(), purchase);
|
||||
GaoProductSku sku = selection.getSku();
|
||||
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);
|
||||
GaoProductSkuUnit unit = selection.getUnit();
|
||||
TimiException.requiredTrue(Boolean.TRUE.equals(sku.getStockEnabled()), "该 SKU 未启用库存管理");
|
||||
BigDecimal quantity = validateQuantity(sku, request.getQuantity());
|
||||
BigDecimal baseQuantity = quantity.multiply(unit.getBaseQuantityPerUnit());
|
||||
List<GaoProductStockRecord> existingList = recordService.listByRequestNo(storeId, requestNo);
|
||||
if (!existingList.isEmpty()) {
|
||||
GaoProductStockRecord existing = existingList.getFirst();
|
||||
verifyExisting(existing, operationType, product.getId(), unit.getId(), quantity, baseQuantity, request.getRemark());
|
||||
return detail(storeId, product.getId());
|
||||
verifyExisting(existing, operationType, sku.getId(), unit.getId(), quantity, baseQuantity, request.getRemark());
|
||||
return detail(storeId, sku.getId());
|
||||
}
|
||||
TimiException.requiredTrue(hasAttachment(request.getAttachmentList()), "库存操作必须上传照片");
|
||||
TimiException.requiredTrue(hasText(request.getRemark()), "库存操作必须填写备注");
|
||||
GaoStockChangeItem item = GaoStockChangeItem.of(product, unit, quantity, baseQuantity);
|
||||
List<GaoProductStock> result = executeInternal(storeId, product.getId(), List.of(item), requestNo,
|
||||
GaoStockChangeItem item = GaoStockChangeItem.of(product, sku, unit, quantity, baseQuantity);
|
||||
List<GaoProductStock> result = executeInternal(storeId, sku.getId(), List.of(item), requestNo,
|
||||
userLoginService.getRequireLoginUserId(), operationType, request.getRemark(), request.getAttachmentList());
|
||||
return result.getFirst();
|
||||
}
|
||||
@@ -220,13 +231,13 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
||||
|| source.getOperationType() == GaoProductStockRecord.OperationType.MANUAL_OUT, "只有手工入库或手工出库流水可以冲正");
|
||||
TimiException.requiredNull(source.getSourceRecordId(), "只能冲正原始手工库存流水");
|
||||
String storeId = requiredText(source.getStoreId(), "原库存流水缺少门店");
|
||||
String productId = requiredText(source.getProductId(), "原库存流水缺少产品");
|
||||
String skuId = requiredText(source.getSkuId(), "原库存流水缺少 SKU");
|
||||
storeScopeService.checkStoreId(storeId);
|
||||
|
||||
List<GaoProductStockRecord> existingList = recordService.listByRequestNo(storeId, requestNo);
|
||||
if (!existingList.isEmpty()) {
|
||||
verifyManualRevokeExisting(existingList, sourceRecordId, remark);
|
||||
return detail(storeId, productId);
|
||||
return detail(storeId, skuId);
|
||||
}
|
||||
TimiException.requiredNull(recordService.getRevokeBySourceRecordId(sourceRecordId), "该手工库存流水已经冲正");
|
||||
TimiException.requiredTrue(hasAttachment(request.getAttachmentList()), "库存流水冲正必须上传照片");
|
||||
@@ -236,7 +247,7 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
||||
BigDecimal sourceReservedDelta = decimal(source.getReservedDelta());
|
||||
TimiException.requiredTrue(sourceReservedDelta.signum() == 0, "手工库存流水不能包含预占变动");
|
||||
|
||||
GaoProductStock stock = lockOrCreateStock(storeId, productId);
|
||||
GaoProductStock stock = lockOrCreateStock(storeId, skuId);
|
||||
BigDecimal onHandBefore = decimal(stock.getOnHandQuantity());
|
||||
BigDecimal reservedBefore = decimal(stock.getReservedQuantity());
|
||||
BigDecimal onHandDelta = source.getOnHandDelta().negate();
|
||||
@@ -247,9 +258,13 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
||||
BigDecimal reservedAfter = reservedBefore.add(reservedDelta);
|
||||
GaoProductStockRecord revoke = new GaoProductStockRecord();
|
||||
revoke.setStoreId(storeId);
|
||||
revoke.setProductId(productId);
|
||||
revoke.setProductUnitId(source.getProductUnitId());
|
||||
revoke.setSkuId(skuId);
|
||||
revoke.setSkuUnitId(source.getSkuUnitId());
|
||||
revoke.setProductIdSnapshot(source.getProductIdSnapshot());
|
||||
revoke.setProductNameSnapshot(source.getProductNameSnapshot());
|
||||
revoke.setSkuCodeSnapshot(source.getSkuCodeSnapshot());
|
||||
revoke.setSkuNameSnapshot(source.getSkuNameSnapshot());
|
||||
revoke.setSpecSummarySnapshot(source.getSpecSummarySnapshot());
|
||||
revoke.setUnitSnapshot(source.getUnitSnapshot());
|
||||
revoke.setBaseQuantityPerUnitSnapshot(source.getBaseQuantityPerUnitSnapshot());
|
||||
revoke.setQuantity(source.getQuantity());
|
||||
@@ -289,11 +304,11 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
||||
String normalizedRequestNo = requiredText(requestNo, "not found requestNo");
|
||||
storeScopeService.checkStoreId(storeId);
|
||||
List<GaoStockChangeItem> itemList = new ArrayList<>(sourceList);
|
||||
itemList.sort(Comparator.comparing(GaoStockChangeItem::getProductId));
|
||||
Set<String> productIdSet = new HashSet<>();
|
||||
itemList.sort(Comparator.comparing(GaoStockChangeItem::getSkuId));
|
||||
Set<String> skuIdSet = new HashSet<>();
|
||||
for (GaoStockChangeItem item : itemList) {
|
||||
TimiException.required(item, "库存变更项不能为空");
|
||||
TimiException.requiredTrue(productIdSet.add(item.getProductId()), "同一库存操作不能重复产品");
|
||||
TimiException.requiredTrue(skuIdSet.add(item.getSkuId()), "同一库存操作不能重复 SKU");
|
||||
TimiException.required(item.getBaseQuantity(), "基础单位数量不能为空");
|
||||
TimiException.requiredTrue(0 < item.getBaseQuantity().signum(), "基础单位数量必须大于 0");
|
||||
}
|
||||
@@ -302,13 +317,13 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
||||
verifyExistingList(existingList, itemList, operationType, bizId);
|
||||
List<GaoProductStock> result = new ArrayList<>();
|
||||
for (GaoStockChangeItem item : itemList) {
|
||||
result.add(detail(storeId, item.getProductId()));
|
||||
result.add(detail(storeId, item.getSkuId()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
List<GaoProductStock> result = new ArrayList<>();
|
||||
for (GaoStockChangeItem item : itemList) {
|
||||
GaoProductStock stock = lockOrCreateStock(storeId, item.getProductId());
|
||||
GaoProductStock stock = lockOrCreateStock(storeId, item.getSkuId());
|
||||
BigDecimal onHandBefore = decimal(stock.getOnHandQuantity());
|
||||
BigDecimal reservedBefore = decimal(stock.getReservedQuantity());
|
||||
BigDecimal onHandDelta = onHandDelta(operationType, item.getBaseQuantity());
|
||||
@@ -319,9 +334,13 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
||||
BigDecimal reservedAfter = reservedBefore.add(reservedDelta);
|
||||
GaoProductStockRecord record = new GaoProductStockRecord();
|
||||
record.setStoreId(storeId);
|
||||
record.setProductId(item.getProductId());
|
||||
record.setProductUnitId(item.getProductUnitId());
|
||||
record.setSkuId(item.getSkuId());
|
||||
record.setSkuUnitId(item.getSkuUnitId());
|
||||
record.setProductIdSnapshot(item.getProductIdSnapshot());
|
||||
record.setProductNameSnapshot(item.getProductNameSnapshot());
|
||||
record.setSkuCodeSnapshot(item.getSkuCodeSnapshot());
|
||||
record.setSkuNameSnapshot(item.getSkuNameSnapshot());
|
||||
record.setSpecSummarySnapshot(item.getSpecSummarySnapshot());
|
||||
record.setUnitSnapshot(item.getUnitSnapshot());
|
||||
record.setBaseQuantityPerUnitSnapshot(item.getBaseQuantityPerUnitSnapshot());
|
||||
record.setQuantity(item.getQuantity());
|
||||
@@ -350,21 +369,21 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
||||
return result;
|
||||
}
|
||||
|
||||
private GaoProductStock lockOrCreateStock(String storeId, String productId) {
|
||||
GaoProductStock stock = stockMapper.selectForUpdate(storeId, productId);
|
||||
private GaoProductStock lockOrCreateStock(String storeId, String skuId) {
|
||||
GaoProductStock stock = stockMapper.selectForUpdate(storeId, skuId);
|
||||
if (stock != null) {
|
||||
return stock;
|
||||
}
|
||||
stock = new GaoProductStock();
|
||||
stock.setId(UUID.randomUUID().toString());
|
||||
stock.setStoreId(storeId);
|
||||
stock.setProductId(productId);
|
||||
stock.setSkuId(skuId);
|
||||
stock.setOnHandQuantity(ZERO);
|
||||
stock.setReservedQuantity(ZERO);
|
||||
stock.setUpdatedBy(userLoginService.getRequireLoginUserId());
|
||||
stock.setCreatedAt(Time.now());
|
||||
stockMapper.insertIgnore(stock);
|
||||
stock = stockMapper.selectForUpdate(storeId, productId);
|
||||
stock = stockMapper.selectForUpdate(storeId, skuId);
|
||||
TimiException.required(stock, "库存余额创建失败");
|
||||
return stock;
|
||||
}
|
||||
@@ -386,21 +405,21 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
||||
};
|
||||
}
|
||||
|
||||
private BigDecimal validateQuantity(GaoProduct product, BigDecimal quantity) {
|
||||
private BigDecimal validateQuantity(GaoProductSku sku, BigDecimal quantity) {
|
||||
TimiException.required(quantity, "数量不能为空");
|
||||
TimiException.requiredTrue(0 < quantity.signum(), "数量必须大于 0");
|
||||
TimiException.requiredTrue(3 >= quantity.stripTrailingZeros().scale(), "数量最多保留三位小数");
|
||||
if (!Boolean.TRUE.equals(product.getAllowDecimalQuantity())) {
|
||||
if (!Boolean.TRUE.equals(sku.getAllowDecimalQuantity())) {
|
||||
TimiException.requiredTrue(quantity.stripTrailingZeros().scale() <= 0, "该产品不允许小数数量");
|
||||
}
|
||||
return quantity;
|
||||
}
|
||||
|
||||
private void verifyExisting(GaoProductStockRecord existing, GaoProductStockRecord.OperationType operationType, String productId, String unitId,
|
||||
private void verifyExisting(GaoProductStockRecord existing, GaoProductStockRecord.OperationType operationType, String skuId, String unitId,
|
||||
BigDecimal quantity, BigDecimal baseQuantity, String remark) {
|
||||
TimiException.requiredTrue(existing.getOperationType() == operationType
|
||||
&& productId.equals(existing.getProductId())
|
||||
&& unitId.equals(existing.getProductUnitId())
|
||||
&& skuId.equals(existing.getSkuId())
|
||||
&& unitId.equals(existing.getSkuUnitId())
|
||||
&& quantity.compareTo(existing.getQuantity()) == 0
|
||||
&& baseQuantity.compareTo(existing.getBaseQuantity()) == 0
|
||||
&& sameText(existing.getRemark(), remark), "requestNo 对应库存操作内容不一致");
|
||||
@@ -418,11 +437,11 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
||||
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);
|
||||
recordMap.put(record.getSkuId(), record);
|
||||
}
|
||||
TimiException.requiredTrue(recordMap.size() == itemList.size(), "requestNo 对应库存操作内容不一致");
|
||||
for (GaoStockChangeItem item : itemList) {
|
||||
GaoProductStockRecord record = recordMap.get(item.getProductId());
|
||||
GaoProductStockRecord record = recordMap.get(item.getSkuId());
|
||||
TimiException.required(record, "requestNo 对应库存操作内容不一致");
|
||||
TimiException.requiredTrue(item.getBaseQuantity().compareTo(record.getBaseQuantity()) == 0, "requestNo 对应库存操作内容不一致");
|
||||
}
|
||||
@@ -432,8 +451,8 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
||||
stock.setOnHandQuantity(decimal(stock.getOnHandQuantity()));
|
||||
stock.setReservedQuantity(decimal(stock.getReservedQuantity()));
|
||||
stock.setAvailableQuantity(stock.getOnHandQuantity().subtract(stock.getReservedQuantity()));
|
||||
if (stock.getProduct() == null && stock.getProductId() != null) {
|
||||
stock.setProduct(productService.get(stock.getProductId()));
|
||||
if (stock.getSku() == null && stock.getSkuId() != null) {
|
||||
stock.setSku(skuService.get(stock.getSkuId()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
package com.imyeyu.api.modules.gao.service.implement;
|
||||
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProduct;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductUnit;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductUnitService;
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/// GAO 产品单位服务实现
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-02
|
||||
@Service
|
||||
public class GaoProductUnitServiceImplement implements GaoProductUnitService {
|
||||
|
||||
private static final BigDecimal ONE = BigDecimal.ONE;
|
||||
|
||||
@Override
|
||||
public List<GaoProductUnit> validateAndNormalize(GaoProduct product, List<GaoProductUnit> sourceList) {
|
||||
TimiException.required(product, "not found product");
|
||||
TimiException.requiredTrue(sourceList != null && !sourceList.isEmpty(), "产品至少需要一个单位");
|
||||
List<GaoProductUnit> result = new ArrayList<>();
|
||||
Set<String> nameSet = new HashSet<>();
|
||||
Set<String> idSet = new HashSet<>();
|
||||
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());
|
||||
if (unit.getId() != null) {
|
||||
TimiException.requiredTrue(idSet.add(unit.getId()), "产品单位 ID 不能重复");
|
||||
}
|
||||
unit.setName(requiredText(source.getName(), "产品单位名称不能为空"));
|
||||
TimiException.requiredTrue(nameSet.add(unit.getName()), "同一产品的单位名称不能重复");
|
||||
unit.setBaseQuantityPerUnit(TimiJava.defaultIfNull(source.getBaseQuantityPerUnit(), ONE));
|
||||
TimiException.requiredTrue(0 < unit.getBaseQuantityPerUnit().signum(), "基础单位数量系数必须大于 0");
|
||||
TimiException.requiredTrue(6 >= unit.getBaseQuantityPerUnit().scale(), "基础单位数量系数最多保留六位小数");
|
||||
unit.setSalePrice(nonNegative(source.getSalePrice(), "单位建议售价不能为负数"));
|
||||
unit.setIsBase(Boolean.TRUE.equals(source.getIsBase()));
|
||||
unit.setAllowPurchase(TimiJava.defaultIfNull(source.getAllowPurchase(), true));
|
||||
unit.setAllowSale(TimiJava.defaultIfNull(source.getAllowSale(), true));
|
||||
unit.setSort(TimiJava.defaultIfNull(source.getSort(), i));
|
||||
if (unit.getIsBase()) {
|
||||
baseCount++;
|
||||
TimiException.requiredTrue(1 >= baseCount, "一个产品只能有一个基础单位");
|
||||
TimiException.requiredTrue(unit.getBaseQuantityPerUnit().compareTo(ONE) == 0, "基础单位数量系数必须为 1");
|
||||
}
|
||||
result.add(unit);
|
||||
}
|
||||
TimiException.requiredTrue(0 < baseCount, "产品必须有一个基础单位");
|
||||
TimiException.requiredTrue(product.getProductType() != GaoProduct.ProductType.SERVICE || !Boolean.TRUE.equals(product.getStockEnabled()), "SERVICE 产品不能启用库存");
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal convertQuantityToBase(GaoProductUnit unit, BigDecimal quantity) {
|
||||
TimiException.required(unit, "not found product unit");
|
||||
TimiException.required(quantity, "数量不能为空");
|
||||
TimiException.requiredTrue(0 < quantity.signum(), "数量必须大于 0");
|
||||
BigDecimal result = quantity.multiply(unit.getBaseQuantityPerUnit());
|
||||
try {
|
||||
return result.setScale(3, RoundingMode.UNNECESSARY);
|
||||
} catch (ArithmeticException e) {
|
||||
throw new TimiException(com.imyeyu.java.bean.timi.TimiCode.ERROR, "业务数量换算后不能精确保存为三位小数", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String requiredText(String value, String message) {
|
||||
TimiException.required(value, message);
|
||||
String result = value.trim();
|
||||
TimiException.requiredTrue(!result.isEmpty(), message);
|
||||
return result;
|
||||
}
|
||||
|
||||
private Long nonNegative(Long value, String message) {
|
||||
if (value != null) {
|
||||
TimiException.requiredTrue(0 <= value, message);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
+9
@@ -4,6 +4,7 @@ import com.imyeyu.api.modules.gao.mapper.GaoSalesOrderChartMapper;
|
||||
import com.imyeyu.api.modules.gao.service.GaoSalesOrderChartService;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerOrderProductRankView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerOrderRankView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerOrderSkuRankView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerRepeatDailyStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoCustomerRepeatIntervalStatView;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoSalesOrderChartReq;
|
||||
@@ -66,6 +67,14 @@ public class GaoSalesOrderChartServiceImplement implements GaoSalesOrderChartSer
|
||||
return mapper.selectCustomerProductRank(req);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GaoCustomerOrderSkuRankView> customerSkuRank(GaoSalesOrderChartReq req) {
|
||||
checkRangeReq(req);
|
||||
TimiException.required(req.getCustomerId(), "客户订单 SKU 排行必须指定客户");
|
||||
normalizeTopN(req);
|
||||
return mapper.selectCustomerSkuRank(req);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GaoSalesOrderStatusStatView> status(GaoSalesOrderChartReq req) {
|
||||
checkRangeReq(req);
|
||||
|
||||
+40
-36
@@ -10,7 +10,8 @@ import com.imyeyu.api.modules.gao.entity.GaoCustomer;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoEventRecord;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoFundRecord;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProduct;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductUnit;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSku;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSkuUnit;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoSalesOrder;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoSalesOrderItem;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoStore;
|
||||
@@ -22,13 +23,13 @@ import com.imyeyu.api.modules.gao.service.GaoFundRecordService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoOrderFulfillmentService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoPointTriggerService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductUnitService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoProductSkuService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoSalesOrderService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
|
||||
import com.imyeyu.api.modules.gao.service.GaoStoreService;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoDirectProductOrderCreateRequest;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoPackageOrderCreateData;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductUnitSelection;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSelection;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoSalesOrderCancelRequest;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoSalesOrderItemRequest;
|
||||
import com.imyeyu.api.modules.gao.vo.GaoSalesOrderPage;
|
||||
@@ -78,7 +79,7 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
|
||||
private final ObjectProvider<GaoOrderFulfillmentService> fulfillmentServiceProvider;
|
||||
private final GaoCustomerService customerService;
|
||||
private final GaoProductService productService;
|
||||
private final GaoProductUnitService unitService;
|
||||
private final GaoProductSkuService skuService;
|
||||
private final GaoStoreService storeService;
|
||||
private final GaoStoreScopeService storeScopeService;
|
||||
private final GaoEventRecordService eventRecordService;
|
||||
@@ -347,20 +348,20 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
|
||||
|
||||
private List<GaoSalesOrderItem> resolveDirectItems(String storeId, List<GaoSalesOrderItemRequest> sourceList) {
|
||||
TimiException.requiredTrue(sourceList != null && !sourceList.isEmpty(), "直接产品订单至少需要一个产品");
|
||||
Set<String> productUnitKeySet = new HashSet<>();
|
||||
Set<String> skuUnitKeySet = new HashSet<>();
|
||||
List<GaoSalesOrderItem> result = new ArrayList<>();
|
||||
boolean canOverridePrice = permissionChecker.hasAny(ModuleCode.GAO, GaoPermissionCode.SALES_ORDER_PRICE_OVERRIDE.getValue());
|
||||
for (int i = 0; i < sourceList.size(); i++) {
|
||||
GaoSalesOrderItemRequest source = sourceList.get(i);
|
||||
TimiException.required(source, "订单产品明细不能为空");
|
||||
TimiException.required(source.getProductId(), "产品不能为空");
|
||||
TimiException.required(source.getProductUnitId(), "产品单位不能为空");
|
||||
TimiException.requiredTrue(productUnitKeySet.add(source.getProductId() + "|" + source.getProductUnitId()), "同一订单不能重复产品和单位");
|
||||
GaoProductUnitSelection selection = productService.getSellableProductUnit(storeId, source.getProductId(), source.getProductUnitId());
|
||||
TimiException.required(source.getSkuId(), "SKU 不能为空");
|
||||
TimiException.required(source.getSkuUnitId(), "SKU 单位不能为空");
|
||||
TimiException.requiredTrue(skuUnitKeySet.add(source.getSkuId() + "|" + source.getSkuUnitId()), "同一订单不能重复 SKU 和单位");
|
||||
GaoProductSkuUnitSelection selection = skuService.getSellableSkuUnit(storeId, source.getSkuId(), source.getSkuUnitId());
|
||||
GaoProduct product = selection.getProduct();
|
||||
GaoProductUnit unit = selection.getUnit();
|
||||
BigDecimal quantity = validateQuantity(product, source.getQuantity());
|
||||
unitService.convertQuantityToBase(unit, quantity);
|
||||
GaoProductSku sku = selection.getSku();
|
||||
GaoProductSkuUnit unit = selection.getUnit();
|
||||
BigDecimal quantity = validateQuantity(sku, source.getQuantity());
|
||||
Long unitPrice = TimiJava.defaultIfNull(source.getUnitPrice(), unit.getSalePrice());
|
||||
TimiException.required(unitPrice, "产品单价不能为空");
|
||||
TimiException.requiredTrue(0 <= unitPrice, "产品单价不能为负数");
|
||||
@@ -370,10 +371,14 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
|
||||
}
|
||||
long lineAmount = toCentAmount(quantity, unitPrice);
|
||||
GaoSalesOrderItem item = new GaoSalesOrderItem();
|
||||
item.setProductId(product.getId());
|
||||
item.setProductUnitId(unit.getId());
|
||||
item.setSkuId(sku.getId());
|
||||
item.setSkuUnitId(unit.getId());
|
||||
item.setItemType(GaoSalesOrderItem.ItemType.DIRECT_PRODUCT);
|
||||
item.setProductNameSnapshot(product.getName());
|
||||
item.setProductIdSnapshot(product.getId());
|
||||
item.setSkuCodeSnapshot(sku.getCode());
|
||||
item.setSkuNameSnapshot(sku.getName());
|
||||
item.setSpecSummarySnapshot(sku.getSpecSummary());
|
||||
item.setUnitSnapshot(unit.getName());
|
||||
item.setBaseQuantityPerUnitSnapshot(unit.getBaseQuantityPerUnit());
|
||||
item.setQuantity(quantity);
|
||||
@@ -392,18 +397,18 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
|
||||
private void verifyExistingDirectItems(GaoSalesOrder existing, List<GaoSalesOrderItemRequest> requestList) {
|
||||
List<GaoSalesOrderItem> existingList = itemMapper.selectBySalesOrderId(existing.getId());
|
||||
TimiException.requiredTrue(requestList != null && existingList.size() == requestList.size(), "requestNo 对应订单内容不一致");
|
||||
Set<String> productUnitKeySet = new HashSet<>();
|
||||
Set<String> skuUnitKeySet = new HashSet<>();
|
||||
for (GaoSalesOrderItemRequest request : requestList) {
|
||||
TimiException.required(request, "订单产品明细不能为空");
|
||||
TimiException.required(request.getProductId(), "产品不能为空");
|
||||
TimiException.required(request.getProductUnitId(), "产品单位不能为空");
|
||||
TimiException.required(request.getSkuId(), "SKU 不能为空");
|
||||
TimiException.required(request.getSkuUnitId(), "SKU 单位不能为空");
|
||||
TimiException.required(request.getQuantity(), "购买数量不能为空");
|
||||
TimiException.requiredTrue(productUnitKeySet.add(request.getProductId() + "|" + request.getProductUnitId()), "同一订单不能重复产品和单位");
|
||||
TimiException.requiredTrue(skuUnitKeySet.add(request.getSkuId() + "|" + request.getSkuUnitId()), "同一订单不能重复 SKU 和单位");
|
||||
GaoSalesOrderItem item = existingList.stream()
|
||||
.filter(value -> value.getProductId().equals(request.getProductId()) && value.getProductUnitId().equals(request.getProductUnitId()))
|
||||
.filter(value -> value.getSkuId().equals(request.getSkuId()) && value.getSkuUnitId().equals(request.getSkuUnitId()))
|
||||
.findFirst().orElse(null);
|
||||
TimiException.required(item, "requestNo 对应订单内容不一致");
|
||||
TimiException.requiredTrue(item.getProductUnitId().equals(request.getProductUnitId())
|
||||
TimiException.requiredTrue(item.getSkuUnitId().equals(request.getSkuUnitId())
|
||||
&& item.getQuantity().compareTo(request.getQuantity()) == 0
|
||||
&& (request.getUnitPrice() == null || item.getUnitPrice().equals(request.getUnitPrice())), "requestNo 对应订单内容不一致");
|
||||
}
|
||||
@@ -434,15 +439,15 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
|
||||
TimiException.requiredTrue(0 <= saleAmount, "成交价不能为负数");
|
||||
GaoSalesOrderItem.ItemType expectedItemType = orderType == GaoSalesOrder.OrderType.PACKAGE
|
||||
? GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT : GaoSalesOrderItem.ItemType.DIRECT_PRODUCT;
|
||||
Set<String> productUnitKeySet = new HashSet<>();
|
||||
Set<String> skuUnitKeySet = new HashSet<>();
|
||||
long lineAmountSum = 0L;
|
||||
for (GaoSalesOrderItem item : itemList) {
|
||||
TimiException.required(item, "销售订单明细不能为空");
|
||||
TimiException.requiredTrue(item.getItemType() == expectedItemType, "订单项类型与订单类型不匹配");
|
||||
TimiException.required(item.getProductId(), "订单项产品不能为空");
|
||||
TimiException.required(item.getProductUnitId(), "订单项单位不能为空");
|
||||
TimiException.requiredTrue(productUnitKeySet.add(item.getProductId() + "|" + item.getProductUnitId()),
|
||||
"同一订单不能重复产品和单位");
|
||||
TimiException.required(item.getSkuId(), "订单项 SKU 不能为空");
|
||||
TimiException.required(item.getSkuUnitId(), "订单项 SKU 单位不能为空");
|
||||
TimiException.requiredTrue(skuUnitKeySet.add(item.getSkuId() + "|" + item.getSkuUnitId()),
|
||||
"同一订单不能重复 SKU 和单位");
|
||||
TimiException.required(item.getQuantity(), "订单项数量不能为空");
|
||||
TimiException.requiredTrue(0 < item.getQuantity().signum(), "订单项数量必须大于 0");
|
||||
TimiException.requiredTrue(3 >= item.getQuantity().stripTrailingZeros().scale(), "订单项数量最多保留三位小数");
|
||||
@@ -494,9 +499,9 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
|
||||
List<GaoSalesOrderItem> existingItemList = itemMapper.selectBySalesOrderId(existing.getId());
|
||||
TimiException.requiredTrue(existingItemList.size() == requestItemList.size(), "requestNo 对应订单内容不一致");
|
||||
Map<String, GaoSalesOrderItem> existingItemMap = existingItemList.stream()
|
||||
.collect(java.util.stream.Collectors.toMap(item -> item.getProductId() + "|" + item.getProductUnitId(), java.util.function.Function.identity()));
|
||||
.collect(java.util.stream.Collectors.toMap(item -> item.getSkuId() + "|" + item.getSkuUnitId(), java.util.function.Function.identity()));
|
||||
for (GaoSalesOrderItem requestItem : requestItemList) {
|
||||
GaoSalesOrderItem existingItem = existingItemMap.get(requestItem.getProductId() + "|" + requestItem.getProductUnitId());
|
||||
GaoSalesOrderItem existingItem = existingItemMap.get(requestItem.getSkuId() + "|" + requestItem.getSkuUnitId());
|
||||
TimiException.required(existingItem, "requestNo 对应订单内容不一致");
|
||||
TimiException.requiredTrue(existingItem.getItemType() == GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT
|
||||
&& existingItem.getQuantity().compareTo(requestItem.getQuantity()) == 0
|
||||
@@ -550,20 +555,19 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
|
||||
if (itemList == null || itemList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<String> productIdList = itemList.stream()
|
||||
.map(GaoSalesOrderItem::getProductId)
|
||||
List<String> skuIdList = itemList.stream()
|
||||
.map(GaoSalesOrderItem::getSkuId)
|
||||
.filter(value -> value != null && !value.isBlank())
|
||||
.distinct()
|
||||
.toList();
|
||||
if (productIdList.isEmpty()) {
|
||||
if (skuIdList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<String, List<Attachment>> attachmentMap = attachmentService.mapByBizIdList(
|
||||
Attachment.BizType.GAO_PRODUCT,
|
||||
productIdList,
|
||||
GaoProduct.AttachType.PHOTO.name());
|
||||
Attachment.BizType.GAO_PRODUCT_SKU,
|
||||
skuIdList);
|
||||
for (GaoSalesOrderItem item : itemList) {
|
||||
item.setAttachmentList(attachmentMap.getOrDefault(item.getProductId(), List.of()));
|
||||
item.setAttachmentList(attachmentMap.getOrDefault(item.getSkuId(), List.of()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,11 +595,11 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
|
||||
}
|
||||
}
|
||||
|
||||
private BigDecimal validateQuantity(GaoProduct product, BigDecimal quantity) {
|
||||
private BigDecimal validateQuantity(GaoProductSku sku, BigDecimal quantity) {
|
||||
TimiException.required(quantity, "购买数量不能为空");
|
||||
TimiException.requiredTrue(0 < quantity.signum(), "购买数量必须大于 0");
|
||||
TimiException.requiredTrue(3 >= quantity.stripTrailingZeros().scale(), "购买数量最多保留三位小数");
|
||||
if (!Boolean.TRUE.equals(product.getAllowDecimalQuantity())) {
|
||||
if (!Boolean.TRUE.equals(sku.getAllowDecimalQuantity())) {
|
||||
TimiException.requiredTrue(quantity.stripTrailingZeros().scale() <= 0, "该产品不允许小数数量");
|
||||
}
|
||||
return quantity;
|
||||
|
||||
@@ -37,4 +37,19 @@ public class GaoCustomerOrderProductRankView {
|
||||
|
||||
/// 直接产品销售金额,单位分
|
||||
private Long directSaleAmount;
|
||||
|
||||
/// 直接产品购买数量,基础单位
|
||||
private BigDecimal directBaseQuantity;
|
||||
|
||||
/// 套餐权益购买数量,基础单位
|
||||
private BigDecimal packageBaseQuantity;
|
||||
|
||||
/// 已预占数量,基础单位
|
||||
private BigDecimal reservedBaseQuantity;
|
||||
|
||||
/// 已履约数量,基础单位
|
||||
private BigDecimal fulfilledBaseQuantity;
|
||||
|
||||
/// 当前可履约数量,基础单位
|
||||
private BigDecimal availableBaseQuantity;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.imyeyu.api.modules.gao.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/// 客户订单 SKU 排行视图
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-07
|
||||
@Data
|
||||
public class GaoCustomerOrderSkuRankView {
|
||||
|
||||
/// SKU ID
|
||||
private String skuId;
|
||||
|
||||
/// SKU 编码快照
|
||||
private String skuCode;
|
||||
|
||||
/// SKU 名称快照
|
||||
private String skuName;
|
||||
|
||||
/// 规格摘要快照
|
||||
private String specSummary;
|
||||
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
|
||||
/// 产品名称快照
|
||||
private String productName;
|
||||
|
||||
/// 订单数
|
||||
private Long orderCount;
|
||||
|
||||
/// 直接产品购买数量,基础单位
|
||||
private BigDecimal directBaseQuantity;
|
||||
|
||||
/// 套餐权益购买数量,基础单位
|
||||
private BigDecimal packageBaseQuantity;
|
||||
|
||||
/// 待履约数量,基础单位
|
||||
private BigDecimal reservedBaseQuantity;
|
||||
|
||||
/// 已履约数量,基础单位
|
||||
private BigDecimal fulfilledBaseQuantity;
|
||||
|
||||
/// 当前可履约数量,基础单位
|
||||
private BigDecimal availableBaseQuantity;
|
||||
|
||||
/// 直接产品销售金额,单位分
|
||||
private Long directSaleAmount;
|
||||
}
|
||||
@@ -11,15 +11,27 @@ import java.math.BigDecimal;
|
||||
@Data
|
||||
public class GaoCustomerPackagePurchaseItem {
|
||||
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
/// SKU ID
|
||||
private String skuId;
|
||||
|
||||
/// 产品单位 ID
|
||||
private String productUnitId;
|
||||
/// SKU 单位 ID
|
||||
private String skuUnitId;
|
||||
|
||||
/// 产品名称快照,由后端生成
|
||||
private String productNameSnapshot;
|
||||
|
||||
/// 产品 ID 快照,由后端生成
|
||||
private String productIdSnapshot;
|
||||
|
||||
/// SKU 编码快照,由后端生成
|
||||
private String skuCodeSnapshot;
|
||||
|
||||
/// SKU 名称快照,由后端生成
|
||||
private String skuNameSnapshot;
|
||||
|
||||
/// 规格摘要快照,由后端生成
|
||||
private String specSummarySnapshot;
|
||||
|
||||
/// 单位快照,由后端生成
|
||||
private String unitSnapshot;
|
||||
|
||||
|
||||
@@ -16,6 +16,9 @@ public class GaoProductChartReq {
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
|
||||
/// SKU ID
|
||||
private String skuId;
|
||||
|
||||
/// 分类 ID
|
||||
private String categoryId;
|
||||
|
||||
|
||||
@@ -20,6 +20,12 @@ public class GaoProductFulfillmentDailyStatView {
|
||||
/// 套餐权益履约数量
|
||||
private BigDecimal packageQuantity;
|
||||
|
||||
/// 直接产品履约数量,基础单位
|
||||
private BigDecimal directBaseQuantity;
|
||||
|
||||
/// 套餐权益履约数量,基础单位
|
||||
private BigDecimal packageBaseQuantity;
|
||||
|
||||
/// 履约基础单位数量
|
||||
private BigDecimal baseQuantity;
|
||||
}
|
||||
|
||||
@@ -34,4 +34,19 @@ public class GaoProductFulfillmentRankView {
|
||||
|
||||
/// 当前可履约数量
|
||||
private BigDecimal availableQuantity;
|
||||
|
||||
/// 直接产品购买数量,基础单位
|
||||
private BigDecimal directBaseQuantity;
|
||||
|
||||
/// 套餐权益购买数量,基础单位
|
||||
private BigDecimal packageBaseQuantity;
|
||||
|
||||
/// 待履约数量,基础单位
|
||||
private BigDecimal reservedBaseQuantity;
|
||||
|
||||
/// 已履约数量,基础单位
|
||||
private BigDecimal fulfilledBaseQuantity;
|
||||
|
||||
/// 当前可履约数量,基础单位
|
||||
private BigDecimal availableBaseQuantity;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,12 @@ public class GaoProductSalesDailyStatView {
|
||||
/// 套餐权益数量
|
||||
private BigDecimal packageQuantity;
|
||||
|
||||
/// 直接产品销售数量,基础单位
|
||||
private BigDecimal directBaseQuantity;
|
||||
|
||||
/// 套餐权益数量,基础单位
|
||||
private BigDecimal packageBaseQuantity;
|
||||
|
||||
/// 直接产品销售金额,单位分
|
||||
private Long directSaleAmount;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,12 @@ public class GaoProductSalesRankView {
|
||||
/// 套餐权益数量
|
||||
private BigDecimal packageQuantity;
|
||||
|
||||
/// 直接产品销售数量,基础单位
|
||||
private BigDecimal directBaseQuantity;
|
||||
|
||||
/// 套餐权益数量,基础单位
|
||||
private BigDecimal packageBaseQuantity;
|
||||
|
||||
/// 直接产品销售金额,单位分
|
||||
private Long directSaleAmount;
|
||||
}
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.imyeyu.api.modules.gao.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/// SKU 每日履约统计视图
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-07
|
||||
@Data
|
||||
public class GaoProductSkuFulfillmentDailyStatView {
|
||||
|
||||
/// 日期值,格式为 yyyyMMdd
|
||||
private Integer dateValue;
|
||||
|
||||
/// SKU ID
|
||||
private String skuId;
|
||||
|
||||
/// SKU 编码快照
|
||||
private String skuCode;
|
||||
|
||||
/// SKU 名称快照
|
||||
private String skuName;
|
||||
|
||||
/// 规格摘要快照
|
||||
private String specSummary;
|
||||
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
|
||||
/// 产品名称快照
|
||||
private String productName;
|
||||
|
||||
/// 直接产品履约数量,基础单位
|
||||
private BigDecimal directBaseQuantity;
|
||||
|
||||
/// 套餐权益履约数量,基础单位
|
||||
private BigDecimal packageBaseQuantity;
|
||||
|
||||
/// 履约基础单位数量
|
||||
private BigDecimal baseQuantity;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.imyeyu.api.modules.gao.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/// SKU 履约进度排行视图
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-07
|
||||
@Data
|
||||
public class GaoProductSkuFulfillmentRankView {
|
||||
|
||||
/// SKU ID
|
||||
private String skuId;
|
||||
|
||||
/// SKU 编码快照
|
||||
private String skuCode;
|
||||
|
||||
/// SKU 名称快照
|
||||
private String skuName;
|
||||
|
||||
/// 规格摘要快照
|
||||
private String specSummary;
|
||||
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
|
||||
/// 产品名称快照
|
||||
private String productName;
|
||||
|
||||
/// 订单数
|
||||
private Long orderCount;
|
||||
|
||||
/// 直接产品购买数量,基础单位
|
||||
private BigDecimal directBaseQuantity;
|
||||
|
||||
/// 套餐权益购买数量,基础单位
|
||||
private BigDecimal packageBaseQuantity;
|
||||
|
||||
/// 待履约数量,基础单位
|
||||
private BigDecimal reservedBaseQuantity;
|
||||
|
||||
/// 已履约数量,基础单位
|
||||
private BigDecimal fulfilledBaseQuantity;
|
||||
|
||||
/// 当前可履约数量,基础单位
|
||||
private BigDecimal availableBaseQuantity;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.imyeyu.api.modules.gao.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/// SKU 每日销售统计视图
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-07
|
||||
@Data
|
||||
public class GaoProductSkuSalesDailyStatView {
|
||||
|
||||
/// 日期值,格式为 yyyyMMdd
|
||||
private Integer dateValue;
|
||||
|
||||
/// SKU ID
|
||||
private String skuId;
|
||||
|
||||
/// SKU 编码快照
|
||||
private String skuCode;
|
||||
|
||||
/// SKU 名称快照
|
||||
private String skuName;
|
||||
|
||||
/// 规格摘要快照
|
||||
private String specSummary;
|
||||
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
|
||||
/// 产品名称快照
|
||||
private String productName;
|
||||
|
||||
/// 包含该 SKU 的订单数
|
||||
private Long orderCount;
|
||||
|
||||
/// 直接产品销售数量,基础单位
|
||||
private BigDecimal directBaseQuantity;
|
||||
|
||||
/// 套餐权益数量,基础单位
|
||||
private BigDecimal packageBaseQuantity;
|
||||
|
||||
/// 直接产品销售金额,单位分
|
||||
private Long directSaleAmount;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.imyeyu.api.modules.gao.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/// SKU 销售排行视图
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-07
|
||||
@Data
|
||||
public class GaoProductSkuSalesRankView {
|
||||
|
||||
/// SKU ID
|
||||
private String skuId;
|
||||
|
||||
/// SKU 编码快照
|
||||
private String skuCode;
|
||||
|
||||
/// SKU 名称快照
|
||||
private String skuName;
|
||||
|
||||
/// 规格摘要快照
|
||||
private String specSummary;
|
||||
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
|
||||
/// 产品名称快照
|
||||
private String productName;
|
||||
|
||||
/// 包含该 SKU 的订单数
|
||||
private Long orderCount;
|
||||
|
||||
/// 直接产品销售数量,基础单位
|
||||
private BigDecimal directBaseQuantity;
|
||||
|
||||
/// 套餐权益数量,基础单位
|
||||
private BigDecimal packageBaseQuantity;
|
||||
|
||||
/// 直接产品销售金额,单位分
|
||||
private Long directSaleAmount;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.imyeyu.api.modules.gao.vo;
|
||||
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProduct;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/// SKU 库存健康度视图
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-07
|
||||
@Data
|
||||
public class GaoProductSkuStockHealthView {
|
||||
|
||||
/// SKU ID
|
||||
private String skuId;
|
||||
|
||||
/// SKU 编码
|
||||
private String skuCode;
|
||||
|
||||
/// SKU 名称
|
||||
private String skuName;
|
||||
|
||||
/// 规格摘要
|
||||
private String specSummary;
|
||||
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
|
||||
/// 产品名称
|
||||
private String productName;
|
||||
|
||||
/// 分类 ID
|
||||
private String categoryId;
|
||||
|
||||
/// 分类名称
|
||||
private String categoryName;
|
||||
|
||||
/// 产品类型
|
||||
private GaoProduct.ProductType productType;
|
||||
|
||||
/// 实际库存,基础单位
|
||||
private BigDecimal onHandQuantity;
|
||||
|
||||
/// 预占库存,基础单位
|
||||
private BigDecimal reservedQuantity;
|
||||
|
||||
/// 可用库存,基础单位
|
||||
private BigDecimal availableQuantity;
|
||||
|
||||
/// 最低库存,基础单位
|
||||
private BigDecimal minStockQuantity;
|
||||
|
||||
/// 1 为低于最低库存,0 为正常
|
||||
private Integer lowStock;
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package com.imyeyu.api.modules.gao.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/// SKU 每日库存变动统计视图
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-07
|
||||
@Data
|
||||
public class GaoProductSkuStockMovementDailyStatView {
|
||||
|
||||
/// 日期值,格式为 yyyyMMdd
|
||||
private Integer dateValue;
|
||||
|
||||
/// SKU ID
|
||||
private String skuId;
|
||||
|
||||
/// SKU 编码快照
|
||||
private String skuCode;
|
||||
|
||||
/// SKU 名称快照
|
||||
private String skuName;
|
||||
|
||||
/// 规格摘要快照
|
||||
private String specSummary;
|
||||
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
|
||||
/// 产品名称快照
|
||||
private String productName;
|
||||
|
||||
/// 实际入库数量,基础单位
|
||||
private BigDecimal inQuantity;
|
||||
|
||||
/// 实际出库数量,基础单位
|
||||
private BigDecimal outQuantity;
|
||||
|
||||
/// 实际库存净变化,基础单位
|
||||
private BigDecimal netOnHandQuantity;
|
||||
|
||||
/// 新增预占数量,基础单位
|
||||
private BigDecimal reserveQuantity;
|
||||
|
||||
/// 释放预占数量,基础单位
|
||||
private BigDecimal releaseQuantity;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.imyeyu.api.modules.gao.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/// SKU 单位销售排行视图
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-07
|
||||
@Data
|
||||
public class GaoProductSkuUnitSalesRankView {
|
||||
|
||||
/// SKU ID
|
||||
private String skuId;
|
||||
|
||||
/// SKU 编码快照
|
||||
private String skuCode;
|
||||
|
||||
/// SKU 名称快照
|
||||
private String skuName;
|
||||
|
||||
/// 规格摘要快照
|
||||
private String specSummary;
|
||||
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
|
||||
/// 产品名称快照
|
||||
private String productName;
|
||||
|
||||
/// SKU 单位 ID
|
||||
private String skuUnitId;
|
||||
|
||||
/// 单位名称快照
|
||||
private String unitName;
|
||||
|
||||
/// 订单数
|
||||
private Long orderCount;
|
||||
|
||||
/// 销售数量
|
||||
private BigDecimal quantity;
|
||||
|
||||
/// 销售数量,基础单位
|
||||
private BigDecimal baseQuantity;
|
||||
|
||||
/// 销售金额,单位分
|
||||
private Long saleAmount;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.imyeyu.api.modules.gao.vo;
|
||||
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProduct;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSku;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSkuUnit;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/// 已校验的 SKU 单位选项
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-06
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class GaoProductSkuUnitSelection {
|
||||
|
||||
/// 产品
|
||||
private GaoProduct product;
|
||||
|
||||
/// SKU
|
||||
private GaoProductSku sku;
|
||||
|
||||
/// SKU 单位
|
||||
private GaoProductSkuUnit unit;
|
||||
}
|
||||
@@ -19,11 +19,11 @@ public class GaoProductStockChangeRequest {
|
||||
/// 门店 ID
|
||||
private String storeId;
|
||||
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
/// SKU ID
|
||||
private String skuId;
|
||||
|
||||
/// 产品单位 ID
|
||||
private String productUnitId;
|
||||
/// SKU 单位 ID
|
||||
private String skuUnitId;
|
||||
|
||||
/// 业务数量
|
||||
private BigDecimal quantity;
|
||||
|
||||
@@ -41,4 +41,10 @@ public class GaoProductStockHealthView {
|
||||
|
||||
/// 1 为低于最低库存,0 为正常
|
||||
private Integer lowStock;
|
||||
|
||||
/// 参与库存统计的 SKU 数量
|
||||
private Long skuCount;
|
||||
|
||||
/// 低于最低库存的 SKU 数量
|
||||
private Long lowStockSkuCount;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,6 @@ public class GaoProductStockPage extends BasePage {
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
|
||||
/// 产品名称或编码关键词
|
||||
/// 产品或 SKU 名称、编码、规格摘要关键词
|
||||
private String keyword;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.imyeyu.api.modules.gao.vo;
|
||||
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProduct;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/// 产品库存第一层汇总视图
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-07
|
||||
@Data
|
||||
public class GaoProductStockProductView {
|
||||
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
|
||||
/// 产品编码
|
||||
private String productCode;
|
||||
|
||||
/// 产品名称
|
||||
private String productName;
|
||||
|
||||
/// 产品类型
|
||||
private GaoProduct.ProductType productType;
|
||||
|
||||
/// 可库存 SKU 数量
|
||||
private Long skuCount;
|
||||
|
||||
/// 库存数量,使用基础单位
|
||||
private BigDecimal onHandQuantity;
|
||||
|
||||
/// 预占数量,使用基础单位
|
||||
private BigDecimal reservedQuantity;
|
||||
|
||||
/// 可用数量,使用基础单位
|
||||
private BigDecimal availableQuantity;
|
||||
}
|
||||
@@ -17,9 +17,24 @@ public class GaoProductUnitSalesRankView {
|
||||
/// 产品名称
|
||||
private String productName;
|
||||
|
||||
/// SKU ID
|
||||
private String skuId;
|
||||
|
||||
/// SKU 编码快照
|
||||
private String skuCode;
|
||||
|
||||
/// SKU 名称快照
|
||||
private String skuName;
|
||||
|
||||
/// 规格摘要快照
|
||||
private String specSummary;
|
||||
|
||||
/// 产品单位 ID
|
||||
private String productUnitId;
|
||||
|
||||
/// SKU 单位 ID
|
||||
private String skuUnitId;
|
||||
|
||||
/// 单位名称
|
||||
private String unitName;
|
||||
|
||||
@@ -29,6 +44,9 @@ public class GaoProductUnitSalesRankView {
|
||||
/// 销售数量
|
||||
private BigDecimal quantity;
|
||||
|
||||
/// 销售数量,基础单位
|
||||
private BigDecimal baseQuantity;
|
||||
|
||||
/// 销售金额,单位分
|
||||
private Long saleAmount;
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.imyeyu.api.modules.gao.vo;
|
||||
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProduct;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductUnit;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/// 已校验的产品单位选项
|
||||
///
|
||||
/// @author Codex
|
||||
/// @since 2026-09-02
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class GaoProductUnitSelection {
|
||||
|
||||
/// 产品
|
||||
private GaoProduct product;
|
||||
|
||||
/// 单位
|
||||
private GaoProductUnit unit;
|
||||
}
|
||||
@@ -16,6 +16,12 @@ public class GaoSalesOrderChartReq {
|
||||
/// 客户 ID
|
||||
private String customerId;
|
||||
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
|
||||
/// SKU ID
|
||||
private String skuId;
|
||||
|
||||
/// 订单类型
|
||||
private GaoSalesOrder.OrderType orderType;
|
||||
|
||||
|
||||
@@ -11,11 +11,11 @@ import java.math.BigDecimal;
|
||||
@Data
|
||||
public class GaoSalesOrderItemRequest {
|
||||
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
/// SKU ID
|
||||
private String skuId;
|
||||
|
||||
/// 产品单位 ID
|
||||
private String productUnitId;
|
||||
/// SKU 单位 ID
|
||||
private String skuUnitId;
|
||||
|
||||
/// 购买数量
|
||||
private BigDecimal quantity;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package com.imyeyu.api.modules.gao.vo;
|
||||
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProduct;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductUnit;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSku;
|
||||
import com.imyeyu.api.modules.gao.entity.GaoProductSkuUnit;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
@@ -13,15 +14,27 @@ import java.math.BigDecimal;
|
||||
@Data
|
||||
public class GaoStockChangeItem {
|
||||
|
||||
/// 产品 ID
|
||||
private String productId;
|
||||
/// SKU ID
|
||||
private String skuId;
|
||||
|
||||
/// 产品单位 ID
|
||||
private String productUnitId;
|
||||
/// SKU 单位 ID
|
||||
private String skuUnitId;
|
||||
|
||||
/// 产品 ID 快照
|
||||
private String productIdSnapshot;
|
||||
|
||||
/// 产品名称快照
|
||||
private String productNameSnapshot;
|
||||
|
||||
/// SKU 编码快照
|
||||
private String skuCodeSnapshot;
|
||||
|
||||
/// SKU 名称快照
|
||||
private String skuNameSnapshot;
|
||||
|
||||
/// 规格摘要快照
|
||||
private String specSummarySnapshot;
|
||||
|
||||
/// 单位名称快照
|
||||
private String unitSnapshot;
|
||||
|
||||
@@ -40,15 +53,20 @@ public class GaoStockChangeItem {
|
||||
/// 生成库存项
|
||||
///
|
||||
/// @param product 产品
|
||||
/// @param unit 单位
|
||||
/// @param sku SKU
|
||||
/// @param unit SKU 单位
|
||||
/// @param quantity 业务数量
|
||||
/// @param baseQuantity 基础单位数量
|
||||
/// @return 库存变更项
|
||||
public static GaoStockChangeItem of(GaoProduct product, GaoProductUnit unit, BigDecimal quantity, BigDecimal baseQuantity) {
|
||||
public static GaoStockChangeItem of(GaoProduct product, GaoProductSku sku, GaoProductSkuUnit unit, BigDecimal quantity, BigDecimal baseQuantity) {
|
||||
GaoStockChangeItem result = new GaoStockChangeItem();
|
||||
result.productId = product.getId();
|
||||
result.productUnitId = unit.getId();
|
||||
result.skuId = sku.getId();
|
||||
result.skuUnitId = unit.getId();
|
||||
result.productIdSnapshot = product.getId();
|
||||
result.productNameSnapshot = product.getName();
|
||||
result.skuCodeSnapshot = sku.getCode();
|
||||
result.skuNameSnapshot = sku.getName();
|
||||
result.specSummarySnapshot = sku.getSpecSummary();
|
||||
result.unitSnapshot = unit.getName();
|
||||
result.baseQuantityPerUnitSnapshot = unit.getBaseQuantityPerUnit();
|
||||
result.quantity = quantity;
|
||||
|
||||
Reference in New Issue
Block a user