From 00bb3007556c645489b106c16c98ad097ef45150 Mon Sep 17 00:00:00 2001 From: Timi Date: Wed, 9 Sep 2026 01:49:40 +0800 Subject: [PATCH] support split fulfillment by base unit --- .../gao/entity/GaoOrderFulfillmentItem.java | 18 +- .../modules/gao/entity/GaoSalesOrderItem.java | 23 ++- .../gao/mapper/GaoSalesOrderItemMapper.java | 8 +- .../gao/service/GaoSalesOrderService.java | 8 +- .../GaoCustomerPackageServiceImplement.java | 115 +++++++---- .../GaoOrderFulfillmentServiceImplement.java | 183 +++++++++++------- .../GaoSalesOrderServiceImplement.java | 64 ++++-- .../vo/GaoOrderFulfillmentItemRequest.java | 3 + ...ort_gao_split_fulfillment_by_base_unit.sql | 23 +++ .../timi-server/gao/GaoProductChartMapper.xml | 16 +- .../gao/GaoProductSkuChartMapper.xml | 6 +- .../gao/GaoSalesOrderChartMapper.xml | 26 +-- .../gao/GaoSalesOrderItemMapper.xml | 16 +- 13 files changed, 320 insertions(+), 189 deletions(-) create mode 100644 src/main/resources/db/migration/timiserver/V74__support_gao_split_fulfillment_by_base_unit.sql diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoOrderFulfillmentItem.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoOrderFulfillmentItem.java index 645efbf..008dae5 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoOrderFulfillmentItem.java +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoOrderFulfillmentItem.java @@ -29,7 +29,7 @@ public class GaoOrderFulfillmentItem implements IDEntity, Creatable { /// SKU ID private String skuId; - /// SKU 单位 ID + /// 本次实际履约 SKU 单位 ID private String skuUnitId; /// 产品图片附件 @@ -45,17 +45,17 @@ public class GaoOrderFulfillmentItem implements IDEntity, Creatable { /// 基础单位数量 private BigDecimal baseQuantity; - /// 操作前待履约数量 - private BigDecimal reservedBefore; + /// 操作前待履约基础单位数量 + private BigDecimal reservedBaseBefore; - /// 操作后待履约数量 - private BigDecimal reservedAfter; + /// 操作后待履约基础单位数量 + private BigDecimal reservedBaseAfter; - /// 操作前已履约数量 - private BigDecimal fulfilledBefore; + /// 操作前已履约基础单位数量 + private BigDecimal fulfilledBaseBefore; - /// 操作后已履约数量 - private BigDecimal fulfilledAfter; + /// 操作后已履约基础单位数量 + private BigDecimal fulfilledBaseAfter; /// 产品名称快照 private String productNameSnapshot; diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoSalesOrderItem.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoSalesOrderItem.java index db2166d..eb66708 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoSalesOrderItem.java +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoSalesOrderItem.java @@ -71,11 +71,14 @@ public class GaoSalesOrderItem extends UUIDEntity { /// 购买数量 private BigDecimal quantity; - /// 待履约数量 - private BigDecimal reservedQuantity; + /// 购买基础单位数量 + private BigDecimal baseQuantity; - /// 已履约数量 - private BigDecimal fulfilledQuantity; + /// 待履约基础单位数量 + private BigDecimal reservedBaseQuantity; + + /// 已履约基础单位数量 + private BigDecimal fulfilledBaseQuantity; /// 实际单价,单位分 private Long unitPrice; @@ -89,15 +92,15 @@ public class GaoSalesOrderItem extends UUIDEntity { /// 备注 private String remark; - /// 待核销数量 + /// 待核销基础单位数量 @Transient - private BigDecimal pendingQuantity; + private BigDecimal pendingBaseQuantity; - /// 当前可履约数量 + /// 当前可履约基础单位数量 @Transient - private BigDecimal availableQuantity; + private BigDecimal availableBaseQuantity; - /// 权益剩余数量 + /// 权益剩余基础单位数量 @Transient - private BigDecimal remainingQuantity; + private BigDecimal remainingBaseQuantity; } diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoSalesOrderItemMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoSalesOrderItemMapper.java index a51eb31..484ad80 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoSalesOrderItemMapper.java +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoSalesOrderItemMapper.java @@ -22,9 +22,9 @@ public interface GaoSalesOrderItemMapper extends BaseMapper /// 检查履约权限 void checkFulfillmentPermission(GaoSalesOrder order); - /// 原子调整订单项待履约数量 - int updateItemReservedQuantity(String itemId, BigDecimal delta); + /// 原子调整订单项待履约基础单位数量 + int updateItemReservedBaseQuantity(String itemId, BigDecimal delta); - /// 原子调整订单项已履约数量 - int updateItemFulfilledQuantity(String itemId, BigDecimal delta); + /// 原子调整订单项已履约基础单位数量 + int updateItemFulfilledBaseQuantity(String itemId, BigDecimal delta); /// 锁定订单明细 GaoSalesOrderItem getItemForUpdate(String itemId); diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerPackageServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerPackageServiceImplement.java index d63d3e9..6e75900 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerPackageServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerPackageServiceImplement.java @@ -22,6 +22,7 @@ import com.imyeyu.api.modules.gao.vo.GaoCustomerPackagePage; import com.imyeyu.api.modules.gao.vo.GaoCustomerPackagePurchaseRequest; import com.imyeyu.api.modules.gao.vo.GaoCustomerPackagePurchaseItem; import com.imyeyu.api.modules.gao.vo.GaoPackageOrderCreateData; +import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSelection; import com.imyeyu.api.modules.gao.vo.GaoSalesOrderCancelRequest; import com.imyeyu.api.modules.system.entity.Logger; import com.imyeyu.api.modules.system.service.LoggerService; @@ -38,6 +39,10 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -53,6 +58,8 @@ import java.util.stream.Collectors; @RequiredArgsConstructor public class GaoCustomerPackageServiceImplement extends AbstractEntityService implements GaoCustomerPackageService { + private static final BigDecimal ZERO = BigDecimal.ZERO; + private final GaoCustomerPackageMapper mapper; private final GaoPackageConfigService packageConfigService; private final GaoProductSkuService skuService; @@ -143,9 +150,13 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService itemList = new java.util.ArrayList<>(request.getItemList()); - verifyPurchaseItems(itemList); - itemList.sort(java.util.Comparator.comparing(GaoCustomerPackagePurchaseItem::getSort)); + List requestItemList = new ArrayList<>(request.getItemList()); + verifyPurchaseItems(requestItemList); + boolean canOverridePriceAndQuantity = permissionChecker.hasAny(ModuleCode.GAO, + com.imyeyu.api.modules.gao.bean.GaoPermissionCode.CUSTOMER_PACKAGE_PRICE_OVERRIDE.getValue()); + List itemList = resolvePurchaseItems(config.getStoreId(), requestItemList, configItemList, canOverridePriceAndQuantity); + itemList.sort(Comparator.comparing(GaoCustomerPackagePurchaseItem::getSort).thenComparing(GaoCustomerPackagePurchaseItem::getSkuId) + .thenComparing(GaoCustomerPackagePurchaseItem::getSkuUnitId)); request.setItemList(itemList); GaoSalesOrder existingOrder = salesOrderService.getByStoreIdAndRequestNo(config.getStoreId(), request.getRequestNo()); @@ -154,19 +165,14 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService orderItemList = itemList.stream().map(this::toOrderItem).toList(); @@ -267,8 +273,9 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService itemList) { - TimiException.requiredTrue(!itemList.isEmpty(), "购买套餐至少需要一个产品"); Set keySet = new HashSet<>(); for (int i = 0; i < itemList.size(); i++) { GaoCustomerPackagePurchaseItem item = itemList.get(i); @@ -285,7 +291,7 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService= item.getTotalQuantity().stripTrailingZeros().scale(), "购买产品数量最多保留三位小数"); String key = item.getSkuId() + "|" + item.getSkuUnitId(); TimiException.requiredTrue(keySet.add(key), "购买套餐内 SKU 和单位不能重复"); @@ -293,38 +299,69 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService itemList) { - for (GaoPackageConfigItem item : itemList) { - TimiException.required(item.getSkuId(), "套餐配置缺少 SKU"); - TimiException.required(item.getSkuUnitId(), "套餐配置缺少 SKU 单位"); - // 通过 SKU 服务唯一选品入口重新校验 SKU 和单位仍处于可销售状态 - skuService.getSellableSkuUnit(storeId, item.getSkuId(), item.getSkuUnitId()); + private List resolvePurchaseItems(String storeId, List requestItemList, + List configItemList, boolean canOverridePriceAndQuantity) { + Map> configItemMap = configItemList.stream() + .collect(Collectors.groupingBy(GaoPackageConfigItem::getSkuId)); + Map configBaseQuantityMap = new HashMap<>(); + for (GaoPackageConfigItem configItem : configItemList) { + configBaseQuantityMap.merge(configItem.getSkuId(), + toBaseQuantity(configItem.getDefaultQuantity(), configItem.getBaseQuantityPerUnitSnapshot(), "套餐配置基础数量最多保留三位小数"), BigDecimal::add); } + Map requestBaseQuantityMap = new HashMap<>(); + List result = new ArrayList<>(); + for (GaoCustomerPackagePurchaseItem requestItem : requestItemList) { + List skuConfigItemList = configItemMap.get(requestItem.getSkuId()); + TimiException.requiredTrue(skuConfigItemList != null && !skuConfigItemList.isEmpty(), "购买 SKU 不属于套餐配置"); + GaoProductSkuUnitSelection selection = skuService.getSellableSkuUnit(storeId, requestItem.getSkuId(), requestItem.getSkuUnitId()); + TimiException.requiredTrue(skuConfigItemList.stream().anyMatch(configItem -> 0 <= configItem.getBaseQuantityPerUnitSnapshot() + .compareTo(selection.getUnit().getBaseQuantityPerUnit())), "套餐权益单位不能高于配置单位"); + BigDecimal requestBaseQuantity = toBaseQuantity(requestItem.getTotalQuantity(), selection.getUnit().getBaseQuantityPerUnit(), "套餐权益基础数量最多保留三位小数"); + requestBaseQuantityMap.merge(requestItem.getSkuId(), requestBaseQuantity, BigDecimal::add); + if (requestItem.getTotalQuantity().signum() == 0) { + continue; + } + if (!Boolean.TRUE.equals(selection.getSku().getAllowDecimalQuantity())) { + TimiException.requiredTrue(requestItem.getTotalQuantity().stripTrailingZeros().scale() <= 0, "该 SKU 不允许小数数量"); + } + GaoPackageConfigItem template = resolveItemTemplate(skuConfigItemList, selection.getUnit().getId()); + GaoCustomerPackagePurchaseItem item = new GaoCustomerPackagePurchaseItem(); + item.setSkuId(selection.getSku().getId()); + item.setSkuUnitId(selection.getUnit().getId()); + item.setProductNameSnapshot(selection.getProduct().getName()); + item.setProductIdSnapshot(selection.getProduct().getId()); + item.setSkuCodeSnapshot(selection.getSku().getCode()); + item.setSkuNameSnapshot(selection.getSku().getName()); + item.setSpecSummarySnapshot(selection.getSku().getSpecSummary()); + item.setUnitSnapshot(selection.getUnit().getName()); + item.setBaseQuantityPerUnitSnapshot(selection.getUnit().getBaseQuantityPerUnit()); + item.setTotalQuantity(requestItem.getTotalQuantity()); + item.setSort(template.getSort()); + item.setRemark(template.getRemark()); + result.add(item); + } + TimiException.requiredTrue(!result.isEmpty(), "购买套餐至少需要一个产品"); + if (!canOverridePriceAndQuantity) { + for (Map.Entry entry : configBaseQuantityMap.entrySet()) { + TimiException.requiredTrue(entry.getValue().compareTo(requestBaseQuantityMap.getOrDefault(entry.getKey(), ZERO)) == 0, + "普通员工不能修改套餐权益配额"); + } + } + return result; } - private void verifySameStructureAsConfig(List itemList, List configItemList) { - TimiException.requiredTrue(itemList.size() == configItemList.size(), "套餐权益项目必须与套餐配置一致"); - Map itemMap = itemList.stream().collect(Collectors.toMap(item -> item.getSkuId() + "|" + item.getSkuUnitId(), Function.identity())); - for (GaoPackageConfigItem configItem : configItemList) { - 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()) - && sameText(item.getRemark(), configItem.getRemark()), "套餐权益项目的名称、单位、排序和备注不可修改"); - } + private GaoPackageConfigItem resolveItemTemplate(List configItemList, String skuUnitId) { + return configItemList.stream().filter(item -> skuUnitId.equals(item.getSkuUnitId())).findFirst() + .orElseGet(() -> configItemList.stream().min(Comparator.comparing(GaoPackageConfigItem::getSort)).orElseThrow()); } - private void verifySameQuantityAsConfig(List itemList, List configItemList) { - Map itemMap = itemList.stream().collect(Collectors.toMap(item -> item.getSkuId() + "|" + item.getSkuUnitId(), Function.identity())); - for (GaoPackageConfigItem configItem : configItemList) { - GaoCustomerPackagePurchaseItem item = itemMap.get(configItem.getSkuId() + "|" + configItem.getSkuUnitId()); - TimiException.requiredTrue(item.getTotalQuantity().compareTo(configItem.getDefaultQuantity()) == 0, "普通员工不能修改套餐权益配额"); + private BigDecimal toBaseQuantity(BigDecimal quantity, BigDecimal baseQuantityPerUnit, String message) { + BigDecimal result = quantity.multiply(baseQuantityPerUnit); + TimiException.requiredTrue(3 >= result.stripTrailingZeros().scale(), message); + try { + return result.setScale(3, RoundingMode.UNNECESSARY); + } catch (ArithmeticException e) { + throw new TimiException(com.imyeyu.java.bean.timi.TimiCode.ERROR, message, e); } } diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoOrderFulfillmentServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoOrderFulfillmentServiceImplement.java index 631c9dc..ce884f8 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoOrderFulfillmentServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoOrderFulfillmentServiceImplement.java @@ -29,6 +29,7 @@ import com.imyeyu.api.modules.gao.vo.GaoOrderFulfillmentItemRequest; import com.imyeyu.api.modules.gao.vo.GaoOrderFulfillmentPage; import com.imyeyu.api.modules.gao.vo.GaoOrderFulfillmentPrepareStockRequest; import com.imyeyu.api.modules.gao.vo.GaoOrderFulfillmentRevokeRequest; +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; @@ -46,6 +47,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; @@ -194,12 +196,11 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService sourceList = loadSources(order, itemRequests(snapshot.getId()), false); + List sourceList = loadPersistedSources(order, itemMapper.selectByFulfillmentId(snapshot.getId())); GaoOrderFulfillment fulfillment = lockFulfillment(snapshot.getId(), order); storeScopeService.checkStoreId(fulfillment.getStoreId()); TimiException.requiredTrue(fulfillment.getStatus() == GaoOrderFulfillment.Status.WAITING_STOCK, "履约单不是等待库存状态"); TimiException.requiredTrue(order.getStatus() != GaoSalesOrder.Status.CANCELLED, "已取消订单不能准备库存"); - salesOrderService.checkFulfillmentPermission(order); List fulfillmentItemList = itemMapper.selectByFulfillmentIdForUpdate(fulfillment.getId()); TimiException.requiredTrue(!fulfillmentItemList.isEmpty(), "履约单没有明细"); List stockItems = buildStockItems(order.getStoreId(), sourceList); @@ -228,12 +229,11 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService sourceList = loadSources(order, itemRequests(snapshot.getId()), false); + List sourceList = loadPersistedSources(order, itemMapper.selectByFulfillmentId(snapshot.getId())); GaoOrderFulfillment fulfillment = lockFulfillment(snapshot.getId(), order); storeScopeService.checkStoreId(fulfillment.getStoreId()); TimiException.requiredTrue(fulfillment.getStatus() == GaoOrderFulfillment.Status.PENDING, "履约单不是待核销状态"); TimiException.requiredTrue(order.getStatus() != GaoSalesOrder.Status.CANCELLED, "已取消订单不能核销履约"); - salesOrderService.checkFulfillmentPermission(order); List fulfillmentItemList = itemMapper.selectByFulfillmentIdForUpdate(fulfillment.getId()); TimiException.requiredTrue(!fulfillmentItemList.isEmpty(), "履约单没有明细"); assertReserved(sourceList); @@ -283,7 +283,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService sourceList = loadSources(order, itemRequests(snapshot.getId()), false); + List sourceList = loadPersistedSources(order, itemMapper.selectByFulfillmentId(snapshot.getId())); GaoOrderFulfillment fulfillment = lockFulfillment(snapshot.getId(), order); storeScopeService.checkStoreId(fulfillment.getStoreId()); TimiException.requiredTrue(fulfillment.getStatus() == GaoOrderFulfillment.Status.WAITING_STOCK @@ -326,7 +326,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService sourceItemList = itemMapper.selectByFulfillmentId(sourceSnapshot.getId()); - List sourceList = loadSources(order, copyRequestItems(sourceItemList), false); + List sourceList = loadPersistedSources(order, sourceItemList); GaoOrderFulfillment source = lockFulfillment(sourceSnapshot.getId(), order); storeScopeService.checkStoreId(source.getStoreId()); TimiException.requiredTrue(source.getStatus() == GaoOrderFulfillment.Status.VERIFIED && source.getSourceFulfillmentId() == null, "只有原始已核销履约可以冲正"); @@ -356,10 +356,10 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService sourceList) { for (SourceEntry source : sourceList) { - TimiException.requiredTrue(1 == salesOrderService.updateItemReservedQuantity(source.sourceId(), source.quantity()), "订单明细占用失败"); + TimiException.requiredTrue(1 == salesOrderService.updateItemReservedBaseQuantity(source.sourceId(), source.baseQuantity()), "订单明细占用失败"); GaoOrderFulfillmentItem item = source.toFulfillmentItem(fulfillment.getId()); itemMapper.insert(item); } } private void decreaseReserved(SourceEntry source) { - TimiException.requiredTrue(1 == salesOrderService.updateItemReservedQuantity(source.sourceId(), source.quantity().negate()), "订单明细占用释放失败"); + TimiException.requiredTrue(1 == salesOrderService.updateItemReservedBaseQuantity(source.sourceId(), source.baseQuantity().negate()), "订单明细占用释放失败"); } private void increaseFulfilled(SourceEntry source) { - TimiException.requiredTrue(1 == salesOrderService.updateItemFulfilledQuantity(source.sourceId(), source.quantity()), "订单明细履约失败"); + TimiException.requiredTrue(1 == salesOrderService.updateItemFulfilledBaseQuantity(source.sourceId(), source.baseQuantity()), "订单明细履约失败"); } private void decreaseFulfilled(SourceEntry source) { - TimiException.requiredTrue(0 <= source.fulfilledBefore().compareTo(source.quantity()), "已履约数量不足以冲正"); - TimiException.requiredTrue(1 == salesOrderService.updateItemFulfilledQuantity(source.sourceId(), source.quantity().negate()), "订单明细冲正失败"); + TimiException.requiredTrue(0 <= source.fulfilledBaseBefore().compareTo(source.baseQuantity()), "已履约数量不足以冲正"); + TimiException.requiredTrue(1 == salesOrderService.updateItemFulfilledBaseQuantity(source.sourceId(), source.baseQuantity().negate()), "订单明细冲正失败"); } private void assertReserved(List sourceList) { for (SourceEntry source : sourceList) { - TimiException.requiredTrue(0 <= source.reservedBefore().compareTo(source.quantity()), "履约待占用数量不足"); + TimiException.requiredTrue(0 <= source.reservedBaseBefore().compareTo(source.baseQuantity()), "履约待占用数量不足"); } } @@ -430,6 +430,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService loadPersistedSources(GaoSalesOrder order, List itemList) { + TimiException.requiredTrue(itemList != null && !itemList.isEmpty(), "履约单没有明细"); + List sortedItemList = new ArrayList<>(itemList); + sortedItemList.sort(Comparator.comparing(GaoOrderFulfillmentItem::getSourceOrderItemId)); + Set sourceIdSet = new HashSet<>(); + List result = new ArrayList<>(); + for (GaoOrderFulfillmentItem item : sortedItemList) { + TimiException.required(item, "履约明细不能为空"); + String sourceId = requiredText(item.getSourceOrderItemId(), "履约明细来源订单项不能为空"); + TimiException.requiredTrue(sourceIdSet.add(sourceId), "同一履约单不能重复来源明细"); + GaoSalesOrderItem source = salesOrderService.getItemForUpdate(sourceId); + validateSourceOrderItem(order, source); + 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(item.getBaseQuantityPerUnitSnapshot()); + BigDecimal quantity = requiredPositiveQuantity(item.getQuantity(), "履约数量"); + BigDecimal baseQuantity = requiredBaseQuantity(item.getBaseQuantity()); + TimiException.requiredTrue(baseQuantity.compareTo(toBaseQuantity(quantity, baseQuantityPerUnit, "履约基础数量最多保留三位小数")) == 0, + "履约基础数量与单位换算快照不一致"); + result.add(buildSource(order, source, item.getSkuUnitId(), item.getUnitSnapshot(), baseQuantityPerUnit, quantity, baseQuantity, product, sku, false)); + } + return result; } - private SourceEntry buildSource(GaoSalesOrder order, String orderItemId, String skuId, String unitId, String productId, - String productName, String skuCode, String skuName, String specSummary, JsonNode skuSpecSnapshot, String unitName, BigDecimal baseQuantityPerUnit, BigDecimal quantity, BigDecimal totalQuantity, - BigDecimal reservedQuantity, BigDecimal fulfilledQuantity, GaoProduct product, GaoProductSku sku, GaoProductSkuUnit unit, - boolean checkAvailable) { + private SourceEntry toSource(GaoSalesOrder order, GaoSalesOrderItem source, GaoProduct product, GaoProductSku sku, + GaoProductSkuUnit unit, BigDecimal quantity, boolean checkAvailable) { + TimiException.requiredTrue(source.getSkuId().equals(sku.getId()), "履约单位不属于来源 SKU"); + TimiException.requiredTrue(order.getStoreId().equals(product.getStoreId()), "履约来源产品不属于当前门店"); + quantity = requiredPositiveQuantity(quantity, "履约数量"); if (!Boolean.TRUE.equals(sku.getAllowDecimalQuantity())) { TimiException.requiredTrue(quantity.stripTrailingZeros().scale() <= 0, "该产品不允许小数数量"); } - BigDecimal reservedBefore = decimal(reservedQuantity); - BigDecimal fulfilledBefore = decimal(fulfilledQuantity); + BigDecimal baseQuantityPerUnit = requiredBaseQuantityPerUnit(unit.getBaseQuantityPerUnit()); + BigDecimal baseQuantity = toBaseQuantity(quantity, baseQuantityPerUnit, "履约基础数量最多保留三位小数"); + return buildSource(order, source, unit.getId(), unit.getName(), baseQuantityPerUnit, quantity, baseQuantity, product, sku, checkAvailable); + } + + private SourceEntry buildSource(GaoSalesOrder order, GaoSalesOrderItem source, String unitId, String unitName, + BigDecimal baseQuantityPerUnit, BigDecimal quantity, BigDecimal baseQuantity, GaoProduct product, GaoProductSku sku, + boolean checkAvailable) { + BigDecimal reservedBaseBefore = decimal(source.getReservedBaseQuantity()); + BigDecimal fulfilledBaseBefore = decimal(source.getFulfilledBaseQuantity()); + BigDecimal totalBaseQuantity = requiredBaseQuantity(source.getBaseQuantity()); if (checkAvailable) { - BigDecimal available = decimal(totalQuantity).subtract(reservedBefore).subtract(fulfilledBefore); - TimiException.requiredTrue(0 <= available.compareTo(quantity), "来源产品可履约数量不足"); + BigDecimal availableBaseQuantity = totalBaseQuantity.subtract(reservedBaseBefore).subtract(fulfilledBaseBefore); + TimiException.requiredTrue(0 <= availableBaseQuantity.compareTo(baseQuantity), "来源产品可履约数量不足"); } - return new SourceEntry(orderItemId, skuId, unitId, productId, productName, skuCode, skuName, specSummary, skuSpecSnapshot, unitName, baseQuantityPerUnit, - quantity, decimal(totalQuantity), reservedBefore, fulfilledBefore, product, sku, unit); + return new SourceEntry(source.getId(), source.getSkuId(), unitId, source.getProductIdSnapshot(), source.getProductNameSnapshot(), + source.getSkuCodeSnapshot(), source.getSkuNameSnapshot(), source.getSpecSummarySnapshot(), source.getSkuSpecSnapshot(), unitName, + baseQuantityPerUnit, quantity, baseQuantity, totalBaseQuantity, reservedBaseBefore, fulfilledBaseBefore, product, sku); + } + + private void validateSourceOrderItem(GaoSalesOrder order, GaoSalesOrderItem source) { + TimiException.required(source, "not found sales order item"); + TimiException.requiredTrue(order.getId().equals(source.getSalesOrderId()), "订单明细不属于当前订单"); + GaoSalesOrderItem.ItemType expectedItemType = order.getOrderType() == GaoSalesOrder.OrderType.PACKAGE + ? GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT : GaoSalesOrderItem.ItemType.DIRECT_PRODUCT; + TimiException.requiredTrue(source.getItemType() == expectedItemType, "订单项类型与订单类型不匹配"); } private List buildStockItems(String storeId, List sourceList) { @@ -489,7 +523,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService Boolean.TRUE.equals(unit.getIsBase())).findFirst().orElse(null); TimiException.required(baseUnit, "实物产品缺少基础库存单位"); - BigDecimal baseQuantity = source.quantity().multiply(source.unit().getBaseQuantityPerUnit()); + BigDecimal baseQuantity = source.baseQuantity(); GaoStockChangeItem item = itemMap.get(source.sku().getId()); if (item == null) { item = GaoStockChangeItem.of(source.product(), source.sku(), baseUnit, baseQuantity, baseQuantity); @@ -528,21 +562,6 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService itemRequests(String fulfillmentId) { - return copyRequestItems(itemMapper.selectByFulfillmentId(fulfillmentId)); - } - - private List copyRequestItems(List itemList) { - List result = new ArrayList<>(); - for (GaoOrderFulfillmentItem item : TimiJava.safeIterable(itemList)) { - GaoOrderFulfillmentItemRequest request = new GaoOrderFulfillmentItemRequest(); - request.setSourceOrderItemId(item.getSourceOrderItemId()); - request.setQuantity(item.getQuantity()); - result.add(request); - } - return result; - } - private GaoOrderFulfillmentItem copyFulfillmentItem(GaoOrderFulfillmentItem source) { GaoOrderFulfillmentItem result = new GaoOrderFulfillmentItem(); result.setSkuId(source.getSkuId()); @@ -561,20 +580,36 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService= value.stripTrailingZeros().scale(), fieldName + "最多保留三位小数"); + return value; + } + + private BigDecimal requiredBaseQuantity(BigDecimal value) { + TimiException.required(value, "基础单位数量不能为空"); + TimiException.requiredTrue(0 < value.signum(), "基础单位数量必须大于 0"); + TimiException.requiredTrue(3 >= value.stripTrailingZeros().scale(), "基础单位数量最多保留三位小数"); + return value; + } + + private BigDecimal toBaseQuantity(BigDecimal quantity, BigDecimal baseQuantityPerUnit, String message) { + BigDecimal result = quantity.multiply(baseQuantityPerUnit); + TimiException.requiredTrue(3 >= result.stripTrailingZeros().scale(), message); + try { + return result.setScale(3, RoundingMode.UNNECESSARY); + } catch (ArithmeticException e) { + throw new TimiException(TimiCode.ERROR, message, e); + } + } + private void verifyIdempotency(GaoOrderFulfillment existing, GaoOrderFulfillmentCreateRequest request) { TimiException.requiredTrue(existing.getSalesOrderId().equals(request.getSalesOrderId()) && (request.getDeliveredAt() == null || request.getDeliveredAt().equals(existing.getDeliveredAt())) @@ -582,12 +617,14 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService existingItems = itemMapper.selectByFulfillmentId(existing.getId()); List requestItems = request.getItemList(); TimiException.requiredTrue(existingItems.size() == (requestItems == null ? 0 : requestItems.size()), "requestNo 对应履约请求内容不一致"); - Map requestMap = new HashMap<>(); + Map requestMap = new HashMap<>(); for (GaoOrderFulfillmentItemRequest item : TimiJava.safeIterable(requestItems)) { - requestMap.put(item.getSourceOrderItemId(), item.getQuantity()); + requestMap.put(item.getSourceOrderItemId(), item); } for (GaoOrderFulfillmentItem item : existingItems) { - TimiException.requiredTrue(item.getQuantity().compareTo(requestMap.getOrDefault(item.getSourceOrderItemId(), ZERO)) == 0, "requestNo 对应履约请求内容不一致"); + GaoOrderFulfillmentItemRequest requestItem = requestMap.get(item.getSourceOrderItemId()); + TimiException.requiredTrue(requestItem != null && item.getSkuUnitId().equals(requestItem.getSkuUnitId()) + && item.getQuantity().compareTo(requestItem.getQuantity()) == 0, "requestNo 对应履约请求内容不一致"); } } @@ -671,8 +708,8 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService itemList = itemMapper.selectBySalesOrderId(order.getId()); boolean fulfilled = !itemList.isEmpty(); for (GaoSalesOrderItem item : itemList) { - if (decimal(item.getFulfilledQuantity()).compareTo(decimal(item.getQuantity())) < 0) { + if (decimal(item.getFulfilledBaseQuantity()).compareTo(decimal(item.getBaseQuantity())) < 0) { fulfilled = false; break; } @@ -383,8 +389,9 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService= item.getQuantity().stripTrailingZeros().scale(), "订单项数量最多保留三位小数"); TimiException.required(item.getBaseQuantityPerUnitSnapshot(), "订单项单位换算快照不能为空"); TimiException.requiredTrue(0 < item.getBaseQuantityPerUnitSnapshot().signum(), "订单项单位换算快照必须大于 0"); - TimiException.requiredTrue(item.getReservedQuantity() != null && item.getFulfilledQuantity() != null, + TimiException.required(item.getBaseQuantity(), "订单项基础单位数量不能为空"); + TimiException.requiredTrue(item.getBaseQuantity().compareTo(toBaseQuantity(item.getQuantity(), item.getBaseQuantityPerUnitSnapshot(), "订单项基础单位数量最多保留三位小数")) == 0, + "订单项基础单位数量与单位换算快照不一致"); + TimiException.requiredTrue(item.getReservedBaseQuantity() != null && item.getFulfilledBaseQuantity() != null, "订单项履约数量不能为空"); - TimiException.requiredTrue(0 <= item.getReservedQuantity().signum() && 0 <= item.getFulfilledQuantity().signum(), + TimiException.requiredTrue(0 <= item.getReservedBaseQuantity().signum() && 0 <= item.getFulfilledBaseQuantity().signum(), "订单项履约数量不能为负数"); - TimiException.requiredTrue(item.getReservedQuantity().add(item.getFulfilledQuantity()).compareTo(item.getQuantity()) <= 0, + TimiException.requiredTrue(item.getReservedBaseQuantity().add(item.getFulfilledBaseQuantity()).compareTo(item.getBaseQuantity()) <= 0, "订单项履约数量不能超过购买数量"); if (expectedItemType == GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT) { TimiException.requiredNull(item.getUnitPrice(), "套餐订单项不能填写单价"); @@ -544,12 +554,12 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService itemList) { @@ -606,6 +616,24 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService= result.stripTrailingZeros().scale(), message); + try { + return result.setScale(3, RoundingMode.UNNECESSARY); + } catch (ArithmeticException e) { + throw new TimiException(TimiCode.ERROR, message, e); + } + } + private long toCentAmount(BigDecimal quantity, long unitPrice) { try { return BigDecimal.valueOf(unitPrice).multiply(quantity).setScale(0, RoundingMode.UNNECESSARY).longValueExact(); diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoOrderFulfillmentItemRequest.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoOrderFulfillmentItemRequest.java index b21a153..b0b6b36 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoOrderFulfillmentItemRequest.java +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoOrderFulfillmentItemRequest.java @@ -14,6 +14,9 @@ public class GaoOrderFulfillmentItemRequest { /// 来源销售订单明细 ID private String sourceOrderItemId; + /// 本次实际履约 SKU 单位 ID + private String skuUnitId; + /// 本次履约数量 private BigDecimal quantity; } diff --git a/src/main/resources/db/migration/timiserver/V74__support_gao_split_fulfillment_by_base_unit.sql b/src/main/resources/db/migration/timiserver/V74__support_gao_split_fulfillment_by_base_unit.sql new file mode 100644 index 0000000..e2167de --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V74__support_gao_split_fulfillment_by_base_unit.sql @@ -0,0 +1,23 @@ +-- 销售订单明细的履约计数统一改为基础单位,订单下单单位和数量仍保留用于展示 +ALTER TABLE `gao_sales_order_item` + ADD COLUMN `base_quantity` DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '购买基础单位数量' AFTER `quantity`, + ADD COLUMN `reserved_base_quantity` DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '待履约基础单位数量' AFTER `base_quantity`, + ADD COLUMN `fulfilled_base_quantity` DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '已履约基础单位数量' AFTER `reserved_base_quantity`; + +UPDATE `gao_sales_order_item` +SET `base_quantity` = `quantity` * `base_quantity_per_unit_snapshot`, + `reserved_base_quantity` = `reserved_quantity` * `base_quantity_per_unit_snapshot`, + `fulfilled_base_quantity` = `fulfilled_quantity` * `base_quantity_per_unit_snapshot`; + +ALTER TABLE `gao_sales_order_item` + DROP COLUMN `reserved_quantity`, + DROP COLUMN `fulfilled_quantity`; + +-- 履约明细的前后值改名,明确其计量单位为基础单位 +ALTER TABLE `gao_order_fulfillment_item` + CHANGE COLUMN `reserved_before` `reserved_base_before` DECIMAL(18,3) NOT NULL COMMENT '操作前待履约基础单位数量', + CHANGE COLUMN `reserved_after` `reserved_base_after` DECIMAL(18,3) NOT NULL COMMENT '操作后待履约基础单位数量', + CHANGE COLUMN `fulfilled_before` `fulfilled_base_before` DECIMAL(18,3) NOT NULL COMMENT '操作前已履约基础单位数量', + CHANGE COLUMN `fulfilled_after` `fulfilled_base_after` DECIMAL(18,3) NOT NULL COMMENT '操作后已履约基础单位数量'; + +-- 套餐配置继续使用 (package_config_id, sku_id, sku_unit_id) 唯一键:同 SKU 多单位合法,同 SKU 同单位重复由既有唯一键阻止 diff --git a/src/main/resources/mapper/timi-server/gao/GaoProductChartMapper.xml b/src/main/resources/mapper/timi-server/gao/GaoProductChartMapper.xml index 7678d4d..aefc3a4 100644 --- a/src/main/resources/mapper/timi-server/gao/GaoProductChartMapper.xml +++ b/src/main/resources/mapper/timi-server/gao/GaoProductChartMapper.xml @@ -103,14 +103,14 @@ COUNT(DISTINCT o.`id`) AS `orderCount`, COALESCE(SUM(CASE WHEN i.`item_type` = 'DIRECT_PRODUCT' THEN i.`quantity` ELSE 0 END), 0) AS `directQuantity`, COALESCE(SUM(CASE WHEN i.`item_type` = 'PACKAGE_ENTITLEMENT' THEN i.`quantity` ELSE 0 END), 0) AS `packageQuantity`, - COALESCE(SUM(i.`reserved_quantity`), 0) AS `reservedQuantity`, - COALESCE(SUM(i.`fulfilled_quantity`), 0) AS `fulfilledQuantity`, - COALESCE(SUM(i.`quantity` - i.`reserved_quantity` - i.`fulfilled_quantity`), 0) AS `availableQuantity`, - COALESCE(SUM(CASE WHEN i.`item_type` = 'DIRECT_PRODUCT' THEN i.`quantity` * COALESCE(i.`base_quantity_per_unit_snapshot`, 1) ELSE 0 END), 0) AS `directBaseQuantity`, - COALESCE(SUM(CASE WHEN i.`item_type` = 'PACKAGE_ENTITLEMENT' THEN i.`quantity` * COALESCE(i.`base_quantity_per_unit_snapshot`, 1) ELSE 0 END), 0) AS `packageBaseQuantity`, - COALESCE(SUM(i.`reserved_quantity` * COALESCE(i.`base_quantity_per_unit_snapshot`, 1)), 0) AS `reservedBaseQuantity`, - COALESCE(SUM(i.`fulfilled_quantity` * COALESCE(i.`base_quantity_per_unit_snapshot`, 1)), 0) AS `fulfilledBaseQuantity`, - COALESCE(SUM((i.`quantity` - i.`reserved_quantity` - i.`fulfilled_quantity`) * COALESCE(i.`base_quantity_per_unit_snapshot`, 1)), 0) AS `availableBaseQuantity` + COALESCE(SUM(i.`reserved_base_quantity`), 0) AS `reservedQuantity`, + COALESCE(SUM(i.`fulfilled_base_quantity`), 0) AS `fulfilledQuantity`, + COALESCE(SUM(i.`base_quantity` - i.`reserved_base_quantity` - i.`fulfilled_base_quantity`), 0) AS `availableQuantity`, + COALESCE(SUM(CASE WHEN i.`item_type` = 'DIRECT_PRODUCT' THEN i.`base_quantity` ELSE 0 END), 0) AS `directBaseQuantity`, + COALESCE(SUM(CASE WHEN i.`item_type` = 'PACKAGE_ENTITLEMENT' THEN i.`base_quantity` ELSE 0 END), 0) AS `packageBaseQuantity`, + COALESCE(SUM(i.`reserved_base_quantity`), 0) AS `reservedBaseQuantity`, + COALESCE(SUM(i.`fulfilled_base_quantity`), 0) AS `fulfilledBaseQuantity`, + COALESCE(SUM(i.`base_quantity` - i.`reserved_base_quantity` - i.`fulfilled_base_quantity`), 0) AS `availableBaseQuantity` FROM `gao_sales_order_item` i INNER JOIN `gao_sales_order` o ON o.`id` = i.`sales_order_id` INNER JOIN `gao_product` p ON p.`id` = i.`product_id_snapshot` AND p.`store_id` = o.`store_id` diff --git a/src/main/resources/mapper/timi-server/gao/GaoProductSkuChartMapper.xml b/src/main/resources/mapper/timi-server/gao/GaoProductSkuChartMapper.xml index 2f49939..2099756 100644 --- a/src/main/resources/mapper/timi-server/gao/GaoProductSkuChartMapper.xml +++ b/src/main/resources/mapper/timi-server/gao/GaoProductSkuChartMapper.xml @@ -127,9 +127,9 @@ COUNT(DISTINCT o.`id`) AS `orderCount`, COALESCE(SUM(CASE WHEN i.`item_type` = 'DIRECT_PRODUCT' THEN i.`quantity` * COALESCE(i.`base_quantity_per_unit_snapshot`, 1) ELSE 0 END), 0) AS `directBaseQuantity`, COALESCE(SUM(CASE WHEN i.`item_type` = 'PACKAGE_ENTITLEMENT' THEN i.`quantity` * COALESCE(i.`base_quantity_per_unit_snapshot`, 1) ELSE 0 END), 0) AS `packageBaseQuantity`, - COALESCE(SUM(i.`reserved_quantity` * COALESCE(i.`base_quantity_per_unit_snapshot`, 1)), 0) AS `reservedBaseQuantity`, - COALESCE(SUM(i.`fulfilled_quantity` * COALESCE(i.`base_quantity_per_unit_snapshot`, 1)), 0) AS `fulfilledBaseQuantity`, - COALESCE(SUM((i.`quantity` - i.`reserved_quantity` - i.`fulfilled_quantity`) * COALESCE(i.`base_quantity_per_unit_snapshot`, 1)), 0) AS `availableBaseQuantity` + COALESCE(SUM(i.`reserved_base_quantity`), 0) AS `reservedBaseQuantity`, + COALESCE(SUM(i.`fulfilled_base_quantity`), 0) AS `fulfilledBaseQuantity`, + COALESCE(SUM(i.`base_quantity` - i.`reserved_base_quantity` - i.`fulfilled_base_quantity`), 0) AS `availableBaseQuantity` FROM `gao_sales_order_item` i INNER JOIN `gao_sales_order` o ON o.`id` = i.`sales_order_id` INNER JOIN `gao_product` p ON p.`id` = i.`product_id_snapshot` AND p.`store_id` = o.`store_id` diff --git a/src/main/resources/mapper/timi-server/gao/GaoSalesOrderChartMapper.xml b/src/main/resources/mapper/timi-server/gao/GaoSalesOrderChartMapper.xml index a78f674..18f7130 100644 --- a/src/main/resources/mapper/timi-server/gao/GaoSalesOrderChartMapper.xml +++ b/src/main/resources/mapper/timi-server/gao/GaoSalesOrderChartMapper.xml @@ -86,14 +86,14 @@ COUNT(DISTINCT o.`id`) AS `orderCount`, COALESCE(SUM(CASE WHEN i.`item_type` = 'DIRECT_PRODUCT' THEN i.`quantity` ELSE 0 END), 0) AS `directQuantity`, COALESCE(SUM(CASE WHEN i.`item_type` = 'PACKAGE_ENTITLEMENT' THEN i.`quantity` ELSE 0 END), 0) AS `packageQuantity`, - COALESCE(SUM(i.`reserved_quantity`), 0) AS `reservedQuantity`, - COALESCE(SUM(i.`fulfilled_quantity`), 0) AS `fulfilledQuantity`, - COALESCE(SUM(i.`quantity` - i.`reserved_quantity` - i.`fulfilled_quantity`), 0) AS `availableQuantity`, - COALESCE(SUM(CASE WHEN i.`item_type` = 'DIRECT_PRODUCT' THEN i.`quantity` * COALESCE(i.`base_quantity_per_unit_snapshot`, 1) ELSE 0 END), 0) AS `directBaseQuantity`, - COALESCE(SUM(CASE WHEN i.`item_type` = 'PACKAGE_ENTITLEMENT' THEN i.`quantity` * COALESCE(i.`base_quantity_per_unit_snapshot`, 1) ELSE 0 END), 0) AS `packageBaseQuantity`, - COALESCE(SUM(i.`reserved_quantity` * COALESCE(i.`base_quantity_per_unit_snapshot`, 1)), 0) AS `reservedBaseQuantity`, - COALESCE(SUM(i.`fulfilled_quantity` * COALESCE(i.`base_quantity_per_unit_snapshot`, 1)), 0) AS `fulfilledBaseQuantity`, - COALESCE(SUM((i.`quantity` - i.`reserved_quantity` - i.`fulfilled_quantity`) * COALESCE(i.`base_quantity_per_unit_snapshot`, 1)), 0) AS `availableBaseQuantity`, + COALESCE(SUM(i.`reserved_base_quantity`), 0) AS `reservedQuantity`, + COALESCE(SUM(i.`fulfilled_base_quantity`), 0) AS `fulfilledQuantity`, + COALESCE(SUM(i.`base_quantity` - i.`reserved_base_quantity` - i.`fulfilled_base_quantity`), 0) AS `availableQuantity`, + COALESCE(SUM(CASE WHEN i.`item_type` = 'DIRECT_PRODUCT' THEN i.`base_quantity` ELSE 0 END), 0) AS `directBaseQuantity`, + COALESCE(SUM(CASE WHEN i.`item_type` = 'PACKAGE_ENTITLEMENT' THEN i.`base_quantity` ELSE 0 END), 0) AS `packageBaseQuantity`, + COALESCE(SUM(i.`reserved_base_quantity`), 0) AS `reservedBaseQuantity`, + COALESCE(SUM(i.`fulfilled_base_quantity`), 0) AS `fulfilledBaseQuantity`, + COALESCE(SUM(i.`base_quantity` - i.`reserved_base_quantity` - i.`fulfilled_base_quantity`), 0) AS `availableBaseQuantity`, COALESCE(SUM(CASE WHEN i.`item_type` = 'DIRECT_PRODUCT' THEN COALESCE(i.`line_amount`, 0) ELSE 0 END), 0) AS `directSaleAmount` FROM `gao_sales_order_item` i INNER JOIN `gao_sales_order` o ON o.`id` = i.`sales_order_id` @@ -124,11 +124,11 @@ i.`product_id_snapshot` AS `productId`, MAX(i.`product_name_snapshot`) AS `productName`, COUNT(DISTINCT o.`id`) AS `orderCount`, - COALESCE(SUM(CASE WHEN i.`item_type` = 'DIRECT_PRODUCT' THEN i.`quantity` * COALESCE(i.`base_quantity_per_unit_snapshot`, 1) ELSE 0 END), 0) AS `directBaseQuantity`, - COALESCE(SUM(CASE WHEN i.`item_type` = 'PACKAGE_ENTITLEMENT' THEN i.`quantity` * COALESCE(i.`base_quantity_per_unit_snapshot`, 1) ELSE 0 END), 0) AS `packageBaseQuantity`, - COALESCE(SUM(i.`reserved_quantity` * COALESCE(i.`base_quantity_per_unit_snapshot`, 1)), 0) AS `reservedBaseQuantity`, - COALESCE(SUM(i.`fulfilled_quantity` * COALESCE(i.`base_quantity_per_unit_snapshot`, 1)), 0) AS `fulfilledBaseQuantity`, - COALESCE(SUM((i.`quantity` - i.`reserved_quantity` - i.`fulfilled_quantity`) * COALESCE(i.`base_quantity_per_unit_snapshot`, 1)), 0) AS `availableBaseQuantity`, + COALESCE(SUM(CASE WHEN i.`item_type` = 'DIRECT_PRODUCT' THEN i.`base_quantity` ELSE 0 END), 0) AS `directBaseQuantity`, + COALESCE(SUM(CASE WHEN i.`item_type` = 'PACKAGE_ENTITLEMENT' THEN i.`base_quantity` ELSE 0 END), 0) AS `packageBaseQuantity`, + COALESCE(SUM(i.`reserved_base_quantity`), 0) AS `reservedBaseQuantity`, + COALESCE(SUM(i.`fulfilled_base_quantity`), 0) AS `fulfilledBaseQuantity`, + COALESCE(SUM(i.`base_quantity` - i.`reserved_base_quantity` - i.`fulfilled_base_quantity`), 0) AS `availableBaseQuantity`, COALESCE(SUM(CASE WHEN i.`item_type` = 'DIRECT_PRODUCT' THEN COALESCE(i.`line_amount`, 0) ELSE 0 END), 0) AS `directSaleAmount` FROM `gao_sales_order_item` i INNER JOIN `gao_sales_order` o ON o.`id` = i.`sales_order_id` diff --git a/src/main/resources/mapper/timi-server/gao/GaoSalesOrderItemMapper.xml b/src/main/resources/mapper/timi-server/gao/GaoSalesOrderItemMapper.xml index 54cf638..3e8c505 100644 --- a/src/main/resources/mapper/timi-server/gao/GaoSalesOrderItemMapper.xml +++ b/src/main/resources/mapper/timi-server/gao/GaoSalesOrderItemMapper.xml @@ -20,16 +20,16 @@ WHERE `id` = #{id} AND `deleted_at` IS NULL FOR UPDATE - + UPDATE `gao_sales_order_item` - SET `reserved_quantity` = `reserved_quantity` + #{delta}, `updated_at` = FLOOR(UNIX_TIMESTAMP(NOW(3)) * 1000) - WHERE `id` = #{id} AND `reserved_quantity` + #{delta} >= 0 - AND `fulfilled_quantity` + `reserved_quantity` + #{delta} <= `quantity` + SET `reserved_base_quantity` = `reserved_base_quantity` + #{delta}, `updated_at` = FLOOR(UNIX_TIMESTAMP(NOW(3)) * 1000) + WHERE `id` = #{id} AND `reserved_base_quantity` + #{delta} >= 0 + AND `fulfilled_base_quantity` + `reserved_base_quantity` + #{delta} <= `base_quantity` - + UPDATE `gao_sales_order_item` - SET `fulfilled_quantity` = `fulfilled_quantity` + #{delta}, `updated_at` = FLOOR(UNIX_TIMESTAMP(NOW(3)) * 1000) - WHERE `id` = #{id} AND `fulfilled_quantity` + #{delta} >= 0 - AND `fulfilled_quantity` + #{delta} <= `quantity` + SET `fulfilled_base_quantity` = `fulfilled_base_quantity` + #{delta}, `updated_at` = FLOOR(UNIX_TIMESTAMP(NOW(3)) * 1000) + WHERE `id` = #{id} AND `fulfilled_base_quantity` + #{delta} >= 0 + AND `fulfilled_base_quantity` + #{delta} <= `base_quantity`