diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoSalesOrderController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoSalesOrderController.java index 36bc23e..0c55ae9 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoSalesOrderController.java +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoSalesOrderController.java @@ -9,6 +9,8 @@ import com.imyeyu.api.modules.gao.service.GaoOrderPaymentService; import com.imyeyu.api.modules.gao.service.GaoSalesOrderService; import com.imyeyu.api.modules.gao.service.GaoStoreScopeService; import com.imyeyu.api.modules.gao.vo.GaoDirectProductOrderCreateRequest; +import com.imyeyu.api.modules.gao.vo.GaoCustomerProductPage; +import com.imyeyu.api.modules.gao.vo.GaoCustomerProductCustomerView; import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentActionRequest; import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentNoRefundRequest; import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentReceiveRequest; @@ -16,6 +18,7 @@ import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentRefundRequest; import com.imyeyu.api.modules.gao.vo.GaoOrderPaymentRevokeRequest; import com.imyeyu.api.modules.gao.vo.GaoSalesOrderCancelRequest; import com.imyeyu.api.modules.gao.vo.GaoSalesOrderPage; +import com.imyeyu.api.modules.gao.vo.GaoSalesOrderReceivableStatView; import com.imyeyu.api.modules.gao.vo.GaoSalesOrderUpdateRequest; import com.imyeyu.api.modules.user.service.UserService; import com.imyeyu.java.TimiJava; @@ -54,6 +57,26 @@ public class GaoSalesOrderController { return service.pageByQuery(page); } + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.SALES_ORDER_READ) + @PostMapping("/summary") + public GaoSalesOrderReceivableStatView summary(@RequestBody GaoSalesOrderPage page) { + TimiException.required(page, "not found sales order summary page"); + page.setStoreId(storeScopeService.resolveStoreId(page.getStoreId())); + return service.summary(page); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.SALES_ORDER_READ) + @PostMapping("/customer-product/list") + public PageResult customerProductList(@RequestBody GaoCustomerProductPage page) { + TimiException.required(page, "not found customer product page"); + page.setStoreId(storeScopeService.resolveStoreId(page.getStoreId())); + return service.pageCustomerProduct(page); + } + @AOPLog @RequestRateLimit @RequireGaoPermission(GaoPermissionCode.SALES_ORDER_READ) diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoSalesOrderMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoSalesOrderMapper.java index f5f14f0..985cdf2 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoSalesOrderMapper.java +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoSalesOrderMapper.java @@ -1,7 +1,11 @@ package com.imyeyu.api.modules.gao.mapper; import com.imyeyu.api.modules.gao.entity.GaoSalesOrder; +import com.imyeyu.api.modules.gao.vo.GaoCustomerProductPage; +import com.imyeyu.api.modules.gao.vo.GaoCustomerProductCustomerRow; +import com.imyeyu.api.modules.gao.vo.GaoCustomerProductView; import com.imyeyu.api.modules.gao.vo.GaoSalesOrderPage; +import com.imyeyu.api.modules.gao.vo.GaoSalesOrderReceivableStatView; import com.imyeyu.spring.mapper.BaseMapper; import org.apache.ibatis.annotations.Param; @@ -29,6 +33,18 @@ public interface GaoSalesOrderMapper extends BaseMapper { /// 按条件查询订单 List selectByQuery(GaoSalesOrderPage page); + /// 按条件汇总订单金额 + GaoSalesOrderReceivableStatView selectSummaryByQuery(GaoSalesOrderPage page); + + /// 按条件统计客户产品客户 + long countCustomerProductCustomerByQuery(GaoCustomerProductPage page); + + /// 按条件查询客户产品客户 + List selectCustomerProductCustomerByQuery(GaoCustomerProductPage page); + + /// 按条件查询客户产品明细 + List selectCustomerProductByQuery(GaoCustomerProductPage page); + /// 更新订单付款汇总 int updatePayment(@Param("id") String id, @Param("pendingPaidAmount") Long pendingPaidAmount, @Param("paidAmount") Long paidAmount, @Param("paidAt") Long paidAt, @Param("refundedAmount") Long refundedAmount, @Param("paymentStatus") GaoSalesOrder.PaymentStatus paymentStatus, diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoSalesOrderService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoSalesOrderService.java index 4eacddf..19ceb01 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoSalesOrderService.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoSalesOrderService.java @@ -3,9 +3,12 @@ package com.imyeyu.api.modules.gao.service; import com.imyeyu.api.modules.gao.entity.GaoSalesOrder; import com.imyeyu.api.modules.gao.entity.GaoSalesOrderItem; import com.imyeyu.api.modules.gao.vo.GaoDirectProductOrderCreateRequest; +import com.imyeyu.api.modules.gao.vo.GaoCustomerProductPage; +import com.imyeyu.api.modules.gao.vo.GaoCustomerProductCustomerView; import com.imyeyu.api.modules.gao.vo.GaoPackageOrderCreateData; import com.imyeyu.api.modules.gao.vo.GaoSalesOrderCancelRequest; import com.imyeyu.api.modules.gao.vo.GaoSalesOrderPage; +import com.imyeyu.api.modules.gao.vo.GaoSalesOrderReceivableStatView; import com.imyeyu.api.modules.gao.vo.GaoSalesOrderUpdateRequest; import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.service.BaseService; @@ -24,6 +27,12 @@ public interface GaoSalesOrderService extends BaseService /// 按条件分页查询订单 PageResult pageByQuery(GaoSalesOrderPage page); + /// 按条件汇总订单金额 + GaoSalesOrderReceivableStatView summary(GaoSalesOrderPage page); + + /// 按条件分页查询客户产品 + PageResult pageCustomerProduct(GaoCustomerProductPage page); + /// 查询订单详情 GaoSalesOrder detail(String id); diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoSalesOrderServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoSalesOrderServiceImplement.java index 1c1eba9..c726397 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoSalesOrderServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoSalesOrderServiceImplement.java @@ -30,10 +30,16 @@ import com.imyeyu.api.modules.gao.service.GaoStoreScopeService; import com.imyeyu.api.modules.gao.service.GaoStoreService; import com.imyeyu.api.modules.gao.vo.GaoDirectProductOrderCreateRequest; import com.imyeyu.api.modules.gao.vo.GaoPackageOrderCreateData; +import com.imyeyu.api.modules.gao.vo.GaoCustomerProductCustomerRow; +import com.imyeyu.api.modules.gao.vo.GaoCustomerProductCustomerView; +import com.imyeyu.api.modules.gao.vo.GaoCustomerProductPage; +import com.imyeyu.api.modules.gao.vo.GaoCustomerProductSummaryView; +import com.imyeyu.api.modules.gao.vo.GaoCustomerProductView; import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSelection; import com.imyeyu.api.modules.gao.vo.GaoSalesOrderCancelRequest; import com.imyeyu.api.modules.gao.vo.GaoSalesOrderItemRequest; import com.imyeyu.api.modules.gao.vo.GaoSalesOrderPage; +import com.imyeyu.api.modules.gao.vo.GaoSalesOrderReceivableStatView; import com.imyeyu.api.modules.gao.vo.GaoSalesOrderUpdateRequest; import com.imyeyu.api.modules.system.audit.AuditOperation; import com.imyeyu.api.modules.system.audit.OperationAudit; @@ -59,6 +65,7 @@ import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -111,6 +118,80 @@ public class GaoSalesOrderServiceImplement extends AbstractEntityService pageCustomerProduct(GaoCustomerProductPage page) { + TimiException.required(page, "not found customer product page"); + PageResult result = new PageResult<>(); + result.setTotal(mapper.countCustomerProductCustomerByQuery(page)); + List customerRows = mapper.selectCustomerProductCustomerByQuery(page); + if (customerRows.isEmpty()) { + result.setList(List.of()); + return result; + } + + List customerIdList = customerRows.stream().map(GaoCustomerProductCustomerRow::getCustomerId).toList(); + page.setCustomerIdList(customerIdList); + List itemList = mapper.selectCustomerProductByQuery(page); + Map customerMap = new LinkedHashMap<>(); + Map> purchasedProductMap = new HashMap<>(); + Map> availableProductMap = new HashMap<>(); + for (GaoCustomerProductCustomerRow row : customerRows) { + GaoCustomerProductCustomerView customerView = new GaoCustomerProductCustomerView(); + GaoCustomer customer = new GaoCustomer(); + customer.setId(row.getCustomerId()); + customer.setCode(row.getCustomerCode()); + customer.setName(row.getCustomerName()); + customerView.setCustomer(customer); + customerView.setOrderCount(row.getOrderCount()); + customerView.setPurchasedProducts(new ArrayList<>()); + customerView.setAvailableProducts(new ArrayList<>()); + customerMap.put(row.getCustomerId(), customerView); + } + for (GaoCustomerProductView item : itemList) { + if (!customerMap.containsKey(item.getCustomerId())) { + continue; + } + mergeCustomerProduct(purchasedProductMap, item, item.getBaseQuantity()); + if (decimal(item.getAvailableBaseQuantity()).compareTo(ZERO) > 0) { + mergeCustomerProduct(availableProductMap, item, item.getAvailableBaseQuantity()); + } + } + for (GaoCustomerProductCustomerRow row : customerRows) { + GaoCustomerProductCustomerView customerView = customerMap.get(row.getCustomerId()); + customerView.setPurchasedProducts(new ArrayList<>(purchasedProductMap.getOrDefault(row.getCustomerId(), Map.of()).values())); + customerView.setAvailableProducts(new ArrayList<>(availableProductMap.getOrDefault(row.getCustomerId(), Map.of()).values())); + } + result.setList(new ArrayList<>(customerMap.values())); + return result; + } + + private void mergeCustomerProduct(Map> productMap, + GaoCustomerProductView item, BigDecimal quantity) { + String customerId = item.getCustomerId(); + String productName = item.getProductNameSnapshot() == null ? "未命名产品" : item.getProductNameSnapshot(); + String unit = item.getBaseUnitName() == null ? item.getUnitSnapshot() : item.getBaseUnitName(); + unit = unit == null ? "基础单位" : unit; + String productKey = (item.getProductIdSnapshot() == null ? productName : item.getProductIdSnapshot()) + "::" + unit; + Map customerProducts = productMap.computeIfAbsent(customerId, key -> new LinkedHashMap<>()); + GaoCustomerProductSummaryView product = customerProducts.get(productKey); + if (product == null) { + product = new GaoCustomerProductSummaryView(); + product.setProductIdSnapshot(item.getProductIdSnapshot()); + product.setProductName(productName); + product.setQuantity(decimal(quantity)); + product.setUnit(unit); + customerProducts.put(productKey, product); + return; + } + product.setQuantity(decimal(product.getQuantity()).add(decimal(quantity))); + } + @Override public GaoSalesOrder detail(String id) { GaoSalesOrder order = get(id); diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerProductCustomerRow.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerProductCustomerRow.java new file mode 100644 index 0000000..3a9fe58 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerProductCustomerRow.java @@ -0,0 +1,23 @@ +package com.imyeyu.api.modules.gao.vo; + +import lombok.Data; + +/// GAO 客户产品客户分页行 +/// +/// @author Codex +/// @since 2026-09-18 +@Data +public class GaoCustomerProductCustomerRow { + + /// 客户 ID + private String customerId; + + /// 客户编码 + private String customerCode; + + /// 客户姓名 + private String customerName; + + /// 相关订单数 + private Long orderCount; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerProductCustomerView.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerProductCustomerView.java new file mode 100644 index 0000000..1edb715 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerProductCustomerView.java @@ -0,0 +1,26 @@ +package com.imyeyu.api.modules.gao.vo; + +import com.imyeyu.api.modules.gao.entity.GaoCustomer; +import lombok.Data; + +import java.util.List; + +/// GAO 客户产品客户视图 +/// +/// @author Codex +/// @since 2026-09-18 +@Data +public class GaoCustomerProductCustomerView { + + /// 客户 + private GaoCustomer customer; + + /// 相关订单数 + private Long orderCount; + + /// 已购买产品 + private List purchasedProducts; + + /// 当前可履约产品 + private List availableProducts; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerProductPage.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerProductPage.java new file mode 100644 index 0000000..edefb8b --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerProductPage.java @@ -0,0 +1,32 @@ +package com.imyeyu.api.modules.gao.vo; + +import com.imyeyu.api.modules.gao.entity.GaoSalesOrderItem; +import com.imyeyu.java.bean.BasePage; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/// GAO 客户产品分页请求 +/// +/// @author Codex +/// @since 2026-09-18 +@Data +@EqualsAndHashCode(callSuper = true) +public class GaoCustomerProductPage extends BasePage { + + /// 门店 ID + private String storeId; + + /// 客户 ID + private String customerId; + + /// 客户、订单或产品关键词 + private String keyword; + + /// 产品来源 + private GaoSalesOrderItem.ItemType itemType; + + /// 内部批量查询客户 ID + private List customerIdList; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerProductSummaryView.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerProductSummaryView.java new file mode 100644 index 0000000..f70d9e1 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerProductSummaryView.java @@ -0,0 +1,25 @@ +package com.imyeyu.api.modules.gao.vo; + +import lombok.Data; + +import java.math.BigDecimal; + +/// GAO 客户产品汇总视图 +/// +/// @author Codex +/// @since 2026-09-18 +@Data +public class GaoCustomerProductSummaryView { + + /// 产品 ID 快照 + private String productIdSnapshot; + + /// 产品名称 + private String productName; + + /// 产品数量,使用基础单位 + private BigDecimal quantity; + + /// 基础单位名称 + private String unit; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerProductView.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerProductView.java new file mode 100644 index 0000000..b5efac0 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerProductView.java @@ -0,0 +1,98 @@ +package com.imyeyu.api.modules.gao.vo; + +import com.imyeyu.api.modules.gao.entity.GaoSalesOrder; +import com.imyeyu.api.modules.gao.entity.GaoSalesOrderItem; +import lombok.Data; + +import java.math.BigDecimal; + +/// GAO 客户产品明细视图 +/// +/// 每一行对应一个订单明细,数量使用基础单位,便于汇总客户产品。 +/// +/// @author Codex +/// @since 2026-09-18 +@Data +public class GaoCustomerProductView { + + /// 订单明细 ID + private String id; + + /// 销售订单 ID + private String salesOrderId; + + /// 客户 ID + private String customerId; + + /// 客户编码 + private String customerCode; + + /// 客户姓名 + private String customerName; + + /// 订单号 + private String orderNo; + + /// 订单类型 + private GaoSalesOrder.OrderType orderType; + + /// 订单状态 + private GaoSalesOrder.Status orderStatus; + + /// 下单时间 + private Long purchasedAt; + + /// 订单项类型 + private GaoSalesOrderItem.ItemType itemType; + + /// 是否赠送权益 + private Boolean gifted; + + /// SKU ID + private String skuId; + + /// SKU 单位 ID + private String skuUnitId; + + /// 产品名称快照 + private String productNameSnapshot; + + /// 产品 ID 快照 + private String productIdSnapshot; + + /// SKU 编码快照 + private String skuCodeSnapshot; + + /// SKU 名称快照 + private String skuNameSnapshot; + + /// 规格摘要快照 + private String specSummarySnapshot; + + /// 单位快照 + private String unitSnapshot; + + /// 当前基础单位名称 + private String baseUnitName; + + /// 基础单位数量系数快照 + private BigDecimal baseQuantityPerUnitSnapshot; + + /// 购买数量 + private BigDecimal quantity; + + /// 购买基础单位数量 + private BigDecimal baseQuantity; + + /// 待履约基础单位数量 + private BigDecimal reservedBaseQuantity; + + /// 已履约基础单位数量 + private BigDecimal fulfilledBaseQuantity; + + /// 剩余权益基础单位数量 + private BigDecimal remainingBaseQuantity; + + /// 当前可立即履约基础单位数量 + private BigDecimal availableBaseQuantity; +} diff --git a/src/main/resources/mapper/timi-server/gao/GaoSalesOrderMapper.xml b/src/main/resources/mapper/timi-server/gao/GaoSalesOrderMapper.xml index ac7479a..dd4e975 100644 --- a/src/main/resources/mapper/timi-server/gao/GaoSalesOrderMapper.xml +++ b/src/main/resources/mapper/timi-server/gao/GaoSalesOrderMapper.xml @@ -49,6 +49,76 @@ ORDER BY o.`purchased_at` DESC, o.`created_at` DESC LIMIT #{offset}, #{limit} + + + i.`deleted_at` IS NULL + AND o.`deleted_at` IS NULL + AND o.`store_id` = #{storeId} + AND o.`status` <> 'CANCELLED' + AND o.`customer_id` = #{customerId} + + AND o.`customer_id` IN + + #{customerIdItem} + + + AND i.`item_type` = #{itemType} + + AND ( + c.`name` LIKE CONCAT('%', #{keyword}, '%') + OR c.`code` LIKE CONCAT('%', #{keyword}, '%') + OR o.`order_no` LIKE CONCAT('%', #{keyword}, '%') + OR i.`product_name_snapshot` LIKE CONCAT('%', #{keyword}, '%') + OR i.`sku_name_snapshot` LIKE CONCAT('%', #{keyword}, '%') + OR i.`sku_code_snapshot` LIKE CONCAT('%', #{keyword}, '%') + OR i.`spec_summary_snapshot` LIKE CONCAT('%', #{keyword}, '%') + ) + + + + + UPDATE `gao_sales_order` SET `pending_paid_amount` = #{pendingPaidAmount}, `paid_amount` = #{paidAmount}, `paid_at` = #{paidAt}, `refunded_amount` = #{refundedAmount},