From db2fca5715e6052c4ecdc2909e52d6a18f0ca0bb Mon Sep 17 00:00:00 2001 From: Timi Date: Sun, 6 Sep 2026 15:49:57 +0800 Subject: [PATCH] update customer package --- .../modules/gao/bean/GaoPermissionCode.java | 8 - .../GaoCustomerPackageController.java | 91 +++---- .../gao/entity/GaoCustomerPackage.java | 35 ++- .../gao/entity/GaoCustomerPackageItem.java | 68 ----- .../gao/entity/GaoOrderFulfillment.java | 15 -- .../gao/entity/GaoOrderFulfillmentItem.java | 5 +- .../api/modules/gao/entity/GaoSalesOrder.java | 2 +- .../modules/gao/entity/GaoSalesOrderItem.java | 27 +- .../mapper/GaoCustomerPackageItemMapper.java | 30 --- .../gao/mapper/GaoCustomerPackageMapper.java | 3 - .../gao/mapper/GaoSalesOrderMapper.java | 4 + .../service/GaoCustomerPackageService.java | 50 +--- .../gao/service/GaoSalesOrderService.java | 7 +- .../GaoCustomerPackageServiceImplement.java | 104 +++----- .../GaoOrderFulfillmentServiceImplement.java | 247 ++++++------------ .../GaoSalesOrderServiceImplement.java | 190 +++++++++----- .../gao/vo/GaoCustomerPackagePage.java | 6 +- .../vo/GaoOrderFulfillmentItemRequest.java | 5 +- .../gao/vo/GaoOrderFulfillmentPage.java | 3 - .../gao/vo/GaoPackageOrderCreateData.java | 6 + ...9__unify_gao_package_order_fulfillment.sql | 38 +++ .../gao/GaoCustomerPackageItemMapper.xml | 39 --- .../gao/GaoCustomerPackageMapper.xml | 10 +- .../gao/GaoOrderFulfillmentMapper.xml | 1 - .../timi-server/gao/GaoSalesOrderMapper.xml | 8 + 25 files changed, 394 insertions(+), 608 deletions(-) delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomerPackageItem.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerPackageItemMapper.java create mode 100644 src/main/resources/db/migration/timiserver/V69__unify_gao_package_order_fulfillment.sql delete mode 100644 src/main/resources/mapper/timi-server/gao/GaoCustomerPackageItemMapper.xml diff --git a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java index c423608..1028d86 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java +++ b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java @@ -111,14 +111,6 @@ public enum GaoPermissionCode implements BuiltinPermissionCode { CUSTOMER_PACKAGE_PRICE_OVERRIDE("CUSTOMER_PACKAGE:PRICE_OVERRIDE", "客户套餐成交价和权益配额调整", true, true), - PACKAGE_DELIVERY_CREATE("PACKAGE_DELIVERY:CREATE", "套餐发放创建", true, true), - - PACKAGE_DELIVERY_READ("PACKAGE_DELIVERY:READ", "套餐发放读取", true, true), - - PACKAGE_DELIVERY_VERIFY("PACKAGE_DELIVERY:VERIFY", "套餐发放核销", true, true), - - PACKAGE_DELIVERY_REVOKE("PACKAGE_DELIVERY:REVOKE", "套餐发放冲正", true, true), - FUND_RECORD_READ("FUND_RECORD:READ", "资金流水读取", true, true), FUND_RECEIVE_CREATE("FUND_RECEIVE:CREATE", "线下收款创建", true, true), diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerPackageController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerPackageController.java index 9fdc013..7bf5bda 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerPackageController.java +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerPackageController.java @@ -6,12 +6,11 @@ import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; import com.imyeyu.api.modules.gao.entity.GaoCustomer; import com.imyeyu.api.modules.gao.entity.GaoCustomerPackage; -import com.imyeyu.api.modules.gao.entity.GaoCustomerPackageItem; -import com.imyeyu.api.modules.gao.entity.GaoOrderFulfillment; -import com.imyeyu.api.modules.gao.entity.GaoProduct; +import com.imyeyu.api.modules.gao.entity.GaoSalesOrder; import com.imyeyu.api.modules.gao.service.GaoCustomerPackageService; import com.imyeyu.api.modules.gao.service.GaoCustomerService; import com.imyeyu.api.modules.gao.service.GaoOrderFulfillmentService; +import com.imyeyu.api.modules.gao.service.GaoSalesOrderService; import com.imyeyu.api.modules.gao.service.GaoStoreScopeService; import com.imyeyu.api.modules.gao.vo.GaoCustomerPackageCancelRequest; import com.imyeyu.api.modules.gao.vo.GaoCustomerPackagePage; @@ -29,7 +28,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.math.BigDecimal; import java.util.List; import java.util.Map; @@ -45,6 +43,7 @@ public class GaoCustomerPackageController { private final GaoCustomerPackageService service; private final GaoCustomerService customerService; private final GaoOrderFulfillmentService fulfillmentService; + private final GaoSalesOrderService salesOrderService; private final AttachmentService attachmentService; private final GaoStoreScopeService storeScopeService; private final UserService userService; @@ -57,23 +56,7 @@ public class GaoCustomerPackageController { TimiException.required(page, "not found page"); page.setStoreId(storeScopeService.resolveStoreId(page.getStoreId())); PageResult result = service.pageByQuery(page); - if (result.getList().isEmpty()) { - return result; - } - List customerIdList = result.getList().stream() - .map(GaoCustomerPackage::getCustomerId) - .filter(value -> value != null && !value.isBlank()) - .distinct() - .toList(); - Map customerMap = customerService.mapByIdList(customerIdList); - Map> attachmentMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_CUSTOMER, customerIdList, GaoCustomer.AttachType.PHOTO.name()); - for (GaoCustomerPackage packageEntity : result.getList()) { - GaoCustomer customer = customerMap.get(packageEntity.getCustomerId()); - if (customer != null) { - customer.setAttachmentList(attachmentMap.get(customer.getId())); - } - packageEntity.setCustomer(customer); - } + fillTransient(result.getList()); return result; } @@ -119,52 +102,42 @@ public class GaoCustomerPackageController { if (packageList == null || packageList.isEmpty()) { return; } - Map> itemMap = service.mapItemListByCustomerPackageIdList(packageList.stream().map(GaoCustomerPackage::getId).toList()); + Map orderMap = salesOrderService.mapByIdList(packageList.stream() + .map(GaoCustomerPackage::getSalesOrderId) + .filter(value -> value != null && !value.isBlank()) + .distinct() + .toList()); + List customerIdList = packageList.stream() + .map(GaoCustomerPackage::getCustomerId) + .filter(value -> value != null && !value.isBlank()) + .distinct() + .toList(); + Map customerMap = customerIdList.isEmpty() ? Map.of() : customerService.mapByIdList(customerIdList); + Map> attachmentMap = customerIdList.isEmpty() + ? Map.of() : attachmentService.mapByBizIdList(Attachment.BizType.GAO_CUSTOMER, customerIdList, GaoCustomer.AttachType.PHOTO.name()); List userIdList = packageList.stream() - .map(GaoCustomerPackage::getCancelOperatorUserId) + .map(packageEntity -> orderMap.get(packageEntity.getSalesOrderId())) + .filter(order -> order != null) + .map(GaoSalesOrder::getCancelOperatorUserId) .filter(value -> value != null && !value.isBlank()) .distinct() .toList(); Map userMap = userIdList.isEmpty() ? Map.of() : userService.mapByIdList(userIdList); for (GaoCustomerPackage packageEntity : packageList) { - List itemList = itemMap.getOrDefault(packageEntity.getId(), List.of()); - fillItemAttachments(itemList); - for (GaoCustomerPackageItem item : itemList) { - fillDerived(item); + GaoSalesOrder order = orderMap.get(packageEntity.getSalesOrderId()); + packageEntity.setSalesOrder(order); + packageEntity.setStatus(order == null ? null : order.getStatus()); + packageEntity.setCancelledAt(order == null ? null : order.getCancelledAt()); + packageEntity.setCancelOperatorUserId(order == null ? null : order.getCancelOperatorUserId()); + packageEntity.setCancelReason(order == null ? null : order.getCancelReason()); + packageEntity.setItemList(order == null ? List.of() : order.getItemList()); + GaoCustomer customer = customerMap.get(packageEntity.getCustomerId()); + if (customer != null) { + customer.setAttachmentList(attachmentMap.getOrDefault(customer.getId(), List.of())); } - packageEntity.setItemList(itemList); - String cancelOperatorUserId = packageEntity.getCancelOperatorUserId(); + packageEntity.setCustomer(customer); + String cancelOperatorUserId = order == null ? null : order.getCancelOperatorUserId(); packageEntity.setCancelOperator(cancelOperatorUserId == null ? null : userMap.get(cancelOperatorUserId)); } } - - private void fillItemAttachments(List itemList) { - if (itemList == null || itemList.isEmpty()) { - return; - } - List productIdList = itemList.stream() - .map(GaoCustomerPackageItem::getProductId) - .filter(value -> value != null && !value.isBlank()) - .distinct() - .toList(); - if (productIdList.isEmpty()) { - return; - } - Map> attachmentMap = attachmentService.mapByBizIdList( - Attachment.BizType.GAO_PRODUCT, - productIdList, - GaoProduct.AttachType.PHOTO.name()); - for (GaoCustomerPackageItem item : itemList) { - item.setAttachmentList(attachmentMap.getOrDefault(item.getProductId(), List.of())); - } - } - - private void fillDerived(GaoCustomerPackageItem item) { - BigDecimal reserved = item.getReservedQuantity() == null ? BigDecimal.ZERO : item.getReservedQuantity(); - BigDecimal fulfilled = item.getFulfilledQuantity() == null ? BigDecimal.ZERO : item.getFulfilledQuantity(); - BigDecimal total = item.getTotalQuantity() == null ? BigDecimal.ZERO : item.getTotalQuantity(); - item.setPendingQuantity(reserved); - item.setAvailableQuantity(total.subtract(reserved).subtract(fulfilled)); - item.setRemainingQuantity(total.subtract(fulfilled)); - } } diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomerPackage.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomerPackage.java index 0e877b3..2425485 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomerPackage.java +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomerPackage.java @@ -16,19 +16,6 @@ import java.util.List; @EqualsAndHashCode(callSuper = true) public class GaoCustomerPackage extends UUIDEntity { - /// 客户套餐状态 - public enum Status { - - /// 履约中 - ACTIVE, - - /// 已全部履约 - FULFILLED, - - /// 已取消 - CANCELLED - } - /// 门店 ID private String storeId; @@ -47,16 +34,20 @@ public class GaoCustomerPackage extends UUIDEntity { /// 标准价快照,单位分 private Long standardAmountSnapshot; - /// 套餐状态 - private Status status; + /// 订单投影状态 + @Transient + private GaoSalesOrder.Status status; - /// 取消时间 + /// 订单投影取消时间 + @Transient private Long cancelledAt; - /// 取消操作人 + /// 订单投影取消操作人 ID + @Transient private String cancelOperatorUserId; - /// 取消原因 + /// 订单投影取消原因 + @Transient private String cancelReason; /// 备注 @@ -70,9 +61,13 @@ public class GaoCustomerPackage extends UUIDEntity { @Transient private User cancelOperator; - /// 产品权益列表 + /// 关联销售订单 @Transient - private List itemList; + private GaoSalesOrder salesOrder; + + /// 关联订单可履约明细投影 + @Transient + private List itemList; /// 统一履约历史 @Transient diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomerPackageItem.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomerPackageItem.java deleted file mode 100644 index c1f605c..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomerPackageItem.java +++ /dev/null @@ -1,68 +0,0 @@ -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 客户套餐产品权益快照 -/// -/// @author Codex -/// @since 2026-08-31 -@Data -@EqualsAndHashCode(callSuper = true) -public class GaoCustomerPackageItem extends UUIDEntity { - - /// 客户套餐 ID - private String customerPackageId; - - /// 产品 ID - private String productId; - - /// 产品单位 ID - private String productUnitId; - - /// 产品图片附件 - @Transient - private List attachmentList; - - /// 产品名称快照 - private String productNameSnapshot; - - /// 产品单位快照 - private String unitSnapshot; - - /// 基础单位数量系数快照 - private BigDecimal baseQuantityPerUnitSnapshot; - - /// 总权益数量 - private BigDecimal totalQuantity; - - /// 待核销占用数量 - private BigDecimal reservedQuantity; - - /// 已履约数量 - private BigDecimal fulfilledQuantity; - - /// 排序值 - private Integer sort; - - /// 备注 - private String remark; - - /// 待核销数量 - @Transient - private BigDecimal pendingQuantity; - - /// 当前可发数量 - @Transient - private BigDecimal availableQuantity; - - /// 权益剩余数量 - @Transient - private BigDecimal remainingQuantity; -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoOrderFulfillment.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoOrderFulfillment.java index bb662d6..48f260d 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoOrderFulfillment.java +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoOrderFulfillment.java @@ -7,10 +7,8 @@ import com.imyeyu.spring.annotation.table.Id; import com.imyeyu.spring.annotation.table.Transient; import com.imyeyu.spring.entity.Creatable; import com.imyeyu.spring.entity.IDEntity; -import com.imyeyu.spring.entity.UUIDEntity; import com.imyeyu.spring.entity.Updatable; import lombok.Data; -import lombok.EqualsAndHashCode; import java.util.List; @@ -21,16 +19,6 @@ import java.util.List; @Data public class GaoOrderFulfillment implements IDEntity, Creatable, Updatable { - /// 履约来源类型 - public enum FulfillmentType { - - /// 客户套餐权益 - PACKAGE_ENTITLEMENT, - - /// 直接产品订单项 - DIRECT_PRODUCT - } - /// 履约状态 public enum Status { @@ -66,9 +54,6 @@ public class GaoOrderFulfillment implements IDEntity, Creatable, Updatab /// 客户 ID private String customerId; - /// 履约来源类型 - private FulfillmentType fulfillmentType; - /// 履约状态 private Status status; 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 3f8cf9a..1621d88 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 @@ -35,12 +35,9 @@ public class GaoOrderFulfillmentItem implements IDEntity, Creatable { @Transient private List attachmentList; - /// 直接订单明细 ID + /// 来源销售订单明细 ID private String sourceOrderItemId; - /// 客户套餐权益项 ID - private String sourceCustomerPackageItemId; - /// 本次履约数量 private BigDecimal quantity; diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoSalesOrder.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoSalesOrder.java index 6bb8783..4e9e1f4 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoSalesOrder.java +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoSalesOrder.java @@ -138,7 +138,7 @@ public class GaoSalesOrder extends UUIDEntity { @Transient private User cancelOperator; - /// 直接产品订单明细 + /// 销售订单明细 @Transient private List itemList; 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 0c0479a..da0b33c 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 @@ -9,7 +9,7 @@ import lombok.EqualsAndHashCode; import java.math.BigDecimal; import java.util.List; -/// GAO 直接产品订单明细 +/// GAO 销售订单通用可履约明细 /// /// @author Codex /// @since 2026-09-02 @@ -17,9 +17,22 @@ import java.util.List; @EqualsAndHashCode(callSuper = true) public class GaoSalesOrderItem extends UUIDEntity { + /// 订单项类型 + public enum ItemType { + + /// 直接产品 + DIRECT_PRODUCT, + + /// 套餐权益 + PACKAGE_ENTITLEMENT + } + /// 销售订单 ID private String salesOrderId; + /// 订单项类型 + private ItemType itemType; + /// 产品 ID private String productId; @@ -59,4 +72,16 @@ public class GaoSalesOrderItem extends UUIDEntity { /// 备注 private String remark; + + /// 待核销数量 + @Transient + private BigDecimal pendingQuantity; + + /// 当前可履约数量 + @Transient + private BigDecimal availableQuantity; + + /// 权益剩余数量 + @Transient + private BigDecimal remainingQuantity; } diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerPackageItemMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerPackageItemMapper.java deleted file mode 100644 index 9766b60..0000000 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerPackageItemMapper.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.imyeyu.api.modules.gao.mapper; - -import com.imyeyu.api.modules.gao.entity.GaoCustomerPackageItem; -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-08-31 -public interface GaoCustomerPackageItemMapper extends BaseMapper { - - /// 按客户套餐查询产品权益 - List selectByCustomerPackageId(@Param("customerPackageId") String customerPackageId); - - /// 批量查询客户套餐产品权益 - List selectByCustomerPackageIdList(@Param("customerPackageIdList") Collection customerPackageIdList); - - /// 锁定客户套餐产品 - GaoCustomerPackageItem selectForUpdate(@Param("id") String id); - - /// 调整待核销占用数量 - int updateReservedQuantity(@Param("id") String id, @Param("delta") java.math.BigDecimal delta); - - /// 调整已履约数量 - int updateFulfilledQuantity(@Param("id") String id, @Param("delta") java.math.BigDecimal delta); -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerPackageMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerPackageMapper.java index 392eb34..1e8155b 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerPackageMapper.java +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerPackageMapper.java @@ -11,9 +11,6 @@ import org.apache.ibatis.annotations.Param; /// @since 2026-08-31 public interface GaoCustomerPackageMapper extends BaseMapper { - /// 锁定客户套餐 - GaoCustomerPackage selectForUpdate(@Param("id") String id); - /// 按销售订单查询客户套餐 GaoCustomerPackage selectBySalesOrderId(@Param("salesOrderId") String salesOrderId); diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoSalesOrderMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoSalesOrderMapper.java index 185e0a8..f5f14f0 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoSalesOrderMapper.java +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoSalesOrderMapper.java @@ -6,6 +6,7 @@ import com.imyeyu.spring.mapper.BaseMapper; import org.apache.ibatis.annotations.Param; import java.util.List; +import java.util.Collection; /// GAO 销售订单 Mapper /// @@ -19,6 +20,9 @@ public interface GaoSalesOrderMapper extends BaseMapper { /// 锁定订单 GaoSalesOrder selectForUpdate(@Param("id") String id); + /// 批量查询订单 + List selectByIdList(@Param("idList") Collection idList); + /// 按条件统计订单 long countByQuery(GaoSalesOrderPage page); diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerPackageService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerPackageService.java index 8c98a01..7c0a800 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerPackageService.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerPackageService.java @@ -1,17 +1,12 @@ package com.imyeyu.api.modules.gao.service; import com.imyeyu.api.modules.gao.entity.GaoCustomerPackage; -import com.imyeyu.api.modules.gao.entity.GaoCustomerPackageItem; import com.imyeyu.api.modules.gao.vo.GaoCustomerPackageCancelRequest; import com.imyeyu.api.modules.gao.vo.GaoCustomerPackagePage; import com.imyeyu.api.modules.gao.vo.GaoCustomerPackagePurchaseRequest; import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.service.BaseService; -import java.math.BigDecimal; -import java.util.Collection; -import java.util.List; -import java.util.Map; /// GAO 客户套餐服务 /// @@ -22,55 +17,12 @@ public interface GaoCustomerPackageService extends BaseService pageByQuery(GaoCustomerPackagePage page); - /// 创建客户套餐及购买事件登记 + /// 创建客户套餐及套餐订单 GaoCustomerPackage purchase(GaoCustomerPackagePurchaseRequest request); /// 取消客户套餐 GaoCustomerPackage cancel(GaoCustomerPackageCancelRequest request); - /// 锁定客户套餐 - /// - /// @param id 客户套餐 ID - /// @return 已锁定的客户套餐,不存在时为空 - GaoCustomerPackage getForUpdate(String id); - /// 按销售订单查询客户套餐 GaoCustomerPackage getBySalesOrderId(String salesOrderId); - - /// 查询客户套餐产品权益 - /// - /// @param customerPackageId 客户套餐 ID - /// @return 产品权益列表 - List listItemByCustomerPackageId(String customerPackageId); - - /// 批量查询客户套餐产品权益 - /// - /// @param customerPackageIdList 客户套餐 ID 列表 - /// @return 按客户套餐 ID 分组的产品权益 - Map> mapItemListByCustomerPackageIdList(Collection customerPackageIdList); - - /// 锁定客户套餐产品权益 - /// - /// @param itemId 客户套餐产品权益 ID - /// @return 已锁定的产品权益,不存在时为空 - GaoCustomerPackageItem getItemForUpdate(String itemId); - - /// 原子调整待核销发放占用数量 - /// - /// @param itemId 客户套餐产品权益 ID - /// @param delta 调整量 - /// @return 受影响行数 - int updateItemReservedQuantity(String itemId, BigDecimal delta); - - /// 原子调整已履约数量 - /// - /// @param itemId 客户套餐产品权益 ID - /// @param delta 调整量 - /// @return 受影响行数 - int updateItemFulfilledQuantity(String itemId, BigDecimal delta); - - /// 保存客户套餐聚合状态 - /// - /// @param packageEntity 客户套餐聚合 - void updateAggregateState(GaoCustomerPackage packageEntity); } diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoSalesOrderService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoSalesOrderService.java index 1ba8598..02bb4eb 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoSalesOrderService.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoSalesOrderService.java @@ -11,7 +11,9 @@ import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.service.BaseService; import java.math.BigDecimal; +import java.util.Collection; import java.util.List; +import java.util.Map; /// GAO 销售订单服务 /// @@ -68,9 +70,12 @@ public interface GaoSalesOrderService extends BaseService /// 根据订单明细刷新订单履约状态 void refreshFulfillmentStatus(GaoSalesOrder order); - /// 写入套餐订单履约状态 + /// 写入销售订单履约状态 void updateFulfillmentStatus(GaoSalesOrder order, boolean fulfilled); /// 查询订单明细 List listItemByOrderId(String orderId); + + /// 批量查询订单及其明细 + Map mapByIdList(Collection idList); } 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 c2aa17a..a601b28 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 @@ -6,11 +6,10 @@ import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.gao.entity.GaoCustomer; import com.imyeyu.api.modules.gao.entity.GaoCustomerPackage; -import com.imyeyu.api.modules.gao.entity.GaoCustomerPackageItem; import com.imyeyu.api.modules.gao.entity.GaoPackageConfig; import com.imyeyu.api.modules.gao.entity.GaoPackageConfigItem; import com.imyeyu.api.modules.gao.entity.GaoSalesOrder; -import com.imyeyu.api.modules.gao.mapper.GaoCustomerPackageItemMapper; +import com.imyeyu.api.modules.gao.entity.GaoSalesOrderItem; import com.imyeyu.api.modules.gao.mapper.GaoCustomerPackageMapper; import com.imyeyu.api.modules.gao.service.GaoSalesOrderService; import com.imyeyu.api.modules.gao.service.GaoCustomerService; @@ -39,7 +38,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; -import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -55,10 +53,7 @@ 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 GaoCustomerPackageItemMapper itemMapper; private final GaoPackageConfigService packageConfigService; private final GaoProductService productService; private final GaoCustomerService customerService; @@ -74,42 +69,12 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService listItemByCustomerPackageId(String customerPackageId) { - return itemMapper.selectByCustomerPackageId(customerPackageId); - } - - @Override - public GaoCustomerPackageItem getItemForUpdate(String itemId) { - return itemMapper.selectForUpdate(itemId); - } - - @Override - public int updateItemReservedQuantity(String itemId, BigDecimal delta) { - return itemMapper.updateReservedQuantity(itemId, delta); - } - - @Override - public int updateItemFulfilledQuantity(String itemId, BigDecimal delta) { - return itemMapper.updateFulfilledQuantity(itemId, delta); - } - - @Override - public void updateAggregateState(GaoCustomerPackage packageEntity) { - mapper.update(packageEntity); - } - /// 客户套餐必须通过购买流程创建 /// /// @param packageEntity 客户套餐 @@ -204,6 +169,7 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService orderItemList = itemList.stream().map(this::toOrderItem).toList(); GaoPackageOrderCreateData orderData = new GaoPackageOrderCreateData(); orderData.setStoreId(config.getStoreId()); orderData.setCustomerId(customer.getId()); @@ -215,6 +181,7 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService itemList) { TimiException.requiredTrue(!itemList.isEmpty(), "购买套餐至少需要一个产品"); Set keySet = new HashSet<>(); @@ -351,29 +323,21 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService existingItemList = itemMapper.selectByCustomerPackageId(existingPackage.getId()); + List existingItemList = salesOrderService.listItemByOrderId(existingOrder.getId()); List requestItemList = TimiJava.defaultIfNull(request.getItemList(), List.of()); verifyPurchaseItems(requestItemList); TimiException.requiredTrue(existingItemList.size() == requestItemList.size(), "requestNo 对应购买请求内容不一致"); - Map existingItemMap = existingItemList.stream().collect(Collectors.toMap(item -> item.getProductId() + "|" + item.getProductUnitId(), Function.identity())); + Map existingItemMap = existingItemList.stream().collect(Collectors.toMap(item -> item.getProductId() + "|" + item.getProductUnitId(), Function.identity())); for (GaoCustomerPackagePurchaseItem item : requestItemList) { - GaoCustomerPackageItem existingItem = existingItemMap.get(item.getProductId() + "|" + item.getProductUnitId()); + GaoSalesOrderItem existingItem = existingItemMap.get(item.getProductId() + "|" + item.getProductUnitId()); TimiException.required(existingItem, "requestNo 对应购买请求内容不一致"); - TimiException.requiredTrue(existingItem.getTotalQuantity().compareTo(item.getTotalQuantity()) == 0 + TimiException.requiredTrue(existingItem.getItemType() == GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT + && existingItem.getQuantity().compareTo(item.getTotalQuantity()) == 0 && existingItem.getSort().equals(item.getSort()) && sameText(existingItem.getRemark(), item.getRemark()), "requestNo 对应购买请求内容不一致"); } } - @Override - public Map> mapItemListByCustomerPackageIdList(Collection customerPackageIdList) { - if (customerPackageIdList == null || customerPackageIdList.isEmpty()) { - return Map.of(); - } - return itemMapper.selectByCustomerPackageIdList(customerPackageIdList).stream() - .collect(Collectors.groupingBy(GaoCustomerPackageItem::getCustomerPackageId)); - } - private String requiredReason(String reason, String message) { TimiException.required(reason, message); String result = reason.trim(); 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 1b9a334..0210518 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 @@ -5,8 +5,6 @@ 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.bean.GaoRoleCode; -import com.imyeyu.api.modules.gao.entity.GaoCustomerPackage; -import com.imyeyu.api.modules.gao.entity.GaoCustomerPackageItem; import com.imyeyu.api.modules.gao.entity.GaoProduct; import com.imyeyu.api.modules.gao.entity.GaoProductUnit; import com.imyeyu.api.modules.gao.entity.GaoSalesOrder; @@ -16,7 +14,6 @@ import com.imyeyu.api.modules.gao.entity.GaoOrderFulfillment; import com.imyeyu.api.modules.gao.entity.GaoOrderFulfillmentItem; import com.imyeyu.api.modules.gao.mapper.GaoOrderFulfillmentItemMapper; import com.imyeyu.api.modules.gao.mapper.GaoOrderFulfillmentMapper; -import com.imyeyu.api.modules.gao.service.GaoCustomerPackageService; import com.imyeyu.api.modules.gao.service.GaoOrderFulfillmentService; import com.imyeyu.api.modules.gao.service.GaoProductService; import com.imyeyu.api.modules.gao.service.GaoProductStockService; @@ -70,7 +67,6 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService sourceList = loadSources(order, packageEntity, requestItems(request.getItemList()), true); + List sourceList = loadSources(order, request.getItemList(), true); List stockItems = buildStockItems(order.getStoreId(), sourceList); boolean stockReady = stockItems.isEmpty() || productStockService.hasSufficientForFulfillment(order.getStoreId(), stockItems); String operatorUserId = userLoginService.getRequireLoginUserId(); - GaoOrderFulfillment fulfillment = newFulfillment(order, sourceList.getFirst().packageSource() - ? GaoOrderFulfillment.FulfillmentType.PACKAGE_ENTITLEMENT : GaoOrderFulfillment.FulfillmentType.DIRECT_PRODUCT, + GaoOrderFulfillment fulfillment = newFulfillment(order, stockReady ? GaoOrderFulfillment.Status.PENDING : GaoOrderFulfillment.Status.WAITING_STOCK, requestNo, operatorUserId, TimiJava.defaultIfNull(request.getDeliveredAt(), Time.now()), request.getRemark()); super.create(fulfillment); @@ -195,15 +189,17 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService sourceList = loadSources(order, itemRequests(snapshot.getId()), false); + GaoOrderFulfillment fulfillment = lockFulfillment(snapshot.getId(), order); storeScopeService.checkStoreId(fulfillment.getStoreId()); TimiException.requiredTrue(fulfillment.getStatus() == GaoOrderFulfillment.Status.WAITING_STOCK, "履约单不是等待库存状态"); - GaoSalesOrder order = salesOrderService.getForUpdate(fulfillment.getSalesOrderId()); TimiException.requiredTrue(order.getStatus() != GaoSalesOrder.Status.CANCELLED, "已取消订单不能准备库存"); salesOrderService.checkFulfillmentPermission(order); - GaoCustomerPackage packageEntity = lockPackage(order, false); - List sourceList = loadSources(order, packageEntity, copyRequestItems(itemMapper.selectByFulfillmentIdForUpdate(fulfillment.getId())), false); + List fulfillmentItemList = itemMapper.selectByFulfillmentIdForUpdate(fulfillment.getId()); + TimiException.requiredTrue(!fulfillmentItemList.isEmpty(), "履约单没有明细"); List stockItems = buildStockItems(order.getStoreId(), sourceList); TimiException.requiredTrue(!stockItems.isEmpty(), "履约单没有需要占用的实物库存"); TimiException.requiredTrue(productStockService.hasSufficientForFulfillment(order.getStoreId(), stockItems), "库存仍然不足,不能准备履约"); @@ -226,16 +222,17 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService sourceList = loadSources(order, itemRequests(snapshot.getId()), false); + GaoOrderFulfillment fulfillment = lockFulfillment(snapshot.getId(), order); storeScopeService.checkStoreId(fulfillment.getStoreId()); TimiException.requiredTrue(fulfillment.getStatus() == GaoOrderFulfillment.Status.PENDING, "履约单不是待核销状态"); - GaoSalesOrder order = salesOrderService.getForUpdate(fulfillment.getSalesOrderId()); TimiException.requiredTrue(order.getStatus() != GaoSalesOrder.Status.CANCELLED, "已取消订单不能核销履约"); salesOrderService.checkFulfillmentPermission(order); - GaoCustomerPackage packageEntity = lockPackage(order, false); List fulfillmentItemList = itemMapper.selectByFulfillmentIdForUpdate(fulfillment.getId()); - List sourceList = loadSources(order, packageEntity, copyRequestItems(fulfillmentItemList), false); + TimiException.requiredTrue(!fulfillmentItemList.isEmpty(), "履约单没有明细"); assertReserved(sourceList); for (SourceEntry source : sourceList) { decreaseReserved(source); @@ -249,7 +246,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService sourceList = loadSources(order, itemRequests(snapshot.getId()), false); + GaoOrderFulfillment fulfillment = lockFulfillment(snapshot.getId(), order); storeScopeService.checkStoreId(fulfillment.getStoreId()); TimiException.requiredTrue(fulfillment.getStatus() == GaoOrderFulfillment.Status.WAITING_STOCK || fulfillment.getStatus() == GaoOrderFulfillment.Status.PENDING, "履约单不是可释放状态"); - GaoSalesOrder order = salesOrderService.getForUpdate(fulfillment.getSalesOrderId()); - GaoCustomerPackage packageEntity = lockPackage(order, false); - List sourceList = loadSources(order, packageEntity, copyRequestItems(itemMapper.selectByFulfillmentIdForUpdate(fulfillment.getId())), false); + List fulfillmentItemList = itemMapper.selectByFulfillmentIdForUpdate(fulfillment.getId()); + TimiException.requiredTrue(!fulfillmentItemList.isEmpty(), "履约单没有明细"); if (fulfillment.getStatus() == GaoOrderFulfillment.Status.PENDING) { List stockItems = buildStockItems(order.getStoreId(), sourceList); if (!stockItems.isEmpty()) { @@ -312,37 +312,43 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService sourceItemList = itemMapper.selectByFulfillmentId(sourceSnapshot.getId()); + List sourceList = loadSources(order, copyRequestItems(sourceItemList), false); + GaoOrderFulfillment source = lockFulfillment(sourceSnapshot.getId(), order); + storeScopeService.checkStoreId(source.getStoreId()); TimiException.requiredTrue(source.getStatus() == GaoOrderFulfillment.Status.VERIFIED && source.getSourceFulfillmentId() == null, "只有原始已核销履约可以冲正"); TimiException.requiredNull(mapper.selectBySourceFulfillmentId(source.getId()), "该履约单已经冲正"); - GaoSalesOrder order = salesOrderService.getForUpdate(source.getSalesOrderId()); - GaoCustomerPackage packageEntity = lockPackage(order, false); - List sourceItemList = itemMapper.selectByFulfillmentIdForUpdate(source.getId()); - List sourceList = loadSources(order, packageEntity, copyRequestItems(sourceItemList), false); + sourceItemList = itemMapper.selectByFulfillmentIdForUpdate(source.getId()); + TimiException.requiredTrue(!sourceItemList.isEmpty(), "履约单没有明细"); List stockItems = buildStockItems(order.getStoreId(), sourceList); if (!stockItems.isEmpty()) { TimiException.requiredTrue(Boolean.TRUE.equals(request.getReturnedToStore()), "实物履约冲正必须确认已退回门店"); TimiException.requiredTrue(hasAttachment(request.getAttachmentList()), "实物履约冲正必须上传退回照片"); } String operatorUserId = userLoginService.getRequireLoginUserId(); - GaoOrderFulfillment reverse = newFulfillment(order, source.getFulfillmentType(), GaoOrderFulfillment.Status.VERIFIED, + GaoOrderFulfillment reverse = newFulfillment(order, GaoOrderFulfillment.Status.VERIFIED, requestNo, operatorUserId, TimiJava.defaultIfNull(request.getDeliveredAt(), Time.now()), request.getRemark()); reverse.setVerifierUserId(operatorUserId); reverse.setVerifiedAt(Time.now()); reverse.setSourceFulfillmentId(source.getId()); reverse.setReason(reason); super.create(reverse); - for (int i = 0; i < sourceList.size(); i++) { - SourceEntry sourceEntry = sourceList.get(i); + Map sourceItemMap = new HashMap<>(); + for (GaoOrderFulfillmentItem sourceItem : sourceItemList) { + sourceItemMap.put(sourceItem.getSourceOrderItemId(), sourceItem); + } + for (SourceEntry sourceEntry : sourceList) { decreaseFulfilled(sourceEntry); - GaoOrderFulfillmentItem sourceItem = sourceItemList.get(i); + GaoOrderFulfillmentItem sourceItem = sourceItemMap.get(sourceEntry.sourceId()); + TimiException.required(sourceItem, "履约明细来源订单项不存在"); GaoOrderFulfillmentItem reverseItem = copyFulfillmentItem(sourceItem); reverseItem.setFulfillmentId(reverse.getId()); reverseItem.setReservedBefore(sourceEntry.reservedBefore()); @@ -365,17 +371,16 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService sourceList) { for (SourceEntry source : sourceList) { - if (source.packageSource()) { - TimiException.requiredTrue(1 == customerPackageService.updateItemReservedQuantity(source.sourceId(), source.quantity()), "套餐权益占用失败"); - } else { - TimiException.requiredTrue(1 == salesOrderService.updateItemReservedQuantity(source.sourceId(), source.quantity()), "订单明细占用失败"); - } + TimiException.requiredTrue(1 == salesOrderService.updateItemReservedQuantity(source.sourceId(), source.quantity()), "订单明细占用失败"); GaoOrderFulfillmentItem item = source.toFulfillmentItem(fulfillment.getId()); itemMapper.insert(item); } } private void decreaseReserved(SourceEntry source) { - if (source.packageSource()) { - TimiException.requiredTrue(1 == customerPackageService.updateItemReservedQuantity(source.sourceId(), source.quantity().negate()), "套餐权益占用释放失败"); - } else { - TimiException.requiredTrue(1 == salesOrderService.updateItemReservedQuantity(source.sourceId(), source.quantity().negate()), "订单明细占用释放失败"); - } + TimiException.requiredTrue(1 == salesOrderService.updateItemReservedQuantity(source.sourceId(), source.quantity().negate()), "订单明细占用释放失败"); } private void increaseFulfilled(SourceEntry source) { - if (source.packageSource()) { - TimiException.requiredTrue(1 == customerPackageService.updateItemFulfilledQuantity(source.sourceId(), source.quantity()), "套餐权益履约失败"); - } else { - TimiException.requiredTrue(1 == salesOrderService.updateItemFulfilledQuantity(source.sourceId(), source.quantity()), "订单明细履约失败"); - } + TimiException.requiredTrue(1 == salesOrderService.updateItemFulfilledQuantity(source.sourceId(), source.quantity()), "订单明细履约失败"); } private void decreaseFulfilled(SourceEntry source) { TimiException.requiredTrue(0 <= source.fulfilledBefore().compareTo(source.quantity()), "已履约数量不足以冲正"); - if (source.packageSource()) { - TimiException.requiredTrue(1 == customerPackageService.updateItemFulfilledQuantity(source.sourceId(), source.quantity().negate()), "套餐权益冲正失败"); - } else { - TimiException.requiredTrue(1 == salesOrderService.updateItemFulfilledQuantity(source.sourceId(), source.quantity().negate()), "订单明细冲正失败"); - } + TimiException.requiredTrue(1 == salesOrderService.updateItemFulfilledQuantity(source.sourceId(), source.quantity().negate()), "订单明细冲正失败"); } private void assertReserved(List sourceList) { @@ -427,67 +416,48 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService loadSources(GaoSalesOrder order, GaoCustomerPackage packageEntity, - List requestItems, boolean checkAvailable) { + private List loadSources(GaoSalesOrder order, List requestItems, boolean checkAvailable) { TimiException.requiredTrue(requestItems != null && !requestItems.isEmpty(), "履约至少需要一个产品"); + List sortedRequestItems = new ArrayList<>(requestItems); Set sourceIdSet = new HashSet<>(); - List result = new ArrayList<>(); - for (GaoOrderFulfillmentItemRequest requestItem : requestItems) { + for (GaoOrderFulfillmentItemRequest requestItem : sortedRequestItems) { TimiException.required(requestItem, "履约明细不能为空"); - boolean hasOrderSource = hasText(requestItem.getSourceOrderItemId()); - boolean hasPackageSource = hasText(requestItem.getSourceCustomerPackageItemId()); - TimiException.requiredTrue(hasOrderSource ^ hasPackageSource, "履约明细来源必须二选一"); - String sourceId = hasOrderSource ? requestItem.getSourceOrderItemId() : requestItem.getSourceCustomerPackageItemId(); + String sourceId = requiredText(requestItem.getSourceOrderItemId(), "履约明细来源订单项不能为空"); + requestItem.setSourceOrderItemId(sourceId); TimiException.requiredTrue(sourceIdSet.add(sourceId), "同一履约单不能重复来源明细"); TimiException.required(requestItem.getQuantity(), "履约数量不能为空"); TimiException.requiredTrue(0 < requestItem.getQuantity().signum(), "履约数量必须大于 0"); TimiException.requiredTrue(3 >= requestItem.getQuantity().stripTrailingZeros().scale(), "履约数量最多保留三位小数"); - if (order.getOrderType() == GaoSalesOrder.OrderType.DIRECT_PRODUCT) { - TimiException.requiredTrue(hasOrderSource && !hasPackageSource, "直接产品订单只能引用订单明细"); - GaoSalesOrderItem source = salesOrderService.getItemForUpdate(sourceId); - TimiException.required(source, "not found sales order item"); - TimiException.requiredTrue(order.getId().equals(source.getSalesOrderId()), "订单明细不属于当前订单"); - result.add(toSource(order, source, requestItem.getQuantity(), false, checkAvailable)); - } else { - TimiException.requiredTrue(hasPackageSource && !hasOrderSource, "套餐订单只能引用客户套餐权益"); - TimiException.required(packageEntity, "订单未找到客户套餐权益"); - GaoCustomerPackageItem source = customerPackageService.getItemForUpdate(sourceId); - TimiException.required(source, "not found customer package item"); - TimiException.requiredTrue(packageEntity.getId().equals(source.getCustomerPackageId()), "套餐权益不属于当前订单"); - result.add(toSource(order, source, requestItem.getQuantity(), true, checkAvailable)); - } + } + sortedRequestItems.sort(Comparator.comparing(GaoOrderFulfillmentItemRequest::getSourceOrderItemId)); + List result = new ArrayList<>(); + for (GaoOrderFulfillmentItemRequest requestItem : sortedRequestItems) { + GaoSalesOrderItem source = salesOrderService.getItemForUpdate(requestItem.getSourceOrderItemId()); + 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, "订单项类型与订单类型不匹配"); + result.add(toSource(order, source, requestItem.getQuantity(), checkAvailable)); } return result; } - private SourceEntry toSource(GaoSalesOrder order, GaoSalesOrderItem source, BigDecimal quantity, boolean packageSource, boolean checkAvailable) { + private SourceEntry toSource(GaoSalesOrder order, GaoSalesOrderItem source, BigDecimal quantity, boolean checkAvailable) { GaoProduct product = productService.get(source.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(), null, source.getProductId(), source.getProductUnitId(), source.getProductNameSnapshot(), + return buildSource(order, source.getId(), source.getProductId(), source.getProductUnitId(), source.getProductNameSnapshot(), source.getUnitSnapshot(), baseQuantityPerUnit, quantity, source.getQuantity(), source.getReservedQuantity(), source.getFulfilledQuantity(), - product, unit, packageSource, checkAvailable); + product, unit, checkAvailable); } - private SourceEntry toSource(GaoSalesOrder order, GaoCustomerPackageItem source, BigDecimal quantity, boolean packageSource, boolean checkAvailable) { - TimiException.required(source.getProductId(), "套餐权益缺少产品快照,不能履约"); - TimiException.required(source.getProductUnitId(), "套餐权益缺少单位快照,不能履约"); - GaoProduct product = productService.get(source.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, null, source.getId(), source.getProductId(), source.getProductUnitId(), source.getProductNameSnapshot(), - source.getUnitSnapshot(), baseQuantityPerUnit, quantity, source.getTotalQuantity(), source.getReservedQuantity(), fulfilledQuantity(source), - product, unit, packageSource, checkAvailable); - } - - private SourceEntry buildSource(GaoSalesOrder order, String orderItemId, String packageItemId, String productId, String unitId, + 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, - boolean packageSource, boolean checkAvailable) { + boolean checkAvailable) { if (!Boolean.TRUE.equals(product.getAllowDecimalQuantity())) { TimiException.requiredTrue(quantity.stripTrailingZeros().scale() <= 0, "该产品不允许小数数量"); } @@ -497,8 +467,8 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService buildStockItems(String storeId, List sourceList) { @@ -523,51 +493,15 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService itemList = customerPackageService.listItemByCustomerPackageId(packageEntity.getId()); - boolean fulfilled = !itemList.isEmpty(); - for (GaoCustomerPackageItem item : itemList) { - if (decimal(fulfilledQuantity(item)).compareTo(decimal(item.getTotalQuantity())) < 0) { - fulfilled = false; - break; - } - } - if (fulfilled && packageEntity.getStatus() == GaoCustomerPackage.Status.ACTIVE) { - packageEntity.setStatus(GaoCustomerPackage.Status.FULFILLED); - customerPackageService.updateAggregateState(packageEntity); - } else if (!fulfilled && packageEntity.getStatus() == GaoCustomerPackage.Status.FULFILLED) { - packageEntity.setStatus(GaoCustomerPackage.Status.ACTIVE); - customerPackageService.updateAggregateState(packageEntity); - } - salesOrderService.updateFulfillmentStatus(order, fulfilled); - } else { - salesOrderService.refreshFulfillmentStatus(order); - } - } - - private List requestItems(List itemList) { - return itemList; + private List itemRequests(String fulfillmentId) { + return copyRequestItems(itemMapper.selectByFulfillmentId(fulfillmentId)); } private List copyRequestItems(List itemList) { @@ -575,7 +509,6 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService existingItems = itemMapper.selectByFulfillmentId(existing.getId()); - List requestItems = requestItems(request.getItemList()); + List requestItems = request.getItemList(); TimiException.requiredTrue(existingItems.size() == (requestItems == null ? 0 : requestItems.size()), "requestNo 对应履约请求内容不一致"); Map requestMap = new HashMap<>(); for (GaoOrderFulfillmentItemRequest item : TimiJava.safeIterable(requestItems)) { - String key = hasText(item.getSourceOrderItemId()) ? "O:" + item.getSourceOrderItemId() : "P:" + item.getSourceCustomerPackageItemId(); - requestMap.put(key, item.getQuantity()); + requestMap.put(item.getSourceOrderItemId(), item.getQuantity()); } for (GaoOrderFulfillmentItem item : existingItems) { - String key = item.getSourceOrderItemId() == null ? "P:" + item.getSourceCustomerPackageItemId() : "O:" + item.getSourceOrderItemId(); - TimiException.requiredTrue(item.getQuantity().compareTo(requestMap.getOrDefault(key, ZERO)) == 0, "requestNo 对应履约请求内容不一致"); + TimiException.requiredTrue(item.getQuantity().compareTo(requestMap.getOrDefault(item.getSourceOrderItemId(), ZERO)) == 0, "requestNo 对应履约请求内容不一致"); } } @@ -641,10 +571,6 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService fulfillmentServiceProvider; - private final ObjectProvider customerPackageServiceProvider; private final GaoCustomerService customerService; private final GaoProductService productService; private final GaoProductUnitService unitService; @@ -114,6 +112,9 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService itemList = itemMapper.selectBySalesOrderId(order.getId()); + for (GaoSalesOrderItem item : itemList) { + fillItemBalance(item); + } fillItemAttachments(itemList); order.setItemList(itemList); fillCustomer(List.of(order)); @@ -177,26 +178,9 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService itemList = itemMapper.selectBySalesOrderId(order.getId()); - boolean fulfilled = false; - if (order.getOrderType() == GaoSalesOrder.OrderType.DIRECT_PRODUCT) { - fulfilled = !itemList.isEmpty(); - for (GaoSalesOrderItem item : itemList) { - if (decimal(item.getFulfilledQuantity()).compareTo(decimal(item.getQuantity())) < 0) { - fulfilled = false; - break; - } + boolean fulfilled = !itemList.isEmpty(); + for (GaoSalesOrderItem item : itemList) { + if (decimal(item.getFulfilledQuantity()).compareTo(decimal(item.getQuantity())) < 0) { + fulfilled = false; + break; } } updateFulfillmentStatus(order, fulfilled); @@ -374,6 +335,16 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService mapByIdList(Collection idList) { + if (idList == null || idList.isEmpty()) { + return Map.of(); + } + List orderList = mapper.selectByIdList(idList); + fillItemList(orderList); + return orderList.stream().collect(java.util.stream.Collectors.toMap(GaoSalesOrder::getId, java.util.function.Function.identity())); + } + private List resolveDirectItems(String storeId, List sourceList) { TimiException.requiredTrue(sourceList != null && !sourceList.isEmpty(), "直接产品订单至少需要一个产品"); Set productUnitKeySet = new HashSet<>(); @@ -401,6 +372,7 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService itemList) { + TimiException.requiredTrue(itemList != null && !itemList.isEmpty(), "销售订单至少需要一个订单明细"); + TimiException.requiredTrue(0 <= saleAmount, "成交价不能为负数"); + GaoSalesOrderItem.ItemType expectedItemType = orderType == GaoSalesOrder.OrderType.PACKAGE + ? GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT : GaoSalesOrderItem.ItemType.DIRECT_PRODUCT; + Set productUnitKeySet = 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.getQuantity(), "订单项数量不能为空"); + TimiException.requiredTrue(0 < item.getQuantity().signum(), "订单项数量必须大于 0"); + TimiException.requiredTrue(3 >= item.getQuantity().stripTrailingZeros().scale(), "订单项数量最多保留三位小数"); + TimiException.required(item.getBaseQuantityPerUnitSnapshot(), "订单项单位换算快照不能为空"); + TimiException.requiredTrue(0 < item.getBaseQuantityPerUnitSnapshot().signum(), "订单项单位换算快照必须大于 0"); + TimiException.requiredTrue(item.getReservedQuantity() != null && item.getFulfilledQuantity() != null, + "订单项履约数量不能为空"); + TimiException.requiredTrue(0 <= item.getReservedQuantity().signum() && 0 <= item.getFulfilledQuantity().signum(), + "订单项履约数量不能为负数"); + TimiException.requiredTrue(item.getReservedQuantity().add(item.getFulfilledQuantity()).compareTo(item.getQuantity()) <= 0, + "订单项履约数量不能超过购买数量"); + if (expectedItemType == GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT) { + TimiException.requiredNull(item.getUnitPrice(), "套餐订单项不能填写单价"); + TimiException.requiredNull(item.getLineAmount(), "套餐订单项不能填写行金额"); + } else { + TimiException.required(item.getUnitPrice(), "直接产品订单项单价不能为空"); + TimiException.required(item.getLineAmount(), "直接产品订单项行金额不能为空"); + TimiException.requiredTrue(0 <= item.getUnitPrice() && 0 <= item.getLineAmount(), "直接产品订单金额不能为负数"); + lineAmountSum = Math.addExact(lineAmountSum, item.getLineAmount()); + } + } + if (expectedItemType == GaoSalesOrderItem.ItemType.DIRECT_PRODUCT) { + TimiException.requiredTrue(lineAmountSum == saleAmount, "直接产品订单行金额合计必须等于成交价"); + } + GaoSalesOrder order = newOrder(storeId, customerId, orderType, requestNo, purchasedAt, operatorUserId, remark); + order.setSaleAmount(saleAmount); + boolean paidAtCreate = saleAmount == 0; + order.setPaymentStatus(paidAtCreate ? GaoSalesOrder.PaymentStatus.PAID : GaoSalesOrder.PaymentStatus.UNPAID); + if (paidAtCreate) { + order.setPaidAt(Time.now()); + } + super.create(order); + for (GaoSalesOrderItem item : itemList) { + item.setSalesOrderId(order.getId()); + itemMapper.insert(item); + } + order.setItemList(itemList); + for (GaoSalesOrderItem item : itemList) { + fillItemBalance(item); + } + if (paidAtCreate) { + pointTriggerService.onSalesOrderPaid(order); + } + return order; + } + + private void verifyExistingPackageItems(GaoSalesOrder existing, List requestItemList) { + TimiException.requiredTrue(requestItemList != null && !requestItemList.isEmpty(), "销售订单至少需要一个订单明细"); + List existingItemList = itemMapper.selectBySalesOrderId(existing.getId()); + TimiException.requiredTrue(existingItemList.size() == requestItemList.size(), "requestNo 对应订单内容不一致"); + Map existingItemMap = existingItemList.stream() + .collect(java.util.stream.Collectors.toMap(item -> item.getProductId() + "|" + item.getProductUnitId(), java.util.function.Function.identity())); + for (GaoSalesOrderItem requestItem : requestItemList) { + GaoSalesOrderItem existingItem = existingItemMap.get(requestItem.getProductId() + "|" + requestItem.getProductUnitId()); + TimiException.required(existingItem, "requestNo 对应订单内容不一致"); + TimiException.requiredTrue(existingItem.getItemType() == GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT + && existingItem.getQuantity().compareTo(requestItem.getQuantity()) == 0 + && existingItem.getSort().equals(requestItem.getSort()) + && sameText(existingItem.getRemark(), requestItem.getRemark()), "requestNo 对应订单内容不一致"); + } + } + private void fillItemList(List orderList) { if (orderList == null || orderList.isEmpty()) { return; @@ -479,9 +528,24 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService itemList) { if (itemList == null || itemList.isEmpty()) { return; @@ -552,6 +616,10 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService itemList; } diff --git a/src/main/resources/db/migration/timiserver/V69__unify_gao_package_order_fulfillment.sql b/src/main/resources/db/migration/timiserver/V69__unify_gao_package_order_fulfillment.sql new file mode 100644 index 0000000..622854d --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V69__unify_gao_package_order_fulfillment.sql @@ -0,0 +1,38 @@ +ALTER TABLE `gao_sales_order_item` + ADD COLUMN `item_type` VARCHAR(32) NOT NULL DEFAULT 'DIRECT_PRODUCT' COMMENT 'DIRECT_PRODUCT 直接产品,PACKAGE_ENTITLEMENT 套餐权益' AFTER `sales_order_id`, + MODIFY COLUMN `unit_price` BIGINT NULL COMMENT '实际单价,套餐权益为空', + MODIFY COLUMN `line_amount` BIGINT NULL COMMENT '行金额,套餐权益为空'; + +ALTER TABLE `gao_order_fulfillment_item` + DROP KEY `uk_gao_order_fulfillment_item_package`, + DROP KEY `idx_gao_order_fulfillment_item_package_source`, + MODIFY COLUMN `source_order_item_id` VARCHAR(36) NOT NULL COMMENT '来源销售订单项 ID', + DROP COLUMN `source_customer_package_item_id`; + +ALTER TABLE `gao_order_fulfillment` + DROP COLUMN `fulfillment_type`; + +ALTER TABLE `gao_customer_package` + DROP KEY `idx_gao_customer_package_customer`, + DROP COLUMN `status`, + DROP COLUMN `cancelled_at`, + DROP COLUMN `cancel_operator_user_id`, + DROP COLUMN `cancel_reason`; + +DROP TABLE IF EXISTS `gao_customer_package_item`; + +DELETE role_permission +FROM `role_permission` role_permission +INNER JOIN `permission` permission ON permission.`id` = role_permission.`permission_id` +WHERE permission.`module_code` = 'GAO' + AND permission.`code` IN ('PACKAGE_DELIVERY:CREATE', 'PACKAGE_DELIVERY:READ', 'PACKAGE_DELIVERY:VERIFY', 'PACKAGE_DELIVERY:REVOKE'); + +DELETE role_permission_delegation +FROM `role_permission_delegation` role_permission_delegation +INNER JOIN `permission` permission ON permission.`id` = role_permission_delegation.`permission_id` +WHERE permission.`module_code` = 'GAO' + AND permission.`code` IN ('PACKAGE_DELIVERY:CREATE', 'PACKAGE_DELIVERY:READ', 'PACKAGE_DELIVERY:VERIFY', 'PACKAGE_DELIVERY:REVOKE'); + +DELETE FROM `permission` +WHERE `module_code` = 'GAO' + AND `code` IN ('PACKAGE_DELIVERY:CREATE', 'PACKAGE_DELIVERY:READ', 'PACKAGE_DELIVERY:VERIFY', 'PACKAGE_DELIVERY:REVOKE'); diff --git a/src/main/resources/mapper/timi-server/gao/GaoCustomerPackageItemMapper.xml b/src/main/resources/mapper/timi-server/gao/GaoCustomerPackageItemMapper.xml deleted file mode 100644 index 0ec6bde..0000000 --- a/src/main/resources/mapper/timi-server/gao/GaoCustomerPackageItemMapper.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - UPDATE `gao_customer_package_item` - SET `reserved_quantity` = `reserved_quantity` + #{delta}, `updated_at` = FLOOR(UNIX_TIMESTAMP(NOW(3)) * 1000) - WHERE `id` = #{id} AND `deleted_at` IS NULL - AND `reserved_quantity` + #{delta} >= 0 - AND `reserved_quantity` + `fulfilled_quantity` + #{delta} <= `total_quantity` - - - UPDATE `gao_customer_package_item` - SET `fulfilled_quantity` = `fulfilled_quantity` + #{delta}, `updated_at` = FLOOR(UNIX_TIMESTAMP(NOW(3)) * 1000) - WHERE `id` = #{id} AND `deleted_at` IS NULL - AND `fulfilled_quantity` + #{delta} >= 0 - AND `reserved_quantity` + `fulfilled_quantity` + #{delta} <= `total_quantity` - - diff --git a/src/main/resources/mapper/timi-server/gao/GaoCustomerPackageMapper.xml b/src/main/resources/mapper/timi-server/gao/GaoCustomerPackageMapper.xml index 035f9c4..f5c2975 100644 --- a/src/main/resources/mapper/timi-server/gao/GaoCustomerPackageMapper.xml +++ b/src/main/resources/mapper/timi-server/gao/GaoCustomerPackageMapper.xml @@ -1,11 +1,6 @@ - SELECT COUNT(1) FROM `gao_customer_package` p LEFT JOIN `gao_customer` c ON c.`id` = p.`customer_id` + INNER JOIN `gao_sales_order` o ON o.`id` = p.`sales_order_id` AND o.`store_id` = p.`store_id` AND o.`deleted_at` IS NULL WHERE + o.`deleted_at` IS NULL AND o.`store_id` = #{storeId}