diff --git a/pom.xml b/pom.xml
index 8a5a97a..66a98fb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
com.imyeyu.timiserverapi
TimiServerAPI
- 1.0.50
+ 1.0.51
jar
TimiServerAPI
imyeyu.com API
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/java/com/imyeyu/api/modules/gao/vo/GaoSalesOrderPage.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoSalesOrderPage.java
index 4c87fcb..576978c 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoSalesOrderPage.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoSalesOrderPage.java
@@ -19,6 +19,9 @@ public class GaoSalesOrderPage extends BasePage {
/// 客户 ID
private String customerId;
+ /// 套餐配置 ID
+ private String packageConfigId;
+
/// 订单类型
private GaoSalesOrder.OrderType orderType;
diff --git a/src/main/resources/mapper/timi-server/gao/GaoSalesOrderMapper.xml b/src/main/resources/mapper/timi-server/gao/GaoSalesOrderMapper.xml
index ad5a835..dd4e975 100644
--- a/src/main/resources/mapper/timi-server/gao/GaoSalesOrderMapper.xml
+++ b/src/main/resources/mapper/timi-server/gao/GaoSalesOrderMapper.xml
@@ -23,6 +23,14 @@
o.`deleted_at` IS NULL
AND o.`store_id` = #{storeId}
AND o.`customer_id` = #{customerId}
+
+ AND EXISTS (
+ SELECT 1 FROM `gao_customer_package` cp
+ WHERE cp.`sales_order_id` = o.`id`
+ AND cp.`package_config_id` = #{packageConfigId}
+ AND cp.`deleted_at` IS NULL
+ )
+
AND o.`order_type` = #{orderType}
AND o.`status` = #{status}
AND o.`payment_status` = #{paymentStatus}
@@ -41,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},