v1.0.38 #50

Merged
timi merged 4 commits from dev into master 2026-09-09 17:33:26 +00:00
6 changed files with 84 additions and 16 deletions
Showing only changes of commit a2d69fe721 - Show all commits
@@ -111,6 +111,8 @@ public enum GaoPermissionCode implements BuiltinPermissionCode {
CUSTOMER_PACKAGE_PRICE_OVERRIDE("CUSTOMER_PACKAGE:PRICE_OVERRIDE", "客户套餐成交价和权益配额调整", true, true), CUSTOMER_PACKAGE_PRICE_OVERRIDE("CUSTOMER_PACKAGE:PRICE_OVERRIDE", "客户套餐成交价和权益配额调整", true, true),
CUSTOMER_PACKAGE_GIFT_CREATE("CUSTOMER_PACKAGE:GIFT_CREATE", "客户套餐创建赠送", true, true),
FUND_RECORD_READ("FUND_RECORD:READ", "资金流水读取", true, true), FUND_RECORD_READ("FUND_RECORD:READ", "资金流水读取", true, true),
FUND_RECEIVE_CREATE("FUND_RECEIVE:CREATE", "线下收款创建", true, true), FUND_RECEIVE_CREATE("FUND_RECEIVE:CREATE", "线下收款创建", true, true),
@@ -34,6 +34,9 @@ public class GaoSalesOrderItem extends UUIDEntity {
/// 订单项类型 /// 订单项类型
private ItemType itemType; private ItemType itemType;
/// true 为赠送权益
private Boolean gifted;
/// SKU ID /// SKU ID
private String skuId; private String skuId;
@@ -4,6 +4,7 @@ import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
import com.imyeyu.api.modules.gao.entity.GaoCustomer; import com.imyeyu.api.modules.gao.entity.GaoCustomer;
import com.imyeyu.api.modules.gao.entity.GaoCustomerPackage; import com.imyeyu.api.modules.gao.entity.GaoCustomerPackage;
import com.imyeyu.api.modules.gao.entity.GaoPackageConfig; import com.imyeyu.api.modules.gao.entity.GaoPackageConfig;
@@ -44,6 +45,7 @@ import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -151,10 +153,13 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
request.setItemList(defaultPurchaseItems(configItemList)); request.setItemList(defaultPurchaseItems(configItemList));
} }
List<GaoCustomerPackagePurchaseItem> requestItemList = new ArrayList<>(request.getItemList()); List<GaoCustomerPackagePurchaseItem> requestItemList = new ArrayList<>(request.getItemList());
normalizePurchaseItems(requestItemList);
verifyPurchaseItems(requestItemList); verifyPurchaseItems(requestItemList);
boolean canOverridePriceAndQuantity = permissionChecker.hasAny(ModuleCode.GAO, boolean canOverridePriceAndQuantity = permissionChecker.hasAny(ModuleCode.GAO,
com.imyeyu.api.modules.gao.bean.GaoPermissionCode.CUSTOMER_PACKAGE_PRICE_OVERRIDE.getValue()); GaoPermissionCode.CUSTOMER_PACKAGE_PRICE_OVERRIDE.getValue());
List<GaoCustomerPackagePurchaseItem> itemList = resolvePurchaseItems(config.getStoreId(), requestItemList, configItemList, canOverridePriceAndQuantity); boolean canCreateGift = permissionChecker.hasAny(ModuleCode.GAO, GaoPermissionCode.CUSTOMER_PACKAGE_GIFT_CREATE.getValue());
List<GaoCustomerPackagePurchaseItem> itemList = resolvePurchaseItems(config.getStoreId(), requestItemList, configItemList,
canOverridePriceAndQuantity, canCreateGift);
itemList.sort(Comparator.comparing(GaoCustomerPackagePurchaseItem::getSort).thenComparing(GaoCustomerPackagePurchaseItem::getSkuId) itemList.sort(Comparator.comparing(GaoCustomerPackagePurchaseItem::getSort).thenComparing(GaoCustomerPackagePurchaseItem::getSkuId)
.thenComparing(GaoCustomerPackagePurchaseItem::getSkuUnitId)); .thenComparing(GaoCustomerPackagePurchaseItem::getSkuUnitId));
request.setItemList(itemList); request.setItemList(itemList);
@@ -246,6 +251,7 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
GaoCustomerPackagePurchaseItem item = new GaoCustomerPackagePurchaseItem(); GaoCustomerPackagePurchaseItem item = new GaoCustomerPackagePurchaseItem();
item.setSkuId(configItem.getSkuId()); item.setSkuId(configItem.getSkuId());
item.setSkuUnitId(configItem.getSkuUnitId()); item.setSkuUnitId(configItem.getSkuUnitId());
item.setGifted(false);
item.setProductNameSnapshot(configItem.getProductNameSnapshot()); item.setProductNameSnapshot(configItem.getProductNameSnapshot());
item.setProductIdSnapshot(configItem.getProductIdSnapshot()); item.setProductIdSnapshot(configItem.getProductIdSnapshot());
item.setSkuCodeSnapshot(configItem.getSkuCodeSnapshot()); item.setSkuCodeSnapshot(configItem.getSkuCodeSnapshot());
@@ -265,6 +271,7 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
item.setItemType(GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT); item.setItemType(GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT);
item.setSkuId(source.getSkuId()); item.setSkuId(source.getSkuId());
item.setSkuUnitId(source.getSkuUnitId()); item.setSkuUnitId(source.getSkuUnitId());
item.setGifted(Boolean.TRUE.equals(source.getGifted()));
item.setProductNameSnapshot(source.getProductNameSnapshot()); item.setProductNameSnapshot(source.getProductNameSnapshot());
item.setProductIdSnapshot(source.getProductIdSnapshot()); item.setProductIdSnapshot(source.getProductIdSnapshot());
item.setSkuCodeSnapshot(source.getSkuCodeSnapshot()); item.setSkuCodeSnapshot(source.getSkuCodeSnapshot());
@@ -284,6 +291,32 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
return item; return item;
} }
private void normalizePurchaseItems(List<GaoCustomerPackagePurchaseItem> itemList) {
Map<String, Integer> indexMap = new LinkedHashMap<>();
for (int i = 0; i < itemList.size(); i++) {
GaoCustomerPackagePurchaseItem item = itemList.get(i);
if (item == null) {
continue;
}
item.setGifted(Boolean.TRUE.equals(item.getGifted()));
if (item.getSkuId() == null || item.getSkuUnitId() == null || item.getTotalQuantity() == null) {
continue;
}
String key = item.getSkuId() + "|" + item.getSkuUnitId() + "|" + item.getGifted();
Integer existingIndex = indexMap.putIfAbsent(key, i);
if (existingIndex == null) {
continue;
}
GaoCustomerPackagePurchaseItem existingItem = itemList.get(existingIndex);
if (existingItem == null || existingItem.getTotalQuantity() == null) {
continue;
}
existingItem.setTotalQuantity(existingItem.getTotalQuantity().add(item.getTotalQuantity()));
itemList.set(i, null);
}
itemList.removeIf(item -> item == null);
}
private void verifyPurchaseItems(List<GaoCustomerPackagePurchaseItem> itemList) { private void verifyPurchaseItems(List<GaoCustomerPackagePurchaseItem> itemList) {
Set<String> keySet = new HashSet<>(); Set<String> keySet = new HashSet<>();
for (int i = 0; i < itemList.size(); i++) { for (int i = 0; i < itemList.size(); i++) {
@@ -294,14 +327,15 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
TimiException.required(item.getTotalQuantity(), "购买产品数量不能为空"); TimiException.required(item.getTotalQuantity(), "购买产品数量不能为空");
TimiException.requiredTrue(0 <= item.getTotalQuantity().signum(), "购买产品数量不能为负数"); TimiException.requiredTrue(0 <= item.getTotalQuantity().signum(), "购买产品数量不能为负数");
TimiException.requiredTrue(3 >= item.getTotalQuantity().stripTrailingZeros().scale(), "购买产品数量最多保留三位小数"); TimiException.requiredTrue(3 >= item.getTotalQuantity().stripTrailingZeros().scale(), "购买产品数量最多保留三位小数");
String key = item.getSkuId() + "|" + item.getSkuUnitId(); item.setGifted(Boolean.TRUE.equals(item.getGifted()));
String key = item.getSkuId() + "|" + item.getSkuUnitId() + "|" + item.getGifted();
TimiException.requiredTrue(keySet.add(key), "购买套餐内 SKU 和单位不能重复"); TimiException.requiredTrue(keySet.add(key), "购买套餐内 SKU 和单位不能重复");
item.setSort(TimiJava.defaultIfNull(item.getSort(), i)); item.setSort(TimiJava.defaultIfNull(item.getSort(), i));
} }
} }
private List<GaoCustomerPackagePurchaseItem> resolvePurchaseItems(String storeId, List<GaoCustomerPackagePurchaseItem> requestItemList, private List<GaoCustomerPackagePurchaseItem> resolvePurchaseItems(String storeId, List<GaoCustomerPackagePurchaseItem> requestItemList,
List<GaoPackageConfigItem> configItemList, boolean canOverridePriceAndQuantity) { List<GaoPackageConfigItem> configItemList, boolean canOverridePriceAndQuantity, boolean canCreateGift) {
Map<String, List<GaoPackageConfigItem>> configItemMap = configItemList.stream() Map<String, List<GaoPackageConfigItem>> configItemMap = configItemList.stream()
.collect(Collectors.groupingBy(GaoPackageConfigItem::getSkuId)); .collect(Collectors.groupingBy(GaoPackageConfigItem::getSkuId));
Map<String, BigDecimal> configBaseQuantityMap = new HashMap<>(); Map<String, BigDecimal> configBaseQuantityMap = new HashMap<>();
@@ -312,23 +346,32 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
Map<String, BigDecimal> requestBaseQuantityMap = new HashMap<>(); Map<String, BigDecimal> requestBaseQuantityMap = new HashMap<>();
List<GaoCustomerPackagePurchaseItem> result = new ArrayList<>(); List<GaoCustomerPackagePurchaseItem> result = new ArrayList<>();
for (GaoCustomerPackagePurchaseItem requestItem : requestItemList) { for (GaoCustomerPackagePurchaseItem requestItem : requestItemList) {
boolean gifted = Boolean.TRUE.equals(requestItem.getGifted());
List<GaoPackageConfigItem> skuConfigItemList = configItemMap.get(requestItem.getSkuId()); List<GaoPackageConfigItem> skuConfigItemList = configItemMap.get(requestItem.getSkuId());
TimiException.requiredTrue(skuConfigItemList != null && !skuConfigItemList.isEmpty(), "购买 SKU 不属于套餐配置"); if (gifted) {
TimiException.requiredTrue(canCreateGift, "没有客户套餐创建赠送权限");
} else {
TimiException.requiredTrue(skuConfigItemList != null && !skuConfigItemList.isEmpty(), "购买 SKU 不属于套餐配置");
}
GaoProductSkuUnitSelection selection = skuService.getSellableSkuUnit(storeId, requestItem.getSkuId(), requestItem.getSkuUnitId()); GaoProductSkuUnitSelection selection = skuService.getSellableSkuUnit(storeId, requestItem.getSkuId(), requestItem.getSkuUnitId());
TimiException.requiredTrue(skuConfigItemList.stream().anyMatch(configItem -> 0 <= configItem.getBaseQuantityPerUnitSnapshot() if (!gifted) {
.compareTo(selection.getUnit().getBaseQuantityPerUnit())), "套餐权益单位不能高于配置单位"); TimiException.requiredTrue(skuConfigItemList.stream().anyMatch(configItem -> 0 <= configItem.getBaseQuantityPerUnitSnapshot()
.compareTo(selection.getUnit().getBaseQuantityPerUnit())), "套餐权益单位不能高于配置单位");
}
BigDecimal requestBaseQuantity = toBaseQuantity(requestItem.getTotalQuantity(), selection.getUnit().getBaseQuantityPerUnit(), "套餐权益基础数量最多保留三位小数"); BigDecimal requestBaseQuantity = toBaseQuantity(requestItem.getTotalQuantity(), selection.getUnit().getBaseQuantityPerUnit(), "套餐权益基础数量最多保留三位小数");
requestBaseQuantityMap.merge(requestItem.getSkuId(), requestBaseQuantity, BigDecimal::add); if (!gifted) {
requestBaseQuantityMap.merge(requestItem.getSkuId(), requestBaseQuantity, BigDecimal::add);
}
if (requestItem.getTotalQuantity().signum() == 0) { if (requestItem.getTotalQuantity().signum() == 0) {
continue; continue;
} }
if (!Boolean.TRUE.equals(selection.getSku().getAllowDecimalQuantity())) { if (!Boolean.TRUE.equals(selection.getSku().getAllowDecimalQuantity())) {
TimiException.requiredTrue(requestItem.getTotalQuantity().stripTrailingZeros().scale() <= 0, "该 SKU 不允许小数数量"); TimiException.requiredTrue(requestItem.getTotalQuantity().stripTrailingZeros().scale() <= 0, "该 SKU 不允许小数数量");
} }
GaoPackageConfigItem template = resolveItemTemplate(skuConfigItemList, selection.getUnit().getId());
GaoCustomerPackagePurchaseItem item = new GaoCustomerPackagePurchaseItem(); GaoCustomerPackagePurchaseItem item = new GaoCustomerPackagePurchaseItem();
item.setSkuId(selection.getSku().getId()); item.setSkuId(selection.getSku().getId());
item.setSkuUnitId(selection.getUnit().getId()); item.setSkuUnitId(selection.getUnit().getId());
item.setGifted(gifted);
item.setProductNameSnapshot(selection.getProduct().getName()); item.setProductNameSnapshot(selection.getProduct().getName());
item.setProductIdSnapshot(selection.getProduct().getId()); item.setProductIdSnapshot(selection.getProduct().getId());
item.setSkuCodeSnapshot(selection.getSku().getCode()); item.setSkuCodeSnapshot(selection.getSku().getCode());
@@ -337,8 +380,14 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
item.setUnitSnapshot(selection.getUnit().getName()); item.setUnitSnapshot(selection.getUnit().getName());
item.setBaseQuantityPerUnitSnapshot(selection.getUnit().getBaseQuantityPerUnit()); item.setBaseQuantityPerUnitSnapshot(selection.getUnit().getBaseQuantityPerUnit());
item.setTotalQuantity(requestItem.getTotalQuantity()); item.setTotalQuantity(requestItem.getTotalQuantity());
item.setSort(template.getSort()); if (gifted) {
item.setRemark(template.getRemark()); item.setSort(requestItem.getSort());
item.setRemark(requestItem.getRemark());
} else {
GaoPackageConfigItem template = resolveItemTemplate(skuConfigItemList, selection.getUnit().getId());
item.setSort(template.getSort());
item.setRemark(template.getRemark());
}
result.add(item); result.add(item);
} }
TimiException.requiredTrue(!result.isEmpty(), "购买套餐至少需要一个产品"); TimiException.requiredTrue(!result.isEmpty(), "购买套餐至少需要一个产品");
@@ -378,9 +427,10 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
List<GaoCustomerPackagePurchaseItem> requestItemList = TimiJava.defaultIfNull(request.getItemList(), List.of()); List<GaoCustomerPackagePurchaseItem> requestItemList = TimiJava.defaultIfNull(request.getItemList(), List.of());
verifyPurchaseItems(requestItemList); verifyPurchaseItems(requestItemList);
TimiException.requiredTrue(existingItemList.size() == requestItemList.size(), "requestNo 对应购买请求内容不一致"); TimiException.requiredTrue(existingItemList.size() == requestItemList.size(), "requestNo 对应购买请求内容不一致");
Map<String, GaoSalesOrderItem> existingItemMap = existingItemList.stream().collect(Collectors.toMap(item -> item.getSkuId() + "|" + item.getSkuUnitId(), Function.identity())); Map<String, GaoSalesOrderItem> existingItemMap = existingItemList.stream().collect(Collectors.toMap(item -> item.getSkuId() + "|" + item.getSkuUnitId() + "|"
+ Boolean.TRUE.equals(item.getGifted()), Function.identity()));
for (GaoCustomerPackagePurchaseItem item : requestItemList) { for (GaoCustomerPackagePurchaseItem item : requestItemList) {
GaoSalesOrderItem existingItem = existingItemMap.get(item.getSkuId() + "|" + item.getSkuUnitId()); GaoSalesOrderItem existingItem = existingItemMap.get(item.getSkuId() + "|" + item.getSkuUnitId() + "|" + Boolean.TRUE.equals(item.getGifted()));
TimiException.required(existingItem, "requestNo 对应购买请求内容不一致"); TimiException.required(existingItem, "requestNo 对应购买请求内容不一致");
TimiException.requiredTrue(existingItem.getItemType() == GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT TimiException.requiredTrue(existingItem.getItemType() == GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT
&& existingItem.getQuantity().compareTo(item.getTotalQuantity()) == 0 && existingItem.getQuantity().compareTo(item.getTotalQuantity()) == 0
@@ -453,9 +453,11 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
for (GaoSalesOrderItem item : itemList) { for (GaoSalesOrderItem item : itemList) {
TimiException.required(item, "销售订单明细不能为空"); TimiException.required(item, "销售订单明细不能为空");
TimiException.requiredTrue(item.getItemType() == expectedItemType, "订单项类型与订单类型不匹配"); TimiException.requiredTrue(item.getItemType() == expectedItemType, "订单项类型与订单类型不匹配");
item.setGifted(Boolean.TRUE.equals(item.getGifted()));
TimiException.required(item.getSkuId(), "订单项 SKU 不能为空"); TimiException.required(item.getSkuId(), "订单项 SKU 不能为空");
TimiException.required(item.getSkuUnitId(), "订单项 SKU 单位不能为空"); TimiException.required(item.getSkuUnitId(), "订单项 SKU 单位不能为空");
TimiException.requiredTrue(skuUnitKeySet.add(item.getSkuId() + "|" + item.getSkuUnitId()), String skuUnitKey = item.getSkuId() + "|" + item.getSkuUnitId() + "|" + item.getGifted();
TimiException.requiredTrue(skuUnitKeySet.add(skuUnitKey),
"同一订单不能重复 SKU 和单位"); "同一订单不能重复 SKU 和单位");
TimiException.required(item.getQuantity(), "订单项数量不能为空"); TimiException.required(item.getQuantity(), "订单项数量不能为空");
TimiException.requiredTrue(0 < item.getQuantity().signum(), "订单项数量必须大于 0"); TimiException.requiredTrue(0 < item.getQuantity().signum(), "订单项数量必须大于 0");
@@ -511,11 +513,14 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
List<GaoSalesOrderItem> existingItemList = itemMapper.selectBySalesOrderId(existing.getId()); List<GaoSalesOrderItem> existingItemList = itemMapper.selectBySalesOrderId(existing.getId());
TimiException.requiredTrue(existingItemList.size() == requestItemList.size(), "requestNo 对应订单内容不一致"); TimiException.requiredTrue(existingItemList.size() == requestItemList.size(), "requestNo 对应订单内容不一致");
Map<String, GaoSalesOrderItem> existingItemMap = existingItemList.stream() Map<String, GaoSalesOrderItem> existingItemMap = existingItemList.stream()
.collect(java.util.stream.Collectors.toMap(item -> item.getSkuId() + "|" + item.getSkuUnitId(), java.util.function.Function.identity())); .collect(java.util.stream.Collectors.toMap(item -> item.getSkuId() + "|" + item.getSkuUnitId() + "|" + Boolean.TRUE.equals(item.getGifted()),
java.util.function.Function.identity()));
for (GaoSalesOrderItem requestItem : requestItemList) { for (GaoSalesOrderItem requestItem : requestItemList) {
GaoSalesOrderItem existingItem = existingItemMap.get(requestItem.getSkuId() + "|" + requestItem.getSkuUnitId()); GaoSalesOrderItem existingItem = existingItemMap.get(requestItem.getSkuId() + "|" + requestItem.getSkuUnitId() + "|"
+ Boolean.TRUE.equals(requestItem.getGifted()));
TimiException.required(existingItem, "requestNo 对应订单内容不一致"); TimiException.required(existingItem, "requestNo 对应订单内容不一致");
TimiException.requiredTrue(existingItem.getItemType() == GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT TimiException.requiredTrue(existingItem.getItemType() == GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT
&& Boolean.TRUE.equals(existingItem.getGifted()) == Boolean.TRUE.equals(requestItem.getGifted())
&& existingItem.getQuantity().compareTo(requestItem.getQuantity()) == 0 && existingItem.getQuantity().compareTo(requestItem.getQuantity()) == 0
&& existingItem.getSort().equals(requestItem.getSort()) && existingItem.getSort().equals(requestItem.getSort())
&& sameText(existingItem.getRemark(), requestItem.getRemark()), "requestNo 对应订单内容不一致"); && sameText(existingItem.getRemark(), requestItem.getRemark()), "requestNo 对应订单内容不一致");
@@ -17,6 +17,9 @@ public class GaoCustomerPackagePurchaseItem {
/// SKU 单位 ID /// SKU 单位 ID
private String skuUnitId; private String skuUnitId;
/// true 表示请求将该明细作为赠送权益创建
private Boolean gifted;
/// 产品名称快照,由后端生成 /// 产品名称快照,由后端生成
private String productNameSnapshot; private String productNameSnapshot;
@@ -0,0 +1,5 @@
ALTER TABLE `gao_sales_order_item`
ADD COLUMN `gifted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'true 为赠送权益' AFTER `item_type`,
DROP KEY `uk_gao_sales_order_item_sku_unit`,
ADD UNIQUE KEY `uk_gao_sales_order_item_sku_unit_gifted` (`sales_order_id`, `sku_id`, `sku_unit_id`, `gifted`),
ADD KEY `idx_gao_sales_order_item_order_gifted` (`sales_order_id`, `gifted`, `sort`, `created_at`);