update customer package

This commit is contained in:
Timi
2026-09-06 15:49:57 +08:00
parent f3166f0413
commit db2fca5715
25 changed files with 394 additions and 608 deletions
@@ -111,14 +111,6 @@ public enum GaoPermissionCode implements BuiltinPermissionCode {
CUSTOMER_PACKAGE_PRICE_OVERRIDE("CUSTOMER_PACKAGE:PRICE_OVERRIDE", "客户套餐成交价和权益配额调整", true, true), CUSTOMER_PACKAGE_PRICE_OVERRIDE("CUSTOMER_PACKAGE:PRICE_OVERRIDE", "客户套餐成交价和权益配额调整", true, true),
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_RECORD_READ("FUND_RECORD:READ", "资金流水读取", true, true),
FUND_RECEIVE_CREATE("FUND_RECEIVE:CREATE", "线下收款创建", true, true), FUND_RECEIVE_CREATE("FUND_RECEIVE:CREATE", "线下收款创建", true, true),
@@ -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.bean.GaoPermissionCode;
import com.imyeyu.api.modules.gao.entity.GaoCustomer; import com.imyeyu.api.modules.gao.entity.GaoCustomer;
import com.imyeyu.api.modules.gao.entity.GaoCustomerPackage; import com.imyeyu.api.modules.gao.entity.GaoCustomerPackage;
import com.imyeyu.api.modules.gao.entity.GaoCustomerPackageItem; import com.imyeyu.api.modules.gao.entity.GaoSalesOrder;
import com.imyeyu.api.modules.gao.entity.GaoOrderFulfillment;
import com.imyeyu.api.modules.gao.entity.GaoProduct;
import com.imyeyu.api.modules.gao.service.GaoCustomerPackageService; import com.imyeyu.api.modules.gao.service.GaoCustomerPackageService;
import com.imyeyu.api.modules.gao.service.GaoCustomerService; import com.imyeyu.api.modules.gao.service.GaoCustomerService;
import com.imyeyu.api.modules.gao.service.GaoOrderFulfillmentService; 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.service.GaoStoreScopeService;
import com.imyeyu.api.modules.gao.vo.GaoCustomerPackageCancelRequest; import com.imyeyu.api.modules.gao.vo.GaoCustomerPackageCancelRequest;
import com.imyeyu.api.modules.gao.vo.GaoCustomerPackagePage; 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.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -45,6 +43,7 @@ public class GaoCustomerPackageController {
private final GaoCustomerPackageService service; private final GaoCustomerPackageService service;
private final GaoCustomerService customerService; private final GaoCustomerService customerService;
private final GaoOrderFulfillmentService fulfillmentService; private final GaoOrderFulfillmentService fulfillmentService;
private final GaoSalesOrderService salesOrderService;
private final AttachmentService attachmentService; private final AttachmentService attachmentService;
private final GaoStoreScopeService storeScopeService; private final GaoStoreScopeService storeScopeService;
private final UserService userService; private final UserService userService;
@@ -57,23 +56,7 @@ public class GaoCustomerPackageController {
TimiException.required(page, "not found page"); TimiException.required(page, "not found page");
page.setStoreId(storeScopeService.resolveStoreId(page.getStoreId())); page.setStoreId(storeScopeService.resolveStoreId(page.getStoreId()));
PageResult<GaoCustomerPackage> result = service.pageByQuery(page); PageResult<GaoCustomerPackage> result = service.pageByQuery(page);
if (result.getList().isEmpty()) { fillTransient(result.getList());
return result;
}
List<String> customerIdList = result.getList().stream()
.map(GaoCustomerPackage::getCustomerId)
.filter(value -> value != null && !value.isBlank())
.distinct()
.toList();
Map<String, GaoCustomer> customerMap = customerService.mapByIdList(customerIdList);
Map<String, List<Attachment>> 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);
}
return result; return result;
} }
@@ -119,52 +102,42 @@ public class GaoCustomerPackageController {
if (packageList == null || packageList.isEmpty()) { if (packageList == null || packageList.isEmpty()) {
return; return;
} }
Map<String, List<GaoCustomerPackageItem>> itemMap = service.mapItemListByCustomerPackageIdList(packageList.stream().map(GaoCustomerPackage::getId).toList()); Map<String, GaoSalesOrder> orderMap = salesOrderService.mapByIdList(packageList.stream()
.map(GaoCustomerPackage::getSalesOrderId)
.filter(value -> value != null && !value.isBlank())
.distinct()
.toList());
List<String> customerIdList = packageList.stream()
.map(GaoCustomerPackage::getCustomerId)
.filter(value -> value != null && !value.isBlank())
.distinct()
.toList();
Map<String, GaoCustomer> customerMap = customerIdList.isEmpty() ? Map.of() : customerService.mapByIdList(customerIdList);
Map<String, List<Attachment>> attachmentMap = customerIdList.isEmpty()
? Map.of() : attachmentService.mapByBizIdList(Attachment.BizType.GAO_CUSTOMER, customerIdList, GaoCustomer.AttachType.PHOTO.name());
List<String> userIdList = packageList.stream() List<String> 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()) .filter(value -> value != null && !value.isBlank())
.distinct() .distinct()
.toList(); .toList();
Map<String, User> userMap = userIdList.isEmpty() ? Map.of() : userService.mapByIdList(userIdList); Map<String, User> userMap = userIdList.isEmpty() ? Map.of() : userService.mapByIdList(userIdList);
for (GaoCustomerPackage packageEntity : packageList) { for (GaoCustomerPackage packageEntity : packageList) {
List<GaoCustomerPackageItem> itemList = itemMap.getOrDefault(packageEntity.getId(), List.of()); GaoSalesOrder order = orderMap.get(packageEntity.getSalesOrderId());
fillItemAttachments(itemList); packageEntity.setSalesOrder(order);
for (GaoCustomerPackageItem item : itemList) { packageEntity.setStatus(order == null ? null : order.getStatus());
fillDerived(item); 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); packageEntity.setCustomer(customer);
String cancelOperatorUserId = packageEntity.getCancelOperatorUserId(); String cancelOperatorUserId = order == null ? null : order.getCancelOperatorUserId();
packageEntity.setCancelOperator(cancelOperatorUserId == null ? null : userMap.get(cancelOperatorUserId)); packageEntity.setCancelOperator(cancelOperatorUserId == null ? null : userMap.get(cancelOperatorUserId));
} }
} }
private void fillItemAttachments(List<GaoCustomerPackageItem> itemList) {
if (itemList == null || itemList.isEmpty()) {
return;
}
List<String> productIdList = itemList.stream()
.map(GaoCustomerPackageItem::getProductId)
.filter(value -> value != null && !value.isBlank())
.distinct()
.toList();
if (productIdList.isEmpty()) {
return;
}
Map<String, List<Attachment>> 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));
}
} }
@@ -16,19 +16,6 @@ import java.util.List;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class GaoCustomerPackage extends UUIDEntity { public class GaoCustomerPackage extends UUIDEntity {
/// 客户套餐状态
public enum Status {
/// 履约中
ACTIVE,
/// 已全部履约
FULFILLED,
/// 已取消
CANCELLED
}
/// 门店 ID /// 门店 ID
private String storeId; private String storeId;
@@ -47,16 +34,20 @@ public class GaoCustomerPackage extends UUIDEntity {
/// 标准价快照,单位分 /// 标准价快照,单位分
private Long standardAmountSnapshot; private Long standardAmountSnapshot;
/// 套餐状态 /// 订单投影状态
private Status status; @Transient
private GaoSalesOrder.Status status;
/// 取消时间 /// 订单投影取消时间
@Transient
private Long cancelledAt; private Long cancelledAt;
/// 取消操作人 /// 订单投影取消操作人 ID
@Transient
private String cancelOperatorUserId; private String cancelOperatorUserId;
/// 取消原因 /// 订单投影取消原因
@Transient
private String cancelReason; private String cancelReason;
/// 备注 /// 备注
@@ -70,9 +61,13 @@ public class GaoCustomerPackage extends UUIDEntity {
@Transient @Transient
private User cancelOperator; private User cancelOperator;
/// 产品权益列表 /// 关联销售订单
@Transient @Transient
private List<GaoCustomerPackageItem> itemList; private GaoSalesOrder salesOrder;
/// 关联订单可履约明细投影
@Transient
private List<GaoSalesOrderItem> itemList;
/// 统一履约历史 /// 统一履约历史
@Transient @Transient
@@ -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<Attachment> 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;
}
@@ -7,10 +7,8 @@ import com.imyeyu.spring.annotation.table.Id;
import com.imyeyu.spring.annotation.table.Transient; import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.Creatable; import com.imyeyu.spring.entity.Creatable;
import com.imyeyu.spring.entity.IDEntity; import com.imyeyu.spring.entity.IDEntity;
import com.imyeyu.spring.entity.UUIDEntity;
import com.imyeyu.spring.entity.Updatable; import com.imyeyu.spring.entity.Updatable;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List; import java.util.List;
@@ -21,16 +19,6 @@ import java.util.List;
@Data @Data
public class GaoOrderFulfillment implements IDEntity<String>, Creatable, Updatable { public class GaoOrderFulfillment implements IDEntity<String>, Creatable, Updatable {
/// 履约来源类型
public enum FulfillmentType {
/// 客户套餐权益
PACKAGE_ENTITLEMENT,
/// 直接产品订单项
DIRECT_PRODUCT
}
/// 履约状态 /// 履约状态
public enum Status { public enum Status {
@@ -66,9 +54,6 @@ public class GaoOrderFulfillment implements IDEntity<String>, Creatable, Updatab
/// 客户 ID /// 客户 ID
private String customerId; private String customerId;
/// 履约来源类型
private FulfillmentType fulfillmentType;
/// 履约状态 /// 履约状态
private Status status; private Status status;
@@ -35,12 +35,9 @@ public class GaoOrderFulfillmentItem implements IDEntity<String>, Creatable {
@Transient @Transient
private List<Attachment> attachmentList; private List<Attachment> attachmentList;
/// 直接订单明细 ID /// 来源销售订单明细 ID
private String sourceOrderItemId; private String sourceOrderItemId;
/// 客户套餐权益项 ID
private String sourceCustomerPackageItemId;
/// 本次履约数量 /// 本次履约数量
private BigDecimal quantity; private BigDecimal quantity;
@@ -138,7 +138,7 @@ public class GaoSalesOrder extends UUIDEntity {
@Transient @Transient
private User cancelOperator; private User cancelOperator;
/// 直接产品订单明细 /// 销售订单明细
@Transient @Transient
private List<GaoSalesOrderItem> itemList; private List<GaoSalesOrderItem> itemList;
@@ -9,7 +9,7 @@ import lombok.EqualsAndHashCode;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.List; import java.util.List;
/// GAO 直接产品订单明细 /// GAO 销售订单通用可履约明细
/// ///
/// @author Codex /// @author Codex
/// @since 2026-09-02 /// @since 2026-09-02
@@ -17,9 +17,22 @@ import java.util.List;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class GaoSalesOrderItem extends UUIDEntity { public class GaoSalesOrderItem extends UUIDEntity {
/// 订单项类型
public enum ItemType {
/// 直接产品
DIRECT_PRODUCT,
/// 套餐权益
PACKAGE_ENTITLEMENT
}
/// 销售订单 ID /// 销售订单 ID
private String salesOrderId; private String salesOrderId;
/// 订单项类型
private ItemType itemType;
/// 产品 ID /// 产品 ID
private String productId; private String productId;
@@ -59,4 +72,16 @@ public class GaoSalesOrderItem extends UUIDEntity {
/// 备注 /// 备注
private String remark; private String remark;
/// 待核销数量
@Transient
private BigDecimal pendingQuantity;
/// 当前可履约数量
@Transient
private BigDecimal availableQuantity;
/// 权益剩余数量
@Transient
private BigDecimal remainingQuantity;
} }
@@ -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<GaoCustomerPackageItem, String> {
/// 按客户套餐查询产品权益
List<GaoCustomerPackageItem> selectByCustomerPackageId(@Param("customerPackageId") String customerPackageId);
/// 批量查询客户套餐产品权益
List<GaoCustomerPackageItem> selectByCustomerPackageIdList(@Param("customerPackageIdList") Collection<String> 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);
}
@@ -11,9 +11,6 @@ import org.apache.ibatis.annotations.Param;
/// @since 2026-08-31 /// @since 2026-08-31
public interface GaoCustomerPackageMapper extends BaseMapper<GaoCustomerPackage, String> { public interface GaoCustomerPackageMapper extends BaseMapper<GaoCustomerPackage, String> {
/// 锁定客户套餐
GaoCustomerPackage selectForUpdate(@Param("id") String id);
/// 按销售订单查询客户套餐 /// 按销售订单查询客户套餐
GaoCustomerPackage selectBySalesOrderId(@Param("salesOrderId") String salesOrderId); GaoCustomerPackage selectBySalesOrderId(@Param("salesOrderId") String salesOrderId);
@@ -6,6 +6,7 @@ import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import java.util.List; import java.util.List;
import java.util.Collection;
/// GAO 销售订单 Mapper /// GAO 销售订单 Mapper
/// ///
@@ -19,6 +20,9 @@ public interface GaoSalesOrderMapper extends BaseMapper<GaoSalesOrder, String> {
/// 锁定订单 /// 锁定订单
GaoSalesOrder selectForUpdate(@Param("id") String id); GaoSalesOrder selectForUpdate(@Param("id") String id);
/// 批量查询订单
List<GaoSalesOrder> selectByIdList(@Param("idList") Collection<String> idList);
/// 按条件统计订单 /// 按条件统计订单
long countByQuery(GaoSalesOrderPage page); long countByQuery(GaoSalesOrderPage page);
@@ -1,17 +1,12 @@
package com.imyeyu.api.modules.gao.service; package com.imyeyu.api.modules.gao.service;
import com.imyeyu.api.modules.gao.entity.GaoCustomerPackage; 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.GaoCustomerPackageCancelRequest;
import com.imyeyu.api.modules.gao.vo.GaoCustomerPackagePage; import com.imyeyu.api.modules.gao.vo.GaoCustomerPackagePage;
import com.imyeyu.api.modules.gao.vo.GaoCustomerPackagePurchaseRequest; import com.imyeyu.api.modules.gao.vo.GaoCustomerPackagePurchaseRequest;
import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.service.BaseService; import com.imyeyu.spring.service.BaseService;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/// GAO 客户套餐服务 /// GAO 客户套餐服务
/// ///
@@ -22,55 +17,12 @@ public interface GaoCustomerPackageService extends BaseService<GaoCustomerPackag
/// 按条件查询客户套餐 /// 按条件查询客户套餐
PageResult<GaoCustomerPackage> pageByQuery(GaoCustomerPackagePage page); PageResult<GaoCustomerPackage> pageByQuery(GaoCustomerPackagePage page);
/// 创建客户套餐及购买事件登记 /// 创建客户套餐及套餐订单
GaoCustomerPackage purchase(GaoCustomerPackagePurchaseRequest request); GaoCustomerPackage purchase(GaoCustomerPackagePurchaseRequest request);
/// 取消客户套餐 /// 取消客户套餐
GaoCustomerPackage cancel(GaoCustomerPackageCancelRequest request); GaoCustomerPackage cancel(GaoCustomerPackageCancelRequest request);
/// 锁定客户套餐
///
/// @param id 客户套餐 ID
/// @return 已锁定的客户套餐,不存在时为空
GaoCustomerPackage getForUpdate(String id);
/// 按销售订单查询客户套餐 /// 按销售订单查询客户套餐
GaoCustomerPackage getBySalesOrderId(String salesOrderId); GaoCustomerPackage getBySalesOrderId(String salesOrderId);
/// 查询客户套餐产品权益
///
/// @param customerPackageId 客户套餐 ID
/// @return 产品权益列表
List<GaoCustomerPackageItem> listItemByCustomerPackageId(String customerPackageId);
/// 批量查询客户套餐产品权益
///
/// @param customerPackageIdList 客户套餐 ID 列表
/// @return 按客户套餐 ID 分组的产品权益
Map<String, List<GaoCustomerPackageItem>> mapItemListByCustomerPackageIdList(Collection<String> 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);
} }
@@ -11,7 +11,9 @@ import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.service.BaseService; import com.imyeyu.spring.service.BaseService;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map;
/// GAO 销售订单服务 /// GAO 销售订单服务
/// ///
@@ -68,9 +70,12 @@ public interface GaoSalesOrderService extends BaseService<GaoSalesOrder, String>
/// 根据订单明细刷新订单履约状态 /// 根据订单明细刷新订单履约状态
void refreshFulfillmentStatus(GaoSalesOrder order); void refreshFulfillmentStatus(GaoSalesOrder order);
/// 写入套餐订单履约状态 /// 写入销售订单履约状态
void updateFulfillmentStatus(GaoSalesOrder order, boolean fulfilled); void updateFulfillmentStatus(GaoSalesOrder order, boolean fulfilled);
/// 查询订单明细 /// 查询订单明细
List<GaoSalesOrderItem> listItemByOrderId(String orderId); List<GaoSalesOrderItem> listItemByOrderId(String orderId);
/// 批量查询订单及其明细
Map<String, GaoSalesOrder> mapByIdList(Collection<String> idList);
} }
@@ -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.common.service.AttachmentService;
import com.imyeyu.api.modules.gao.entity.GaoCustomer; import com.imyeyu.api.modules.gao.entity.GaoCustomer;
import com.imyeyu.api.modules.gao.entity.GaoCustomerPackage; import com.imyeyu.api.modules.gao.entity.GaoCustomerPackage;
import com.imyeyu.api.modules.gao.entity.GaoCustomerPackageItem;
import com.imyeyu.api.modules.gao.entity.GaoPackageConfig; import com.imyeyu.api.modules.gao.entity.GaoPackageConfig;
import com.imyeyu.api.modules.gao.entity.GaoPackageConfigItem; import com.imyeyu.api.modules.gao.entity.GaoPackageConfigItem;
import com.imyeyu.api.modules.gao.entity.GaoSalesOrder; 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.mapper.GaoCustomerPackageMapper;
import com.imyeyu.api.modules.gao.service.GaoSalesOrderService; import com.imyeyu.api.modules.gao.service.GaoSalesOrderService;
import com.imyeyu.api.modules.gao.service.GaoCustomerService; import com.imyeyu.api.modules.gao.service.GaoCustomerService;
@@ -39,7 +38,6 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -55,10 +53,7 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor @RequiredArgsConstructor
public class GaoCustomerPackageServiceImplement extends AbstractEntityService<GaoCustomerPackage, String> implements GaoCustomerPackageService { public class GaoCustomerPackageServiceImplement extends AbstractEntityService<GaoCustomerPackage, String> implements GaoCustomerPackageService {
private static final BigDecimal ZERO = BigDecimal.ZERO;
private final GaoCustomerPackageMapper mapper; private final GaoCustomerPackageMapper mapper;
private final GaoCustomerPackageItemMapper itemMapper;
private final GaoPackageConfigService packageConfigService; private final GaoPackageConfigService packageConfigService;
private final GaoProductService productService; private final GaoProductService productService;
private final GaoCustomerService customerService; private final GaoCustomerService customerService;
@@ -74,42 +69,12 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
return mapper; return mapper;
} }
@Override
public GaoCustomerPackage getForUpdate(String id) {
return mapper.selectForUpdate(id);
}
@Override @Override
public GaoCustomerPackage getBySalesOrderId(String salesOrderId) { public GaoCustomerPackage getBySalesOrderId(String salesOrderId) {
TimiException.required(salesOrderId, "not found salesOrderId"); TimiException.required(salesOrderId, "not found salesOrderId");
return mapper.selectBySalesOrderId(salesOrderId); return mapper.selectBySalesOrderId(salesOrderId);
} }
@Override
public List<GaoCustomerPackageItem> 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 客户套餐 /// @param packageEntity 客户套餐
@@ -204,6 +169,7 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
verifySameQuantityAsConfig(itemList, configItemList); verifySameQuantityAsConfig(itemList, configItemList);
} }
List<GaoSalesOrderItem> orderItemList = itemList.stream().map(this::toOrderItem).toList();
GaoPackageOrderCreateData orderData = new GaoPackageOrderCreateData(); GaoPackageOrderCreateData orderData = new GaoPackageOrderCreateData();
orderData.setStoreId(config.getStoreId()); orderData.setStoreId(config.getStoreId());
orderData.setCustomerId(customer.getId()); orderData.setCustomerId(customer.getId());
@@ -215,6 +181,7 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
orderData.setPurchasedAt(purchasedAt); orderData.setPurchasedAt(purchasedAt);
orderData.setOperatorUserId(userLoginService.getRequireLoginUserId()); orderData.setOperatorUserId(userLoginService.getRequireLoginUserId());
orderData.setRemark(request.getRemark()); orderData.setRemark(request.getRemark());
orderData.setItemList(orderItemList);
GaoSalesOrder salesOrder = salesOrderService.createPackageOrder(orderData); GaoSalesOrder salesOrder = salesOrderService.createPackageOrder(orderData);
attachmentService.updateByBizId(Attachment.BizType.GAO_SALES_ORDER, salesOrder.getId(), request.getAttachmentList()); attachmentService.updateByBizId(Attachment.BizType.GAO_SALES_ORDER, salesOrder.getId(), request.getAttachmentList());
@@ -225,24 +192,11 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
result.setPackageConfigId(config.getId()); result.setPackageConfigId(config.getId());
result.setPackageNameSnapshot(config.getName()); result.setPackageNameSnapshot(config.getName());
result.setStandardAmountSnapshot(config.getStandardAmount()); result.setStandardAmountSnapshot(config.getStandardAmount());
result.setStatus(GaoCustomerPackage.Status.ACTIVE);
result.setRemark(request.getRemark()); result.setRemark(request.getRemark());
super.create(result); super.create(result);
for (GaoCustomerPackagePurchaseItem source : itemList) { result.setSalesOrder(salesOrder);
GaoCustomerPackageItem item = new GaoCustomerPackageItem(); result.setItemList(salesOrder.getItemList());
item.setCustomerPackageId(result.getId()); result.setStatus(salesOrder.getStatus());
item.setProductId(source.getProductId());
item.setProductUnitId(source.getProductUnitId());
item.setProductNameSnapshot(source.getProductNameSnapshot());
item.setUnitSnapshot(source.getUnitSnapshot());
item.setBaseQuantityPerUnitSnapshot(source.getBaseQuantityPerUnitSnapshot());
item.setTotalQuantity(source.getTotalQuantity());
item.setReservedQuantity(ZERO);
item.setFulfilledQuantity(ZERO);
item.setSort(source.getSort());
item.setRemark(source.getRemark());
itemMapper.insert(item);
}
return result; return result;
} }
@@ -256,20 +210,20 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
TimiException.required(request, "not found cancel request"); TimiException.required(request, "not found cancel request");
TimiException.required(request.getCustomerPackageId(), "not found customerPackageId"); TimiException.required(request.getCustomerPackageId(), "not found customerPackageId");
String reason = requiredReason(request.getReason(), "取消原因不能为空"); String reason = requiredReason(request.getReason(), "取消原因不能为空");
GaoCustomerPackage packageEntity = mapper.selectForUpdate(request.getCustomerPackageId()); GaoCustomerPackage packageEntity = get(request.getCustomerPackageId());
TimiException.required(packageEntity, "not found customer package"); TimiException.required(packageEntity, "not found customer package");
storeScopeService.checkStoreId(packageEntity.getStoreId()); storeScopeService.checkStoreId(packageEntity.getStoreId());
TimiException.requiredTrue(packageEntity.getStatus() == GaoCustomerPackage.Status.ACTIVE, "当前套餐不可取消");
TimiException.required(packageEntity.getSalesOrderId(), "套餐未关联销售订单"); TimiException.required(packageEntity.getSalesOrderId(), "套餐未关联销售订单");
GaoSalesOrderCancelRequest orderCancelRequest = new GaoSalesOrderCancelRequest(); GaoSalesOrderCancelRequest orderCancelRequest = new GaoSalesOrderCancelRequest();
orderCancelRequest.setSalesOrderId(packageEntity.getSalesOrderId()); orderCancelRequest.setSalesOrderId(packageEntity.getSalesOrderId());
orderCancelRequest.setReason(reason); orderCancelRequest.setReason(reason);
GaoSalesOrder order = salesOrderService.cancel(orderCancelRequest); GaoSalesOrder order = salesOrderService.cancel(orderCancelRequest);
packageEntity.setStatus(GaoCustomerPackage.Status.CANCELLED); packageEntity.setSalesOrder(order);
packageEntity.setStatus(order.getStatus());
packageEntity.setCancelledAt(order.getCancelledAt()); packageEntity.setCancelledAt(order.getCancelledAt());
packageEntity.setCancelOperatorUserId(order.getCancelOperatorUserId()); packageEntity.setCancelOperatorUserId(order.getCancelOperatorUserId());
packageEntity.setCancelReason(reason); packageEntity.setCancelReason(order.getCancelReason());
mapper.update(packageEntity); packageEntity.setItemList(salesOrderService.listItemByOrderId(order.getId()));
return packageEntity; return packageEntity;
} }
@@ -295,6 +249,24 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
}).toList(); }).toList();
} }
private GaoSalesOrderItem toOrderItem(GaoCustomerPackagePurchaseItem source) {
GaoSalesOrderItem item = new GaoSalesOrderItem();
item.setItemType(GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT);
item.setProductId(source.getProductId());
item.setProductUnitId(source.getProductUnitId());
item.setProductNameSnapshot(source.getProductNameSnapshot());
item.setUnitSnapshot(source.getUnitSnapshot());
item.setBaseQuantityPerUnitSnapshot(source.getBaseQuantityPerUnitSnapshot());
item.setQuantity(source.getTotalQuantity());
item.setReservedQuantity(BigDecimal.ZERO);
item.setFulfilledQuantity(BigDecimal.ZERO);
item.setUnitPrice(null);
item.setLineAmount(null);
item.setSort(source.getSort());
item.setRemark(source.getRemark());
return item;
}
private void verifyPurchaseItems(List<GaoCustomerPackagePurchaseItem> itemList) { private void verifyPurchaseItems(List<GaoCustomerPackagePurchaseItem> itemList) {
TimiException.requiredTrue(!itemList.isEmpty(), "购买套餐至少需要一个产品"); TimiException.requiredTrue(!itemList.isEmpty(), "购买套餐至少需要一个产品");
Set<String> keySet = new HashSet<>(); Set<String> keySet = new HashSet<>();
@@ -351,29 +323,21 @@ public class GaoCustomerPackageServiceImplement extends AbstractEntityService<Ga
TimiException.requiredTrue(existingPackage.getPackageConfigId().equals(request.getPackageConfigId()) TimiException.requiredTrue(existingPackage.getPackageConfigId().equals(request.getPackageConfigId())
&& (request.getPurchasedAt() == null || existingOrder.getPurchasedAt().equals(request.getPurchasedAt())) && (request.getPurchasedAt() == null || existingOrder.getPurchasedAt().equals(request.getPurchasedAt()))
&& sameText(existingOrder.getRemark(), request.getRemark()), "requestNo 对应购买请求内容不一致"); && sameText(existingOrder.getRemark(), request.getRemark()), "requestNo 对应购买请求内容不一致");
List<GaoCustomerPackageItem> existingItemList = itemMapper.selectByCustomerPackageId(existingPackage.getId()); List<GaoSalesOrderItem> existingItemList = salesOrderService.listItemByOrderId(existingOrder.getId());
List<GaoCustomerPackagePurchaseItem> requestItemList = TimiJava.defaultIfNull(request.getItemList(), List.of()); List<GaoCustomerPackagePurchaseItem> requestItemList = TimiJava.defaultIfNull(request.getItemList(), List.of());
verifyPurchaseItems(requestItemList); verifyPurchaseItems(requestItemList);
TimiException.requiredTrue(existingItemList.size() == requestItemList.size(), "requestNo 对应购买请求内容不一致"); TimiException.requiredTrue(existingItemList.size() == requestItemList.size(), "requestNo 对应购买请求内容不一致");
Map<String, GaoCustomerPackageItem> existingItemMap = existingItemList.stream().collect(Collectors.toMap(item -> item.getProductId() + "|" + item.getProductUnitId(), Function.identity())); Map<String, GaoSalesOrderItem> existingItemMap = existingItemList.stream().collect(Collectors.toMap(item -> item.getProductId() + "|" + item.getProductUnitId(), Function.identity()));
for (GaoCustomerPackagePurchaseItem item : requestItemList) { 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.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()) && existingItem.getSort().equals(item.getSort())
&& sameText(existingItem.getRemark(), item.getRemark()), "requestNo 对应购买请求内容不一致"); && sameText(existingItem.getRemark(), item.getRemark()), "requestNo 对应购买请求内容不一致");
} }
} }
@Override
public Map<String, List<GaoCustomerPackageItem>> mapItemListByCustomerPackageIdList(Collection<String> 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) { private String requiredReason(String reason, String message) {
TimiException.required(reason, message); TimiException.required(reason, message);
String result = reason.trim(); String result = reason.trim();
@@ -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.entity.Attachment;
import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.gao.bean.GaoRoleCode; 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.GaoProduct;
import com.imyeyu.api.modules.gao.entity.GaoProductUnit; import com.imyeyu.api.modules.gao.entity.GaoProductUnit;
import com.imyeyu.api.modules.gao.entity.GaoSalesOrder; 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.entity.GaoOrderFulfillmentItem;
import com.imyeyu.api.modules.gao.mapper.GaoOrderFulfillmentItemMapper; import com.imyeyu.api.modules.gao.mapper.GaoOrderFulfillmentItemMapper;
import com.imyeyu.api.modules.gao.mapper.GaoOrderFulfillmentMapper; 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.GaoOrderFulfillmentService;
import com.imyeyu.api.modules.gao.service.GaoProductService; import com.imyeyu.api.modules.gao.service.GaoProductService;
import com.imyeyu.api.modules.gao.service.GaoProductStockService; import com.imyeyu.api.modules.gao.service.GaoProductStockService;
@@ -70,7 +67,6 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
private final GaoOrderFulfillmentMapper mapper; private final GaoOrderFulfillmentMapper mapper;
private final GaoOrderFulfillmentItemMapper itemMapper; private final GaoOrderFulfillmentItemMapper itemMapper;
private final GaoSalesOrderService salesOrderService; private final GaoSalesOrderService salesOrderService;
private final GaoCustomerPackageService customerPackageService;
private final GaoProductService productService; private final GaoProductService productService;
private final GaoProductUnitService unitService; private final GaoProductUnitService unitService;
private final GaoProductStockService productStockService; private final GaoProductStockService productStockService;
@@ -166,13 +162,11 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
TimiException.requiredTrue(hasAttachment(request.getAttachmentList()), "履约必须上传照片"); TimiException.requiredTrue(hasAttachment(request.getAttachmentList()), "履约必须上传照片");
TimiException.requiredTrue(order.getStatus() != GaoSalesOrder.Status.CANCELLED, "已取消订单不能履约"); TimiException.requiredTrue(order.getStatus() != GaoSalesOrder.Status.CANCELLED, "已取消订单不能履约");
salesOrderService.checkFulfillmentPermission(order); salesOrderService.checkFulfillmentPermission(order);
GaoCustomerPackage packageEntity = lockPackage(order, true); List<SourceEntry> sourceList = loadSources(order, request.getItemList(), true);
List<SourceEntry> sourceList = loadSources(order, packageEntity, requestItems(request.getItemList()), true);
List<GaoStockChangeItem> stockItems = buildStockItems(order.getStoreId(), sourceList); List<GaoStockChangeItem> stockItems = buildStockItems(order.getStoreId(), sourceList);
boolean stockReady = stockItems.isEmpty() || productStockService.hasSufficientForFulfillment(order.getStoreId(), stockItems); boolean stockReady = stockItems.isEmpty() || productStockService.hasSufficientForFulfillment(order.getStoreId(), stockItems);
String operatorUserId = userLoginService.getRequireLoginUserId(); String operatorUserId = userLoginService.getRequireLoginUserId();
GaoOrderFulfillment fulfillment = newFulfillment(order, sourceList.getFirst().packageSource() GaoOrderFulfillment fulfillment = newFulfillment(order,
? GaoOrderFulfillment.FulfillmentType.PACKAGE_ENTITLEMENT : GaoOrderFulfillment.FulfillmentType.DIRECT_PRODUCT,
stockReady ? GaoOrderFulfillment.Status.PENDING : GaoOrderFulfillment.Status.WAITING_STOCK, stockReady ? GaoOrderFulfillment.Status.PENDING : GaoOrderFulfillment.Status.WAITING_STOCK,
requestNo, operatorUserId, TimiJava.defaultIfNull(request.getDeliveredAt(), Time.now()), request.getRemark()); requestNo, operatorUserId, TimiJava.defaultIfNull(request.getDeliveredAt(), Time.now()), request.getRemark());
super.create(fulfillment); super.create(fulfillment);
@@ -195,15 +189,17 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
TimiException.required(request, "not found prepare stock request"); TimiException.required(request, "not found prepare stock request");
TimiException.required(request.getFulfillmentId(), "not found fulfillmentId"); TimiException.required(request.getFulfillmentId(), "not found fulfillmentId");
roleChecker.checkAny(ModuleCode.GAO, GaoRoleCode.STORE_MANAGER.name()); roleChecker.checkAny(ModuleCode.GAO, GaoRoleCode.STORE_MANAGER.name());
GaoOrderFulfillment fulfillment = mapper.selectForUpdate(request.getFulfillmentId()); GaoOrderFulfillment snapshot = mapper.selectById(request.getFulfillmentId());
TimiException.required(fulfillment, "not found fulfillment"); TimiException.required(snapshot, "not found fulfillment");
GaoSalesOrder order = salesOrderService.getForUpdate(snapshot.getSalesOrderId());
List<SourceEntry> sourceList = loadSources(order, itemRequests(snapshot.getId()), false);
GaoOrderFulfillment fulfillment = lockFulfillment(snapshot.getId(), order);
storeScopeService.checkStoreId(fulfillment.getStoreId()); storeScopeService.checkStoreId(fulfillment.getStoreId());
TimiException.requiredTrue(fulfillment.getStatus() == GaoOrderFulfillment.Status.WAITING_STOCK, "履约单不是等待库存状态"); TimiException.requiredTrue(fulfillment.getStatus() == GaoOrderFulfillment.Status.WAITING_STOCK, "履约单不是等待库存状态");
GaoSalesOrder order = salesOrderService.getForUpdate(fulfillment.getSalesOrderId());
TimiException.requiredTrue(order.getStatus() != GaoSalesOrder.Status.CANCELLED, "已取消订单不能准备库存"); TimiException.requiredTrue(order.getStatus() != GaoSalesOrder.Status.CANCELLED, "已取消订单不能准备库存");
salesOrderService.checkFulfillmentPermission(order); salesOrderService.checkFulfillmentPermission(order);
GaoCustomerPackage packageEntity = lockPackage(order, false); List<GaoOrderFulfillmentItem> fulfillmentItemList = itemMapper.selectByFulfillmentIdForUpdate(fulfillment.getId());
List<SourceEntry> sourceList = loadSources(order, packageEntity, copyRequestItems(itemMapper.selectByFulfillmentIdForUpdate(fulfillment.getId())), false); TimiException.requiredTrue(!fulfillmentItemList.isEmpty(), "履约单没有明细");
List<GaoStockChangeItem> stockItems = buildStockItems(order.getStoreId(), sourceList); List<GaoStockChangeItem> stockItems = buildStockItems(order.getStoreId(), sourceList);
TimiException.requiredTrue(!stockItems.isEmpty(), "履约单没有需要占用的实物库存"); TimiException.requiredTrue(!stockItems.isEmpty(), "履约单没有需要占用的实物库存");
TimiException.requiredTrue(productStockService.hasSufficientForFulfillment(order.getStoreId(), stockItems), "库存仍然不足,不能准备履约"); TimiException.requiredTrue(productStockService.hasSufficientForFulfillment(order.getStoreId(), stockItems), "库存仍然不足,不能准备履约");
@@ -226,16 +222,17 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
private GaoOrderFulfillment verifyInternal(GaoOrderFulfillmentActionRequest request) { private GaoOrderFulfillment verifyInternal(GaoOrderFulfillmentActionRequest request) {
TimiException.required(request, "not found verify request"); TimiException.required(request, "not found verify request");
TimiException.required(request.getFulfillmentId(), "not found fulfillmentId"); TimiException.required(request.getFulfillmentId(), "not found fulfillmentId");
GaoOrderFulfillment fulfillment = mapper.selectForUpdate(request.getFulfillmentId()); GaoOrderFulfillment snapshot = mapper.selectById(request.getFulfillmentId());
TimiException.required(fulfillment, "not found fulfillment"); TimiException.required(snapshot, "not found fulfillment");
GaoSalesOrder order = salesOrderService.getForUpdate(snapshot.getSalesOrderId());
List<SourceEntry> sourceList = loadSources(order, itemRequests(snapshot.getId()), false);
GaoOrderFulfillment fulfillment = lockFulfillment(snapshot.getId(), order);
storeScopeService.checkStoreId(fulfillment.getStoreId()); storeScopeService.checkStoreId(fulfillment.getStoreId());
TimiException.requiredTrue(fulfillment.getStatus() == GaoOrderFulfillment.Status.PENDING, "履约单不是待核销状态"); TimiException.requiredTrue(fulfillment.getStatus() == GaoOrderFulfillment.Status.PENDING, "履约单不是待核销状态");
GaoSalesOrder order = salesOrderService.getForUpdate(fulfillment.getSalesOrderId());
TimiException.requiredTrue(order.getStatus() != GaoSalesOrder.Status.CANCELLED, "已取消订单不能核销履约"); TimiException.requiredTrue(order.getStatus() != GaoSalesOrder.Status.CANCELLED, "已取消订单不能核销履约");
salesOrderService.checkFulfillmentPermission(order); salesOrderService.checkFulfillmentPermission(order);
GaoCustomerPackage packageEntity = lockPackage(order, false);
List<GaoOrderFulfillmentItem> fulfillmentItemList = itemMapper.selectByFulfillmentIdForUpdate(fulfillment.getId()); List<GaoOrderFulfillmentItem> fulfillmentItemList = itemMapper.selectByFulfillmentIdForUpdate(fulfillment.getId());
List<SourceEntry> sourceList = loadSources(order, packageEntity, copyRequestItems(fulfillmentItemList), false); TimiException.requiredTrue(!fulfillmentItemList.isEmpty(), "履约单没有明细");
assertReserved(sourceList); assertReserved(sourceList);
for (SourceEntry source : sourceList) { for (SourceEntry source : sourceList) {
decreaseReserved(source); decreaseReserved(source);
@@ -249,7 +246,7 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
} }
TimiException.requiredTrue(1 == mapper.updateStatus(fulfillment.getId(), GaoOrderFulfillment.Status.VERIFIED, TimiException.requiredTrue(1 == mapper.updateStatus(fulfillment.getId(), GaoOrderFulfillment.Status.VERIFIED,
verifierUserId, Time.now(), request.getReason()), "履约状态已变化"); verifierUserId, Time.now(), request.getReason()), "履约状态已变化");
refreshAggregateStatus(order, packageEntity); salesOrderService.refreshFulfillmentStatus(order);
return detail(fulfillment.getId()); return detail(fulfillment.getId());
} }
@@ -267,7 +264,8 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
private GaoOrderFulfillment cancelInternal(GaoOrderFulfillmentActionRequest request) { private GaoOrderFulfillment cancelInternal(GaoOrderFulfillmentActionRequest request) {
TimiException.required(request, "not found cancel request"); TimiException.required(request, "not found cancel request");
GaoOrderFulfillment fulfillment = mapper.selectForUpdate(request.getFulfillmentId()); TimiException.required(request.getFulfillmentId(), "not found fulfillmentId");
GaoOrderFulfillment fulfillment = mapper.selectById(request.getFulfillmentId());
TimiException.required(fulfillment, "not found fulfillment"); TimiException.required(fulfillment, "not found fulfillment");
String loginUserId = userLoginService.getRequireLoginUserId(); String loginUserId = userLoginService.getRequireLoginUserId();
TimiException.requiredTrue(loginUserId.equals(fulfillment.getOperatorUserId()), "只能撤销自己提交的履约"); TimiException.requiredTrue(loginUserId.equals(fulfillment.getOperatorUserId()), "只能撤销自己提交的履约");
@@ -278,14 +276,16 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
TimiException.required(request, "not found action request"); TimiException.required(request, "not found action request");
TimiException.required(request.getFulfillmentId(), "not found fulfillmentId"); TimiException.required(request.getFulfillmentId(), "not found fulfillmentId");
String reason = requiredText(request.getReason(), "拒绝或撤销原因不能为空"); String reason = requiredText(request.getReason(), "拒绝或撤销原因不能为空");
GaoOrderFulfillment fulfillment = mapper.selectForUpdate(request.getFulfillmentId()); GaoOrderFulfillment snapshot = mapper.selectById(request.getFulfillmentId());
TimiException.required(fulfillment, "not found fulfillment"); TimiException.required(snapshot, "not found fulfillment");
GaoSalesOrder order = salesOrderService.getForUpdate(snapshot.getSalesOrderId());
List<SourceEntry> sourceList = loadSources(order, itemRequests(snapshot.getId()), false);
GaoOrderFulfillment fulfillment = lockFulfillment(snapshot.getId(), order);
storeScopeService.checkStoreId(fulfillment.getStoreId()); storeScopeService.checkStoreId(fulfillment.getStoreId());
TimiException.requiredTrue(fulfillment.getStatus() == GaoOrderFulfillment.Status.WAITING_STOCK TimiException.requiredTrue(fulfillment.getStatus() == GaoOrderFulfillment.Status.WAITING_STOCK
|| fulfillment.getStatus() == GaoOrderFulfillment.Status.PENDING, "履约单不是可释放状态"); || fulfillment.getStatus() == GaoOrderFulfillment.Status.PENDING, "履约单不是可释放状态");
GaoSalesOrder order = salesOrderService.getForUpdate(fulfillment.getSalesOrderId()); List<GaoOrderFulfillmentItem> fulfillmentItemList = itemMapper.selectByFulfillmentIdForUpdate(fulfillment.getId());
GaoCustomerPackage packageEntity = lockPackage(order, false); TimiException.requiredTrue(!fulfillmentItemList.isEmpty(), "履约单没有明细");
List<SourceEntry> sourceList = loadSources(order, packageEntity, copyRequestItems(itemMapper.selectByFulfillmentIdForUpdate(fulfillment.getId())), false);
if (fulfillment.getStatus() == GaoOrderFulfillment.Status.PENDING) { if (fulfillment.getStatus() == GaoOrderFulfillment.Status.PENDING) {
List<GaoStockChangeItem> stockItems = buildStockItems(order.getStoreId(), sourceList); List<GaoStockChangeItem> stockItems = buildStockItems(order.getStoreId(), sourceList);
if (!stockItems.isEmpty()) { if (!stockItems.isEmpty()) {
@@ -312,37 +312,43 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
String requestNo = requiredText(request.getRequestNo(), "not found requestNo"); String requestNo = requiredText(request.getRequestNo(), "not found requestNo");
TimiException.required(request.getFulfillmentId(), "not found fulfillmentId"); TimiException.required(request.getFulfillmentId(), "not found fulfillmentId");
String reason = requiredText(request.getReason(), "冲正原因不能为空"); String reason = requiredText(request.getReason(), "冲正原因不能为空");
GaoOrderFulfillment source = mapper.selectForUpdate(request.getFulfillmentId()); GaoOrderFulfillment sourceSnapshot = mapper.selectById(request.getFulfillmentId());
TimiException.required(source, "not found fulfillment"); TimiException.required(sourceSnapshot, "not found fulfillment");
storeScopeService.checkStoreId(source.getStoreId()); GaoOrderFulfillment existing = mapper.selectByRequestNo(sourceSnapshot.getStoreId(), requestNo);
GaoOrderFulfillment existing = mapper.selectByRequestNo(source.getStoreId(), requestNo);
if (existing != null) { if (existing != null) {
TimiException.requiredTrue(source.getId().equals(existing.getSourceFulfillmentId()), "requestNo 已被其他履约操作使用"); TimiException.requiredTrue(sourceSnapshot.getId().equals(existing.getSourceFulfillmentId()), "requestNo 已被其他履约操作使用");
return detail(existing.getId()); return detail(existing.getId());
} }
GaoSalesOrder order = salesOrderService.getForUpdate(sourceSnapshot.getSalesOrderId());
List<GaoOrderFulfillmentItem> sourceItemList = itemMapper.selectByFulfillmentId(sourceSnapshot.getId());
List<SourceEntry> 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.requiredTrue(source.getStatus() == GaoOrderFulfillment.Status.VERIFIED && source.getSourceFulfillmentId() == null, "只有原始已核销履约可以冲正");
TimiException.requiredNull(mapper.selectBySourceFulfillmentId(source.getId()), "该履约单已经冲正"); TimiException.requiredNull(mapper.selectBySourceFulfillmentId(source.getId()), "该履约单已经冲正");
GaoSalesOrder order = salesOrderService.getForUpdate(source.getSalesOrderId()); sourceItemList = itemMapper.selectByFulfillmentIdForUpdate(source.getId());
GaoCustomerPackage packageEntity = lockPackage(order, false); TimiException.requiredTrue(!sourceItemList.isEmpty(), "履约单没有明细");
List<GaoOrderFulfillmentItem> sourceItemList = itemMapper.selectByFulfillmentIdForUpdate(source.getId());
List<SourceEntry> sourceList = loadSources(order, packageEntity, copyRequestItems(sourceItemList), false);
List<GaoStockChangeItem> stockItems = buildStockItems(order.getStoreId(), sourceList); List<GaoStockChangeItem> stockItems = buildStockItems(order.getStoreId(), sourceList);
if (!stockItems.isEmpty()) { if (!stockItems.isEmpty()) {
TimiException.requiredTrue(Boolean.TRUE.equals(request.getReturnedToStore()), "实物履约冲正必须确认已退回门店"); TimiException.requiredTrue(Boolean.TRUE.equals(request.getReturnedToStore()), "实物履约冲正必须确认已退回门店");
TimiException.requiredTrue(hasAttachment(request.getAttachmentList()), "实物履约冲正必须上传退回照片"); TimiException.requiredTrue(hasAttachment(request.getAttachmentList()), "实物履约冲正必须上传退回照片");
} }
String operatorUserId = userLoginService.getRequireLoginUserId(); 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()); requestNo, operatorUserId, TimiJava.defaultIfNull(request.getDeliveredAt(), Time.now()), request.getRemark());
reverse.setVerifierUserId(operatorUserId); reverse.setVerifierUserId(operatorUserId);
reverse.setVerifiedAt(Time.now()); reverse.setVerifiedAt(Time.now());
reverse.setSourceFulfillmentId(source.getId()); reverse.setSourceFulfillmentId(source.getId());
reverse.setReason(reason); reverse.setReason(reason);
super.create(reverse); super.create(reverse);
for (int i = 0; i < sourceList.size(); i++) { Map<String, GaoOrderFulfillmentItem> sourceItemMap = new HashMap<>();
SourceEntry sourceEntry = sourceList.get(i); for (GaoOrderFulfillmentItem sourceItem : sourceItemList) {
sourceItemMap.put(sourceItem.getSourceOrderItemId(), sourceItem);
}
for (SourceEntry sourceEntry : sourceList) {
decreaseFulfilled(sourceEntry); decreaseFulfilled(sourceEntry);
GaoOrderFulfillmentItem sourceItem = sourceItemList.get(i); GaoOrderFulfillmentItem sourceItem = sourceItemMap.get(sourceEntry.sourceId());
TimiException.required(sourceItem, "履约明细来源订单项不存在");
GaoOrderFulfillmentItem reverseItem = copyFulfillmentItem(sourceItem); GaoOrderFulfillmentItem reverseItem = copyFulfillmentItem(sourceItem);
reverseItem.setFulfillmentId(reverse.getId()); reverseItem.setFulfillmentId(reverse.getId());
reverseItem.setReservedBefore(sourceEntry.reservedBefore()); reverseItem.setReservedBefore(sourceEntry.reservedBefore());
@@ -365,17 +371,16 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
TimiException.requiredTrue(1 == mapper.updateStatus(source.getId(), GaoOrderFulfillment.Status.REVOKED, TimiException.requiredTrue(1 == mapper.updateStatus(source.getId(), GaoOrderFulfillment.Status.REVOKED,
operatorUserId, Time.now(), reason), "原履约状态已变化"); operatorUserId, Time.now(), reason), "原履约状态已变化");
attachmentService.updateByBizId(Attachment.BizType.GAO_ORDER_FULFILLMENT, reverse.getId(), request.getAttachmentList()); attachmentService.updateByBizId(Attachment.BizType.GAO_ORDER_FULFILLMENT, reverse.getId(), request.getAttachmentList());
refreshAggregateStatus(order, packageEntity); salesOrderService.refreshFulfillmentStatus(order);
return detail(reverse.getId()); return detail(reverse.getId());
} }
private GaoOrderFulfillment newFulfillment(GaoSalesOrder order, GaoOrderFulfillment.FulfillmentType type, private GaoOrderFulfillment newFulfillment(GaoSalesOrder order, GaoOrderFulfillment.Status status,
GaoOrderFulfillment.Status status, String requestNo, String operatorUserId, Long deliveredAt, String remark) { String requestNo, String operatorUserId, Long deliveredAt, String remark) {
GaoOrderFulfillment result = new GaoOrderFulfillment(); GaoOrderFulfillment result = new GaoOrderFulfillment();
result.setSalesOrderId(order.getId()); result.setSalesOrderId(order.getId());
result.setStoreId(order.getStoreId()); result.setStoreId(order.getStoreId());
result.setCustomerId(order.getCustomerId()); result.setCustomerId(order.getCustomerId());
result.setFulfillmentType(type);
result.setStatus(status); result.setStatus(status);
result.setRequestNo(requestNo); result.setRequestNo(requestNo);
result.setOperatorUserId(operatorUserId); result.setOperatorUserId(operatorUserId);
@@ -386,40 +391,24 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
private void insertSourceReservations(GaoOrderFulfillment fulfillment, List<SourceEntry> sourceList) { private void insertSourceReservations(GaoOrderFulfillment fulfillment, List<SourceEntry> sourceList) {
for (SourceEntry source : 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()); GaoOrderFulfillmentItem item = source.toFulfillmentItem(fulfillment.getId());
itemMapper.insert(item); itemMapper.insert(item);
} }
} }
private void decreaseReserved(SourceEntry source) { 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) { 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) { private void decreaseFulfilled(SourceEntry source) {
TimiException.requiredTrue(0 <= source.fulfilledBefore().compareTo(source.quantity()), "已履约数量不足以冲正"); 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<SourceEntry> sourceList) { private void assertReserved(List<SourceEntry> sourceList) {
for (SourceEntry source : sourceList) { for (SourceEntry source : sourceList) {
@@ -427,67 +416,48 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
} }
} }
private List<SourceEntry> loadSources(GaoSalesOrder order, GaoCustomerPackage packageEntity, private List<SourceEntry> loadSources(GaoSalesOrder order, List<GaoOrderFulfillmentItemRequest> requestItems, boolean checkAvailable) {
List<GaoOrderFulfillmentItemRequest> requestItems, boolean checkAvailable) {
TimiException.requiredTrue(requestItems != null && !requestItems.isEmpty(), "履约至少需要一个产品"); TimiException.requiredTrue(requestItems != null && !requestItems.isEmpty(), "履约至少需要一个产品");
List<GaoOrderFulfillmentItemRequest> sortedRequestItems = new ArrayList<>(requestItems);
Set<String> sourceIdSet = new HashSet<>(); Set<String> sourceIdSet = new HashSet<>();
List<SourceEntry> result = new ArrayList<>(); for (GaoOrderFulfillmentItemRequest requestItem : sortedRequestItems) {
for (GaoOrderFulfillmentItemRequest requestItem : requestItems) {
TimiException.required(requestItem, "履约明细不能为空"); TimiException.required(requestItem, "履约明细不能为空");
boolean hasOrderSource = hasText(requestItem.getSourceOrderItemId()); String sourceId = requiredText(requestItem.getSourceOrderItemId(), "履约明细来源订单项不能为空");
boolean hasPackageSource = hasText(requestItem.getSourceCustomerPackageItemId()); requestItem.setSourceOrderItemId(sourceId);
TimiException.requiredTrue(hasOrderSource ^ hasPackageSource, "履约明细来源必须二选一");
String sourceId = hasOrderSource ? requestItem.getSourceOrderItemId() : requestItem.getSourceCustomerPackageItemId();
TimiException.requiredTrue(sourceIdSet.add(sourceId), "同一履约单不能重复来源明细"); TimiException.requiredTrue(sourceIdSet.add(sourceId), "同一履约单不能重复来源明细");
TimiException.required(requestItem.getQuantity(), "履约数量不能为空"); TimiException.required(requestItem.getQuantity(), "履约数量不能为空");
TimiException.requiredTrue(0 < requestItem.getQuantity().signum(), "履约数量必须大于 0"); TimiException.requiredTrue(0 < requestItem.getQuantity().signum(), "履约数量必须大于 0");
TimiException.requiredTrue(3 >= requestItem.getQuantity().stripTrailingZeros().scale(), "履约数量最多保留三位小数"); TimiException.requiredTrue(3 >= requestItem.getQuantity().stripTrailingZeros().scale(), "履约数量最多保留三位小数");
if (order.getOrderType() == GaoSalesOrder.OrderType.DIRECT_PRODUCT) { }
TimiException.requiredTrue(hasOrderSource && !hasPackageSource, "直接产品订单只能引用订单明细"); sortedRequestItems.sort(Comparator.comparing(GaoOrderFulfillmentItemRequest::getSourceOrderItemId));
GaoSalesOrderItem source = salesOrderService.getItemForUpdate(sourceId); List<SourceEntry> result = new ArrayList<>();
for (GaoOrderFulfillmentItemRequest requestItem : sortedRequestItems) {
GaoSalesOrderItem source = salesOrderService.getItemForUpdate(requestItem.getSourceOrderItemId());
TimiException.required(source, "not found sales order item"); TimiException.required(source, "not found sales order item");
TimiException.requiredTrue(order.getId().equals(source.getSalesOrderId()), "订单明细不属于当前订单"); TimiException.requiredTrue(order.getId().equals(source.getSalesOrderId()), "订单明细不属于当前订单");
result.add(toSource(order, source, requestItem.getQuantity(), false, checkAvailable)); GaoSalesOrderItem.ItemType expectedItemType = order.getOrderType() == GaoSalesOrder.OrderType.PACKAGE
} else { ? GaoSalesOrderItem.ItemType.PACKAGE_ENTITLEMENT : GaoSalesOrderItem.ItemType.DIRECT_PRODUCT;
TimiException.requiredTrue(hasPackageSource && !hasOrderSource, "套餐订单只能引用客户套餐权益"); TimiException.requiredTrue(source.getItemType() == expectedItemType, "订单项类型与订单类型不匹配");
TimiException.required(packageEntity, "订单未找到客户套餐权益"); result.add(toSource(order, source, requestItem.getQuantity(), checkAvailable));
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));
}
} }
return result; 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()); GaoProduct product = productService.get(source.getProductId());
TimiException.required(product, "履约来源产品不存在"); TimiException.required(product, "履约来源产品不存在");
TimiException.requiredTrue(order.getStoreId().equals(product.getStoreId()), "履约来源产品不属于当前门店"); TimiException.requiredTrue(order.getStoreId().equals(product.getStoreId()), "履约来源产品不属于当前门店");
BigDecimal baseQuantityPerUnit = requiredBaseQuantityPerUnit(source.getBaseQuantityPerUnitSnapshot()); BigDecimal baseQuantityPerUnit = requiredBaseQuantityPerUnit(source.getBaseQuantityPerUnitSnapshot());
GaoProductUnit unit = snapshotUnit(source.getProductUnitId(), source.getUnitSnapshot(), baseQuantityPerUnit); 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(), 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) { private SourceEntry buildSource(GaoSalesOrder order, String orderItemId, String productId, String unitId,
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,
String productName, String unitName, BigDecimal baseQuantityPerUnit, BigDecimal quantity, BigDecimal totalQuantity, String productName, String unitName, BigDecimal baseQuantityPerUnit, BigDecimal quantity, BigDecimal totalQuantity,
BigDecimal reservedQuantity, BigDecimal fulfilledQuantity, GaoProduct product, GaoProductUnit unit, BigDecimal reservedQuantity, BigDecimal fulfilledQuantity, GaoProduct product, GaoProductUnit unit,
boolean packageSource, boolean checkAvailable) { boolean checkAvailable) {
if (!Boolean.TRUE.equals(product.getAllowDecimalQuantity())) { if (!Boolean.TRUE.equals(product.getAllowDecimalQuantity())) {
TimiException.requiredTrue(quantity.stripTrailingZeros().scale() <= 0, "该产品不允许小数数量"); TimiException.requiredTrue(quantity.stripTrailingZeros().scale() <= 0, "该产品不允许小数数量");
} }
@@ -497,8 +467,8 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
BigDecimal available = decimal(totalQuantity).subtract(reservedBefore).subtract(fulfilledBefore); BigDecimal available = decimal(totalQuantity).subtract(reservedBefore).subtract(fulfilledBefore);
TimiException.requiredTrue(0 <= available.compareTo(quantity), "来源产品可履约数量不足"); TimiException.requiredTrue(0 <= available.compareTo(quantity), "来源产品可履约数量不足");
} }
return new SourceEntry(orderItemId, packageItemId, productId, unitId, productName, unitName, baseQuantityPerUnit, return new SourceEntry(orderItemId, productId, unitId, productName, unitName, baseQuantityPerUnit,
quantity, decimal(totalQuantity), reservedBefore, fulfilledBefore, product, unit, packageSource); quantity, decimal(totalQuantity), reservedBefore, fulfilledBefore, product, unit);
} }
private List<GaoStockChangeItem> buildStockItems(String storeId, List<SourceEntry> sourceList) { private List<GaoStockChangeItem> buildStockItems(String storeId, List<SourceEntry> sourceList) {
@@ -523,51 +493,15 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
return itemMap.values().stream().sorted(Comparator.comparing(GaoStockChangeItem::getProductId)).toList(); return itemMap.values().stream().sorted(Comparator.comparing(GaoStockChangeItem::getProductId)).toList();
} }
private GaoCustomerPackage lockPackage(GaoSalesOrder order, boolean requireActive) { private GaoOrderFulfillment lockFulfillment(String fulfillmentId, GaoSalesOrder order) {
if (order.getOrderType() != GaoSalesOrder.OrderType.PACKAGE) { GaoOrderFulfillment result = mapper.selectForUpdate(fulfillmentId);
return null; TimiException.required(result, "not found fulfillment");
} TimiException.requiredTrue(order.getId().equals(result.getSalesOrderId()), "履约单不属于当前订单");
GaoCustomerPackage source = customerPackageService.getBySalesOrderId(order.getId());
TimiException.required(source, "订单未找到客户套餐权益");
GaoCustomerPackage result = customerPackageService.getForUpdate(source.getId());
TimiException.required(result, "not found customer package");
if (requireActive) {
TimiException.requiredTrue(result.getStatus() == GaoCustomerPackage.Status.ACTIVE, "当前套餐不可履约");
} else {
TimiException.requiredTrue(result.getStatus() != GaoCustomerPackage.Status.CANCELLED, "已取消套餐不能履约");
}
return result; return result;
} }
private BigDecimal fulfilledQuantity(GaoCustomerPackageItem item) { private List<GaoOrderFulfillmentItemRequest> itemRequests(String fulfillmentId) {
return item.getFulfilledQuantity(); return copyRequestItems(itemMapper.selectByFulfillmentId(fulfillmentId));
}
private void refreshAggregateStatus(GaoSalesOrder order, GaoCustomerPackage packageEntity) {
if (packageEntity != null) {
List<GaoCustomerPackageItem> 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<GaoOrderFulfillmentItemRequest> requestItems(List<GaoOrderFulfillmentItemRequest> itemList) {
return itemList;
} }
private List<GaoOrderFulfillmentItemRequest> copyRequestItems(List<GaoOrderFulfillmentItem> itemList) { private List<GaoOrderFulfillmentItemRequest> copyRequestItems(List<GaoOrderFulfillmentItem> itemList) {
@@ -575,7 +509,6 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
for (GaoOrderFulfillmentItem item : TimiJava.safeIterable(itemList)) { for (GaoOrderFulfillmentItem item : TimiJava.safeIterable(itemList)) {
GaoOrderFulfillmentItemRequest request = new GaoOrderFulfillmentItemRequest(); GaoOrderFulfillmentItemRequest request = new GaoOrderFulfillmentItemRequest();
request.setSourceOrderItemId(item.getSourceOrderItemId()); request.setSourceOrderItemId(item.getSourceOrderItemId());
request.setSourceCustomerPackageItemId(item.getSourceCustomerPackageItemId());
request.setQuantity(item.getQuantity()); request.setQuantity(item.getQuantity());
result.add(request); result.add(request);
} }
@@ -587,7 +520,6 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
result.setProductId(source.getProductId()); result.setProductId(source.getProductId());
result.setProductUnitId(source.getProductUnitId()); result.setProductUnitId(source.getProductUnitId());
result.setSourceOrderItemId(source.getSourceOrderItemId()); result.setSourceOrderItemId(source.getSourceOrderItemId());
result.setSourceCustomerPackageItemId(source.getSourceCustomerPackageItemId());
result.setQuantity(source.getQuantity()); result.setQuantity(source.getQuantity());
result.setBaseQuantity(source.getBaseQuantity()); result.setBaseQuantity(source.getBaseQuantity());
result.setProductNameSnapshot(source.getProductNameSnapshot()); result.setProductNameSnapshot(source.getProductNameSnapshot());
@@ -615,16 +547,14 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
&& (request.getDeliveredAt() == null || request.getDeliveredAt().equals(existing.getDeliveredAt())) && (request.getDeliveredAt() == null || request.getDeliveredAt().equals(existing.getDeliveredAt()))
&& sameText(existing.getRemark(), request.getRemark()), "requestNo 对应履约请求内容不一致"); && sameText(existing.getRemark(), request.getRemark()), "requestNo 对应履约请求内容不一致");
List<GaoOrderFulfillmentItem> existingItems = itemMapper.selectByFulfillmentId(existing.getId()); List<GaoOrderFulfillmentItem> existingItems = itemMapper.selectByFulfillmentId(existing.getId());
List<GaoOrderFulfillmentItemRequest> requestItems = requestItems(request.getItemList()); List<GaoOrderFulfillmentItemRequest> requestItems = request.getItemList();
TimiException.requiredTrue(existingItems.size() == (requestItems == null ? 0 : requestItems.size()), "requestNo 对应履约请求内容不一致"); TimiException.requiredTrue(existingItems.size() == (requestItems == null ? 0 : requestItems.size()), "requestNo 对应履约请求内容不一致");
Map<String, BigDecimal> requestMap = new HashMap<>(); Map<String, BigDecimal> requestMap = new HashMap<>();
for (GaoOrderFulfillmentItemRequest item : TimiJava.safeIterable(requestItems)) { for (GaoOrderFulfillmentItemRequest item : TimiJava.safeIterable(requestItems)) {
String key = hasText(item.getSourceOrderItemId()) ? "O:" + item.getSourceOrderItemId() : "P:" + item.getSourceCustomerPackageItemId(); requestMap.put(item.getSourceOrderItemId(), item.getQuantity());
requestMap.put(key, item.getQuantity());
} }
for (GaoOrderFulfillmentItem item : existingItems) { for (GaoOrderFulfillmentItem item : existingItems) {
String key = item.getSourceOrderItemId() == null ? "P:" + item.getSourceCustomerPackageItemId() : "O:" + item.getSourceOrderItemId(); TimiException.requiredTrue(item.getQuantity().compareTo(requestMap.getOrDefault(item.getSourceOrderItemId(), ZERO)) == 0, "requestNo 对应履约请求内容不一致");
TimiException.requiredTrue(item.getQuantity().compareTo(requestMap.getOrDefault(key, ZERO)) == 0, "requestNo 对应履约请求内容不一致");
} }
} }
@@ -641,10 +571,6 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
return false; return false;
} }
private boolean hasText(String value) {
return value != null && !value.trim().isEmpty();
}
private String requiredText(String value, String message) { private String requiredText(String value, String message) {
TimiException.required(value, message); TimiException.required(value, message);
String result = value.trim(); String result = value.trim();
@@ -710,13 +636,13 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
} }
} }
private record SourceEntry(String sourceOrderItemId, String sourceCustomerPackageItemId, String productId, String productUnitId, private record SourceEntry(String sourceOrderItemId, String productId, String productUnitId,
String productNameSnapshot, String unitSnapshot, BigDecimal baseQuantityPerUnitSnapshot, BigDecimal quantity, String productNameSnapshot, String unitSnapshot, BigDecimal baseQuantityPerUnitSnapshot, BigDecimal quantity,
BigDecimal totalQuantity, BigDecimal reservedBefore, BigDecimal fulfilledBefore, GaoProduct product, BigDecimal totalQuantity, BigDecimal reservedBefore, BigDecimal fulfilledBefore, GaoProduct product,
GaoProductUnit unit, boolean packageSource) { GaoProductUnit unit) {
private String sourceId() { private String sourceId() {
return packageSource ? sourceCustomerPackageItemId : sourceOrderItemId; return sourceOrderItemId;
} }
private GaoOrderFulfillmentItem toFulfillmentItem(String fulfillmentId) { private GaoOrderFulfillmentItem toFulfillmentItem(String fulfillmentId) {
@@ -725,7 +651,6 @@ public class GaoOrderFulfillmentServiceImplement extends AbstractEntityService<G
result.setProductId(productId); result.setProductId(productId);
result.setProductUnitId(productUnitId); result.setProductUnitId(productUnitId);
result.setSourceOrderItemId(sourceOrderItemId); result.setSourceOrderItemId(sourceOrderItemId);
result.setSourceCustomerPackageItemId(sourceCustomerPackageItemId);
result.setQuantity(quantity); result.setQuantity(quantity);
result.setBaseQuantity(quantity.multiply(baseQuantityPerUnitSnapshot).setScale(3)); result.setBaseQuantity(quantity.multiply(baseQuantityPerUnitSnapshot).setScale(3));
result.setReservedBefore(reservedBefore); result.setReservedBefore(reservedBefore);
@@ -7,7 +7,6 @@ import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
import com.imyeyu.api.modules.gao.bean.GaoRoleCode; import com.imyeyu.api.modules.gao.bean.GaoRoleCode;
import com.imyeyu.api.modules.gao.entity.GaoCustomer; import com.imyeyu.api.modules.gao.entity.GaoCustomer;
import com.imyeyu.api.modules.gao.entity.GaoCustomerPackage;
import com.imyeyu.api.modules.gao.entity.GaoEventRecord; import com.imyeyu.api.modules.gao.entity.GaoEventRecord;
import com.imyeyu.api.modules.gao.entity.GaoFundRecord; import com.imyeyu.api.modules.gao.entity.GaoFundRecord;
import com.imyeyu.api.modules.gao.entity.GaoProduct; import com.imyeyu.api.modules.gao.entity.GaoProduct;
@@ -18,7 +17,6 @@ import com.imyeyu.api.modules.gao.entity.GaoStore;
import com.imyeyu.api.modules.gao.mapper.GaoSalesOrderItemMapper; import com.imyeyu.api.modules.gao.mapper.GaoSalesOrderItemMapper;
import com.imyeyu.api.modules.gao.mapper.GaoSalesOrderMapper; import com.imyeyu.api.modules.gao.mapper.GaoSalesOrderMapper;
import com.imyeyu.api.modules.gao.service.GaoCustomerService; import com.imyeyu.api.modules.gao.service.GaoCustomerService;
import com.imyeyu.api.modules.gao.service.GaoCustomerPackageService;
import com.imyeyu.api.modules.gao.service.GaoEventRecordService; import com.imyeyu.api.modules.gao.service.GaoEventRecordService;
import com.imyeyu.api.modules.gao.service.GaoFundRecordService; import com.imyeyu.api.modules.gao.service.GaoFundRecordService;
import com.imyeyu.api.modules.gao.service.GaoOrderFulfillmentService; import com.imyeyu.api.modules.gao.service.GaoOrderFulfillmentService;
@@ -55,6 +53,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@@ -77,7 +76,6 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
private final GaoSalesOrderItemMapper itemMapper; private final GaoSalesOrderItemMapper itemMapper;
private final GaoFundRecordService fundRecordService; private final GaoFundRecordService fundRecordService;
private final ObjectProvider<GaoOrderFulfillmentService> fulfillmentServiceProvider; private final ObjectProvider<GaoOrderFulfillmentService> fulfillmentServiceProvider;
private final ObjectProvider<GaoCustomerPackageService> customerPackageServiceProvider;
private final GaoCustomerService customerService; private final GaoCustomerService customerService;
private final GaoProductService productService; private final GaoProductService productService;
private final GaoProductUnitService unitService; private final GaoProductUnitService unitService;
@@ -114,6 +112,9 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
TimiException.required(order, "not found sales order"); TimiException.required(order, "not found sales order");
storeScopeService.checkStoreId(order.getStoreId()); storeScopeService.checkStoreId(order.getStoreId());
List<GaoSalesOrderItem> itemList = itemMapper.selectBySalesOrderId(order.getId()); List<GaoSalesOrderItem> itemList = itemMapper.selectBySalesOrderId(order.getId());
for (GaoSalesOrderItem item : itemList) {
fillItemBalance(item);
}
fillItemAttachments(itemList); fillItemAttachments(itemList);
order.setItemList(itemList); order.setItemList(itemList);
fillCustomer(List.of(order)); fillCustomer(List.of(order));
@@ -177,26 +178,9 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
} }
Long purchasedAt = TimiJava.defaultIfNull(request.getPurchasedAt(), Time.now()); Long purchasedAt = TimiJava.defaultIfNull(request.getPurchasedAt(), Time.now());
String operatorUserId = userLoginService.getRequireLoginUserId(); String operatorUserId = userLoginService.getRequireLoginUserId();
GaoSalesOrder order = newOrder(storeId, customer.getId(), GaoSalesOrder.OrderType.DIRECT_PRODUCT, requestNo, purchasedAt, operatorUserId, request.getRemark()); GaoSalesOrder order = createOrder(storeId, customer.getId(), GaoSalesOrder.OrderType.DIRECT_PRODUCT, requestNo,
order.setSaleAmount(saleAmount); purchasedAt, operatorUserId, request.getRemark(), saleAmount, itemList);
// 零元订单创建时即为全额实收
boolean paidAtCreate = saleAmount == 0;
if (paidAtCreate) {
order.setPaymentStatus(GaoSalesOrder.PaymentStatus.PAID);
order.setPaidAt(Time.now());
} else {
order.setPaymentStatus(GaoSalesOrder.PaymentStatus.UNPAID);
}
super.create(order);
for (GaoSalesOrderItem item : itemList) {
item.setSalesOrderId(order.getId());
itemMapper.insert(item);
}
attachmentService.updateByBizId(Attachment.BizType.GAO_SALES_ORDER, order.getId(), request.getAttachmentList()); attachmentService.updateByBizId(Attachment.BizType.GAO_SALES_ORDER, order.getId(), request.getAttachmentList());
order.setItemList(itemList);
if (paidAtCreate) {
pointTriggerService.onSalesOrderPaid(order);
}
return order; return order;
} }
@@ -213,35 +197,25 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
TimiException.required(data, "not found package order data"); TimiException.required(data, "not found package order data");
String requestNo = requiredText(data.getRequestNo(), "not found requestNo"); String requestNo = requiredText(data.getRequestNo(), "not found requestNo");
String storeId = requiredText(data.getStoreId(), "not found storeId"); String storeId = requiredText(data.getStoreId(), "not found storeId");
TimiException.required(data.getCustomerId(), "not found customerId");
TimiException.required(data.getPackageConfigId(), "not found packageConfigId");
TimiException.required(data.getSaleAmount(), "not found saleAmount");
storeScopeService.checkStoreId(storeId); storeScopeService.checkStoreId(storeId);
GaoSalesOrder existing = mapper.selectByRequestNo(storeId, requestNo); GaoSalesOrder existing = mapper.selectByRequestNo(storeId, requestNo);
if (existing != null) { if (existing != null) {
TimiException.requiredTrue(existing.getOrderType() == GaoSalesOrder.OrderType.PACKAGE TimiException.requiredTrue(existing.getOrderType() == GaoSalesOrder.OrderType.PACKAGE
&& data.getCustomerId().equals(existing.getCustomerId()) && data.getCustomerId().equals(existing.getCustomerId())
&& data.getSaleAmount().equals(existing.getSaleAmount()), "requestNo 已被其他订单使用"); && data.getSaleAmount().equals(existing.getSaleAmount()), "requestNo 已被其他订单使用");
return existing; verifyExistingPackageItems(existing, data.getItemList());
return detail(existing.getId());
} }
GaoCustomer customer = customerService.get(data.getCustomerId()); GaoCustomer customer = customerService.get(data.getCustomerId());
TimiException.required(customer, "not found customer"); TimiException.required(customer, "not found customer");
TimiException.requiredTrue(storeId.equals(customer.getStoreId()), "客户不属于当前门店"); TimiException.requiredTrue(storeId.equals(customer.getStoreId()), "客户不属于当前门店");
TimiException.required(data.getPackageConfigId(), "not found packageConfigId");
TimiException.requiredTrue(0 <= data.getSaleAmount(), "成交价不能为负数"); TimiException.requiredTrue(0 <= data.getSaleAmount(), "成交价不能为负数");
GaoSalesOrder order = newOrder(storeId, customer.getId(), GaoSalesOrder.OrderType.PACKAGE, requestNo, return createOrder(storeId, customer.getId(), GaoSalesOrder.OrderType.PACKAGE, requestNo,
TimiJava.defaultIfNull(data.getPurchasedAt(), Time.now()), data.getOperatorUserId(), data.getRemark()); TimiJava.defaultIfNull(data.getPurchasedAt(), Time.now()), data.getOperatorUserId(), data.getRemark(),
order.setSaleAmount(data.getSaleAmount()); data.getSaleAmount(), data.getItemList());
// 零元订单创建时即为全额实收
boolean paidAtCreate = data.getSaleAmount() == 0;
if (paidAtCreate) {
order.setPaymentStatus(GaoSalesOrder.PaymentStatus.PAID);
order.setPaidAt(Time.now());
} else {
order.setPaymentStatus(GaoSalesOrder.PaymentStatus.UNPAID);
}
super.create(order);
if (paidAtCreate) {
pointTriggerService.onSalesOrderPaid(order);
}
return order;
} }
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@@ -265,26 +239,16 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
TimiException.requiredTrue(fulfillmentServiceProvider.getObject().countPendingBySalesOrderId(order.getId()) == 0, "订单存在待履约操作,不能取消"); TimiException.requiredTrue(fulfillmentServiceProvider.getObject().countPendingBySalesOrderId(order.getId()) == 0, "订单存在待履约操作,不能取消");
String userId = userLoginService.getRequireLoginUserId(); String userId = userLoginService.getRequireLoginUserId();
GaoSalesOrder.RefundStatus refundStatus = safe(order.getPaidAmount()) == 0 ? GaoSalesOrder.RefundStatus.REFUNDED : GaoSalesOrder.RefundStatus.PENDING; GaoSalesOrder.RefundStatus refundStatus = safe(order.getPaidAmount()) == 0 ? GaoSalesOrder.RefundStatus.REFUNDED : GaoSalesOrder.RefundStatus.PENDING;
Long cancelledAt = Time.now();
TimiException.requiredTrue(1 == mapper.cancelIf(order.getId(), GaoSalesOrder.Status.ACTIVE, GaoSalesOrder.Status.CANCELLED, TimiException.requiredTrue(1 == mapper.cancelIf(order.getId(), GaoSalesOrder.Status.ACTIVE, GaoSalesOrder.Status.CANCELLED,
Time.now(), userId, reason, refundStatus), "订单状态已变化"); cancelledAt, userId, reason, refundStatus), "订单状态已变化");
// 订单取消立即失去有效消费资格,不等待退款完成 // 订单取消立即失去有效消费资格,不等待退款完成
pointTriggerService.revokeBySalesOrder(order, reason); pointTriggerService.revokeBySalesOrder(order, reason);
order.setStatus(GaoSalesOrder.Status.CANCELLED); order.setStatus(GaoSalesOrder.Status.CANCELLED);
order.setCancelledAt(Time.now()); order.setCancelledAt(cancelledAt);
order.setCancelOperatorUserId(userId); order.setCancelOperatorUserId(userId);
order.setCancelReason(reason); order.setCancelReason(reason);
order.setRefundStatus(refundStatus); order.setRefundStatus(refundStatus);
if (order.getOrderType() == GaoSalesOrder.OrderType.PACKAGE) {
GaoCustomerPackageService customerPackageService = customerPackageServiceProvider.getObject();
GaoCustomerPackage packageEntity = customerPackageService.getBySalesOrderId(order.getId());
if (packageEntity != null && packageEntity.getStatus() == GaoCustomerPackage.Status.ACTIVE) {
packageEntity.setStatus(GaoCustomerPackage.Status.CANCELLED);
packageEntity.setCancelledAt(order.getCancelledAt());
packageEntity.setCancelOperatorUserId(userId);
packageEntity.setCancelReason(reason);
customerPackageService.updateAggregateState(packageEntity);
}
}
return order; return order;
} }
@@ -342,16 +306,13 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
return; return;
} }
List<GaoSalesOrderItem> itemList = itemMapper.selectBySalesOrderId(order.getId()); List<GaoSalesOrderItem> itemList = itemMapper.selectBySalesOrderId(order.getId());
boolean fulfilled = false; boolean fulfilled = !itemList.isEmpty();
if (order.getOrderType() == GaoSalesOrder.OrderType.DIRECT_PRODUCT) {
fulfilled = !itemList.isEmpty();
for (GaoSalesOrderItem item : itemList) { for (GaoSalesOrderItem item : itemList) {
if (decimal(item.getFulfilledQuantity()).compareTo(decimal(item.getQuantity())) < 0) { if (decimal(item.getFulfilledQuantity()).compareTo(decimal(item.getQuantity())) < 0) {
fulfilled = false; fulfilled = false;
break; break;
} }
} }
}
updateFulfillmentStatus(order, fulfilled); updateFulfillmentStatus(order, fulfilled);
} }
@@ -374,6 +335,16 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
return itemMapper.selectBySalesOrderId(orderId); return itemMapper.selectBySalesOrderId(orderId);
} }
@Override
public Map<String, GaoSalesOrder> mapByIdList(Collection<String> idList) {
if (idList == null || idList.isEmpty()) {
return Map.of();
}
List<GaoSalesOrder> orderList = mapper.selectByIdList(idList);
fillItemList(orderList);
return orderList.stream().collect(java.util.stream.Collectors.toMap(GaoSalesOrder::getId, java.util.function.Function.identity()));
}
private List<GaoSalesOrderItem> resolveDirectItems(String storeId, List<GaoSalesOrderItemRequest> sourceList) { private List<GaoSalesOrderItem> resolveDirectItems(String storeId, List<GaoSalesOrderItemRequest> sourceList) {
TimiException.requiredTrue(sourceList != null && !sourceList.isEmpty(), "直接产品订单至少需要一个产品"); TimiException.requiredTrue(sourceList != null && !sourceList.isEmpty(), "直接产品订单至少需要一个产品");
Set<String> productUnitKeySet = new HashSet<>(); Set<String> productUnitKeySet = new HashSet<>();
@@ -401,6 +372,7 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
GaoSalesOrderItem item = new GaoSalesOrderItem(); GaoSalesOrderItem item = new GaoSalesOrderItem();
item.setProductId(product.getId()); item.setProductId(product.getId());
item.setProductUnitId(unit.getId()); item.setProductUnitId(unit.getId());
item.setItemType(GaoSalesOrderItem.ItemType.DIRECT_PRODUCT);
item.setProductNameSnapshot(product.getName()); item.setProductNameSnapshot(product.getName());
item.setUnitSnapshot(unit.getName()); item.setUnitSnapshot(unit.getName());
item.setBaseQuantityPerUnitSnapshot(unit.getBaseQuantityPerUnit()); item.setBaseQuantityPerUnitSnapshot(unit.getBaseQuantityPerUnit());
@@ -456,6 +428,83 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
return order; return order;
} }
private GaoSalesOrder createOrder(String storeId, String customerId, GaoSalesOrder.OrderType orderType, String requestNo,
Long purchasedAt, String operatorUserId, String remark, long saleAmount, List<GaoSalesOrderItem> 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<String> 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<GaoSalesOrderItem> requestItemList) {
TimiException.requiredTrue(requestItemList != null && !requestItemList.isEmpty(), "销售订单至少需要一个订单明细");
List<GaoSalesOrderItem> existingItemList = itemMapper.selectBySalesOrderId(existing.getId());
TimiException.requiredTrue(existingItemList.size() == requestItemList.size(), "requestNo 对应订单内容不一致");
Map<String, GaoSalesOrderItem> existingItemMap = existingItemList.stream()
.collect(java.util.stream.Collectors.toMap(item -> item.getProductId() + "|" + item.getProductUnitId(), java.util.function.Function.identity()));
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<GaoSalesOrder> orderList) { private void fillItemList(List<GaoSalesOrder> orderList) {
if (orderList == null || orderList.isEmpty()) { if (orderList == null || orderList.isEmpty()) {
return; return;
@@ -479,9 +528,24 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
itemList.addAll(orderItemList); itemList.addAll(orderItemList);
order.setUnpaidAmount(safe(order.getSaleAmount()) - safe(order.getPaidAmount())); order.setUnpaidAmount(safe(order.getSaleAmount()) - safe(order.getPaidAmount()));
} }
for (GaoSalesOrderItem item : itemList) {
fillItemBalance(item);
}
fillItemAttachments(itemList); fillItemAttachments(itemList);
} }
/// 填充订单项履约余额投影
///
/// @param item 订单项
private void fillItemBalance(GaoSalesOrderItem item) {
BigDecimal quantity = decimal(item.getQuantity());
BigDecimal reserved = decimal(item.getReservedQuantity());
BigDecimal fulfilled = decimal(item.getFulfilledQuantity());
item.setPendingQuantity(reserved);
item.setAvailableQuantity(quantity.subtract(reserved).subtract(fulfilled));
item.setRemainingQuantity(quantity.subtract(fulfilled));
}
private void fillItemAttachments(List<GaoSalesOrderItem> itemList) { private void fillItemAttachments(List<GaoSalesOrderItem> itemList) {
if (itemList == null || itemList.isEmpty()) { if (itemList == null || itemList.isEmpty()) {
return; return;
@@ -552,6 +616,10 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService<GaoSale
return result; return result;
} }
private boolean sameText(String left, String right) {
return left == null ? right == null : left.equals(right);
}
private long safe(Long value) { private long safe(Long value) {
return value == null ? 0L : value; return value == null ? 0L : value;
} }
@@ -1,6 +1,6 @@
package com.imyeyu.api.modules.gao.vo; package com.imyeyu.api.modules.gao.vo;
import com.imyeyu.api.modules.gao.entity.GaoCustomerPackage; import com.imyeyu.api.modules.gao.entity.GaoSalesOrder;
import com.imyeyu.java.bean.BasePage; import com.imyeyu.java.bean.BasePage;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@@ -22,7 +22,7 @@ public class GaoCustomerPackagePage extends BasePage {
/// 客户或套餐关键词 /// 客户或套餐关键词
private String keyword; private String keyword;
/// 套餐状态 /// 关联订单状态
private GaoCustomerPackage.Status status; private GaoSalesOrder.Status status;
} }
@@ -11,12 +11,9 @@ import java.math.BigDecimal;
@Data @Data
public class GaoOrderFulfillmentItemRequest { public class GaoOrderFulfillmentItemRequest {
/// 直接订单明细 ID,与客户套餐权益 ID 二选一 /// 来源销售订单明细 ID
private String sourceOrderItemId; private String sourceOrderItemId;
/// 客户套餐权益项 ID,与直接订单明细 ID 二选一
private String sourceCustomerPackageItemId;
/// 本次履约数量 /// 本次履约数量
private BigDecimal quantity; private BigDecimal quantity;
} }
@@ -22,9 +22,6 @@ public class GaoOrderFulfillmentPage extends BasePage {
/// 客户 ID /// 客户 ID
private String customerId; private String customerId;
/// 履约类型
private GaoOrderFulfillment.FulfillmentType fulfillmentType;
/// 履约状态 /// 履约状态
private GaoOrderFulfillment.Status status; private GaoOrderFulfillment.Status status;
@@ -1,7 +1,10 @@
package com.imyeyu.api.modules.gao.vo; package com.imyeyu.api.modules.gao.vo;
import com.imyeyu.api.modules.gao.entity.GaoSalesOrderItem;
import lombok.Data; import lombok.Data;
import java.util.List;
/// GAO 套餐订单内部创建数据 /// GAO 套餐订单内部创建数据
/// ///
/// @author Codex /// @author Codex
@@ -38,4 +41,7 @@ public class GaoPackageOrderCreateData {
/// 备注 /// 备注
private String remark; private String remark;
/// 订单可履约明细
private List<GaoSalesOrderItem> itemList;
} }
@@ -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');
@@ -1,39 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.imyeyu.api.modules.gao.mapper.GaoCustomerPackageItemMapper">
<select id="selectByCustomerPackageId" resultType="com.imyeyu.api.modules.gao.entity.GaoCustomerPackageItem">
SELECT * FROM `gao_customer_package_item`
WHERE `customer_package_id` = #{customerPackageId} AND `deleted_at` IS NULL
ORDER BY `sort`, `created_at`
</select>
<select id="selectByCustomerPackageIdList" resultType="com.imyeyu.api.modules.gao.entity.GaoCustomerPackageItem">
SELECT * FROM `gao_customer_package_item`
WHERE `deleted_at` IS NULL
<choose>
<when test="customerPackageIdList != null and !customerPackageIdList.isEmpty()">
AND `customer_package_id` IN <foreach collection="customerPackageIdList" item="id" open="(" separator="," close=")">#{id}</foreach>
</when>
<otherwise>AND FALSE</otherwise>
</choose>
ORDER BY `customer_package_id`, `sort`, `created_at`
</select>
<select id="selectForUpdate" resultType="com.imyeyu.api.modules.gao.entity.GaoCustomerPackageItem">
SELECT * FROM `gao_customer_package_item`
WHERE `id` = #{id} AND `deleted_at` IS NULL
FOR UPDATE
</select>
<update id="updateReservedQuantity">
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} &gt;= 0
AND `reserved_quantity` + `fulfilled_quantity` + #{delta} &lt;= `total_quantity`
</update>
<update id="updateFulfilledQuantity">
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} &gt;= 0
AND `reserved_quantity` + `fulfilled_quantity` + #{delta} &lt;= `total_quantity`
</update>
</mapper>
@@ -1,11 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.imyeyu.api.modules.gao.mapper.GaoCustomerPackageMapper"> <mapper namespace="com.imyeyu.api.modules.gao.mapper.GaoCustomerPackageMapper">
<select id="selectForUpdate" resultType="com.imyeyu.api.modules.gao.entity.GaoCustomerPackage">
SELECT * FROM `gao_customer_package`
WHERE `id` = #{id} AND `deleted_at` IS NULL
FOR UPDATE
</select>
<select id="selectBySalesOrderId" resultType="com.imyeyu.api.modules.gao.entity.GaoCustomerPackage"> <select id="selectBySalesOrderId" resultType="com.imyeyu.api.modules.gao.entity.GaoCustomerPackage">
SELECT * FROM `gao_customer_package` SELECT * FROM `gao_customer_package`
WHERE `sales_order_id` = #{salesOrderId} AND `deleted_at` IS NULL WHERE `sales_order_id` = #{salesOrderId} AND `deleted_at` IS NULL
@@ -15,18 +10,19 @@
p.`deleted_at` IS NULL p.`deleted_at` IS NULL
<if test="storeId != null and storeId != ''">AND p.`store_id` = #{storeId}</if> <if test="storeId != null and storeId != ''">AND p.`store_id` = #{storeId}</if>
<if test="customerId != null and customerId != ''">AND p.`customer_id` = #{customerId}</if> <if test="customerId != null and customerId != ''">AND p.`customer_id` = #{customerId}</if>
<if test="status != null">AND p.`status` = #{status}</if> <if test="status != null">AND o.`status` = #{status}</if>
<if test="keyword != null and keyword.trim() != ''">AND (p.`package_name_snapshot` LIKE CONCAT('%', #{keyword}, '%') OR c.`name` LIKE CONCAT('%', #{keyword}, '%') OR c.`code` LIKE CONCAT('%', #{keyword}, '%'))</if> <if test="keyword != null and keyword.trim() != ''">AND (p.`package_name_snapshot` LIKE CONCAT('%', #{keyword}, '%') OR c.`name` LIKE CONCAT('%', #{keyword}, '%') OR c.`code` LIKE CONCAT('%', #{keyword}, '%'))</if>
</sql> </sql>
<select id="countByQuery" resultType="long"> <select id="countByQuery" resultType="long">
SELECT COUNT(1) FROM `gao_customer_package` p SELECT COUNT(1) FROM `gao_customer_package` p
LEFT JOIN `gao_customer` c ON c.`id` = p.`customer_id` 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 <include refid="queryWhere"/> WHERE <include refid="queryWhere"/>
</select> </select>
<select id="selectByQuery" resultType="com.imyeyu.api.modules.gao.entity.GaoCustomerPackage"> <select id="selectByQuery" resultType="com.imyeyu.api.modules.gao.entity.GaoCustomerPackage">
SELECT p.* FROM `gao_customer_package` p SELECT p.* FROM `gao_customer_package` p
LEFT JOIN `gao_customer` c ON c.`id` = p.`customer_id` LEFT JOIN `gao_customer` c ON c.`id` = p.`customer_id`
LEFT 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 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 <include refid="queryWhere"/> WHERE <include refid="queryWhere"/>
ORDER BY o.`purchased_at` DESC, p.`created_at` DESC ORDER BY o.`purchased_at` DESC, p.`created_at` DESC
LIMIT #{offset}, #{limit} LIMIT #{offset}, #{limit}
@@ -34,7 +34,6 @@
<if test="storeId != null and storeId != ''">AND `store_id` = #{storeId}</if> <if test="storeId != null and storeId != ''">AND `store_id` = #{storeId}</if>
<if test="salesOrderId != null and salesOrderId != ''">AND `sales_order_id` = #{salesOrderId}</if> <if test="salesOrderId != null and salesOrderId != ''">AND `sales_order_id` = #{salesOrderId}</if>
<if test="customerId != null and customerId != ''">AND `customer_id` = #{customerId}</if> <if test="customerId != null and customerId != ''">AND `customer_id` = #{customerId}</if>
<if test="fulfillmentType != null">AND `fulfillment_type` = #{fulfillmentType}</if>
<if test="status != null">AND `status` = #{status}</if> <if test="status != null">AND `status` = #{status}</if>
<if test="beginAt != null">AND `delivered_at` &gt;= #{beginAt}</if> <if test="beginAt != null">AND `delivered_at` &gt;= #{beginAt}</if>
<if test="endAt != null">AND `delivered_at` &lt;= #{endAt}</if> <if test="endAt != null">AND `delivered_at` &lt;= #{endAt}</if>
@@ -11,6 +11,14 @@
WHERE `id` = #{id} AND `deleted_at` IS NULL WHERE `id` = #{id} AND `deleted_at` IS NULL
FOR UPDATE FOR UPDATE
</select> </select>
<select id="selectByIdList" resultType="com.imyeyu.api.modules.gao.entity.GaoSalesOrder">
SELECT * FROM `gao_sales_order`
WHERE `id` IN
<foreach collection="idList" item="id" separator="," open="(" close=")">
#{id}
</foreach>
AND `deleted_at` IS NULL
</select>
<sql id="queryWhere"> <sql id="queryWhere">
o.`deleted_at` IS NULL o.`deleted_at` IS NULL
<if test="storeId != null and storeId != ''">AND o.`store_id` = #{storeId}</if> <if test="storeId != null and storeId != ''">AND o.`store_id` = #{storeId}</if>