From af9fece0c0223050768a6e6c21ebfcfc608fe5b4 Mon Sep 17 00:00:00 2001
From: dzc <2388224167@qq.com>
Date: Tue, 25 Aug 2026 12:49:36 +0800
Subject: [PATCH 1/3] =?UTF-8?q?feat:hard=E3=80=81wallet=E8=A1=A8=E6=A0=BC?=
=?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=BA=E5=91=98=E3=80=81=E4=BA=BA=E5=91=98?=
=?UTF-8?q?=E8=BA=AB=E4=BB=BD=E5=AD=97=E6=AE=B5=E5=92=8C=E6=90=9C=E7=B4=A2?=
=?UTF-8?q?=20fix:=E4=BC=98=E5=8C=96hard=E6=A8=A1=E5=9D=97=E7=BB=9F?=
=?UTF-8?q?=E8=AE=A1=E5=AF=BC=E5=87=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
db/20260825_add_third_mch_fz_relation.sql | 36 +-
.../bsd/pjcloud/hard/api/dto/HardAuthDto.java | 21 +-
.../hard/api/dto/HardBroadcastDto.java | 21 +-
.../pjcloud/hard/api/dto/HardDoorFlowDto.java | 21 +-
.../pjcloud/hard/api/dto/HardInvoiceDto.java | 17 +-
.../hard/api/dto/order/HardAfterSaleDto.java | 17 +-
.../hard/api/dto/order/HardOrderDto.java | 18 +-
.../hard/api/dto/spu/HardSpuRepertoryDto.java | 18 +-
.../bsd/pjcloud/hard/api/vo/HardAuthVo.java | 17 +-
.../pjcloud/hard/api/vo/HardBroadcastVo.java | 17 +-
.../pjcloud/hard/api/vo/HardDoorFlowVo.java | 17 +-
.../pjcloud/hard/api/vo/HardInvoiceVo.java | 26 +-
.../hard/api/vo/order/HardAfterSaleVo.java | 26 +-
.../HardCanteenProductApptExportRecord.java | 5 +
.../hard/api/vo/order/HardOrderVo.java | 15 +-
.../hard/api/vo/spu/HardSpuRepertoryVo.java | 53 +-
.../order/HardCanteenApptTotalController.java | 117 ---
.../order/HardCanteenDeptTotalController.java | 26 +-
.../order/HardCanteenTotalController.java | 35 +-
...dCanteenProductApptExportMergeHandler.java | 16 +-
.../hard/mapper/HardDoorFlowMapper.java | 3 +-
.../hard/mapper/HardInvoiceMapper.java | 2 +-
.../hard/mapper/order/HardCarMapper.java | 29 +-
.../HardCanteenApptTotalExportService.java | 18 -
.../hard/service/HardCanteenTotalService.java | 17 +
.../service/impl/HardAuthServiceImpl.java | 27 +-
.../impl/HardBroadcastServiceImpl.java | 25 +-
.../service/impl/HardDoorFlowServiceImpl.java | 27 +-
.../service/impl/HardInvoiceServiceImpl.java | 30 +-
.../impl/order/HardAfterSaleServiceImpl.java | 39 +-
...HardCanteenApptTotalExportServiceImpl.java | 497 +--------
.../order/HardCanteenTotalServiceImpl.java | 972 +++++++++++++++---
.../impl/order/HardOrderServiceImpl.java | 44 +-
.../impl/spu/HardSpuRepertoryServiceImpl.java | 33 +-
.../main/resources/mapper/HardAuthMapper.xml | 8 +-
.../resources/mapper/HardBroadcastMapper.xml | 8 +-
.../resources/mapper/HardInvoiceMapper.xml | 2 +-
.../mapper/order/HardAfterSaleMapper.xml | 2 +-
.../resources/mapper/order/HardCarMapper.xml | 251 +++--
...teenProductApptExportMergeHandlerTest.java | 34 +-
...CanteenApptTotalExportServiceImplTest.java | 163 +--
.../HardCanteenTotalServiceImplTest.java | 207 +++-
.../pay/ThirdPaymentTaskTypeEnum.java | 2 +
.../pay/wechat/ThirdAppPlusPayDto.java | 5 +
.../entity/pay/ThirdPayOrderEntity.java | 7 +-
.../entity/pay/ThirdPaymentTaskEntity.java | 2 +
.../thirdparty/config/AppPlusPayConfig.java | 10 +
.../appplus/ThirdAppPlusController.java | 29 +-
.../mapper/pay/ThirdPaymentTaskMapper.java | 12 +
.../pay/ThirdAggregatePayCreateContext.java | 3 +
.../service/ThirdMchFzRelationService.java | 8 +
.../service/ThirdPaymentTaskEnqueuer.java | 46 +
.../ThirdAggregateAliPayServiceImpl.java | 28 +
.../ThirdAggregateOrderPayServiceImpl.java | 29 +-
.../ThirdAggregateWechatPayServiceImpl.java | 12 +
.../ThirdAppPlusAliPayServiceImpl.java | 100 +-
.../impl/appplus/ThirdAppPlusServiceImpl.java | 30 +
...ThirdAppPlusWechatMicropayServiceImpl.java | 4 +
...hirdAppPlusWechatNativePayServiceImpl.java | 12 +
.../ThirdAppPlusWechatServiceImpl.java | 4 +
.../fz/ThirdAppPlusWechatFzServiceImpl.java | 47 +-
.../pay/ThirdMchFzRelationServiceImpl.java | 353 ++++++-
.../impl/pay/ThirdPaymentTaskServiceImpl.java | 12 +
.../resources/mapper/ThirdPayOrderMapper.xml | 1 +
.../mapper/ThirdPaymentTaskMapper.xml | 28 +-
...ThirdPaymentTaskMapperSqlContractTest.java | 18 +
.../service/ThirdPaymentTaskEnqueuerTest.java | 26 +
.../ThirdAggregateAliPayServiceImplTest.java | 24 +-
...ThirdAggregateOrderPayServiceImplTest.java | 8 +-
.../ThirdAppPlusAliPayServiceImplTest.java | 48 +
...irdAppPlusServiceImplOrderCodePayTest.java | 24 +
.../ThirdAppPlusWechatFzServiceImplTest.java | 81 ++
.../config/ThirdPaymentQuartzInitializer.java | 1 +
.../wallet/api/dto/WalletActiveDto.java | 17 +-
.../wallet/api/dto/WalletForeignDto.java | 19 +-
.../wallet/api/dto/WalletFreezeDto.java | 17 +-
.../api/dto/WalletManageBalanceDto.java | 18 +-
.../wallet/api/dto/WalletManageDto.java | 20 +-
.../wallet/api/dto/WalletStrideDto.java | 17 +-
.../wallet/api/dto/WalletTopupActionDto.java | 20 +-
.../wallet/api/dto/WalletTransactionDto.java | 18 +-
.../dto/recharge/WalletRechargeApplyDto.java | 17 +-
.../WalletRechargeBindInvoiceDto.java | 17 +-
.../dto/recharge/WalletRechargeImportDto.java | 18 +-
.../dto/rule/WalletRuleTransactionDto.java | 20 +-
.../pjcloud/wallet/api/vo/WalletActiveVo.java | 27 +-
.../wallet/api/vo/WalletForeignVo.java | 27 +-
.../pjcloud/wallet/api/vo/WalletFreezeVo.java | 27 +-
.../pjcloud/wallet/api/vo/WalletManageVo.java | 62 +-
.../pjcloud/wallet/api/vo/WalletStrideVo.java | 27 +-
.../vo/recharge/WalletRechargeApplyVo.java | 27 +-
.../vo/recharge/WalletRechargeImportVo.java | 19 +-
.../vo/recharge/WalletRechargeInvoiceVo.java | 38 +-
.../api/vo/rule/WalletRuleTransactionVo.java | 19 +-
.../vo/transaction/WalletTransactionVo.java | 62 +-
.../WalletTransactionController.java | 5 +
.../mch/MchPaySettingController.java | 3 +-
.../WalletRechargeImportController.java | 3 +-
.../wallet/mapper/WalletActiveMapper.java | 4 +-
.../wallet/mapper/WalletFreezeMapper.java | 4 +-
.../recharge/WalletRechargeImportMapper.java | 6 +-
.../wallet/service/MchPaySettingService.java | 8 +
.../wallet/service/WalletManageService.java | 7 +
.../service/WalletRechargeImportService.java | 9 +-
.../service/WalletTransactionService.java | 7 +
.../service/impl/WalletActiveServiceImpl.java | 39 +-
.../impl/WalletForeignServiceImpl.java | 34 +-
.../service/impl/WalletFreezeServiceImpl.java | 34 +-
.../impl/WalletManageExportServiceImpl.java | 1 +
.../service/impl/WalletManageServiceImpl.java | 57 +-
.../service/impl/WalletStrideServiceImpl.java | 36 +-
.../impl/WalletTransactionServiceImpl.java | 36 +-
.../impl/mch/MchPaySettingServiceImpl.java | 68 +-
.../WalletRechargeApplyServiceImpl.java | 41 +-
.../WalletRechargeImportServiceImpl.java | 58 +-
.../WalletRechargeInvoiceServiceImpl.java | 28 +-
.../WalletRuleTransactionServiceImpl.java | 33 +-
.../support/WalletUserIdentitySupport.java | 257 +++++
.../resources/mapper/WalletManageMapper.xml | 20 +-
.../recharge/WalletRechargeInvoiceMapper.xml | 26 +-
.../rule/WalletRuleTransactionMapper.xml | 8 +-
.../mch/MchPaySettingServiceImplTest.java | 7 +-
122 files changed, 3963 insertions(+), 1420 deletions(-)
create mode 100644 pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/support/WalletUserIdentitySupport.java
diff --git a/db/20260825_add_third_mch_fz_relation.sql b/db/20260825_add_third_mch_fz_relation.sql
index 25115867..6ffc39cc 100644
--- a/db/20260825_add_third_mch_fz_relation.sql
+++ b/db/20260825_add_third_mch_fz_relation.sql
@@ -54,13 +54,29 @@ SET @relation_task_column_exists = (
);
SET @relation_task_column_sql = IF(
@relation_task_column_exists = 0,
- 'ALTER TABLE `third_payment_task` ADD COLUMN `fz_relation_id` bigint DEFAULT NULL COMMENT ''商户分账关系主键'' AFTER `fz_order_id`, ADD INDEX `idx_third_payment_task_relation` (`tenant_id`, `fz_relation_id`)',
+ 'ALTER TABLE `third_payment_task` ADD COLUMN `fz_relation_id` bigint DEFAULT NULL COMMENT ''商户分账关系主键'' AFTER `fz_order_id`',
'SELECT 1'
);
PREPARE relation_task_column_stmt FROM @relation_task_column_sql;
EXECUTE relation_task_column_stmt;
DEALLOCATE PREPARE relation_task_column_stmt;
+SET @relation_task_index_exists = (
+ SELECT COUNT(1)
+ FROM information_schema.STATISTICS
+ WHERE TABLE_SCHEMA = DATABASE()
+ AND TABLE_NAME = 'third_payment_task'
+ AND INDEX_NAME = 'idx_third_payment_task_relation'
+);
+SET @relation_task_index_sql = IF(
+ @relation_task_index_exists = 0,
+ 'ALTER TABLE `third_payment_task` ADD INDEX `idx_third_payment_task_relation` (`tenant_id`, `fz_relation_id`)',
+ 'SELECT 1'
+);
+PREPARE relation_task_index_stmt FROM @relation_task_index_sql;
+EXECUTE relation_task_index_stmt;
+DEALLOCATE PREPARE relation_task_index_stmt;
+
-- 支付订单固定引用创建时使用的已绑定关系,后续分账不跟随平台接收账号配置漂移。
SET @pay_relation_column_exists = (
SELECT COUNT(1)
@@ -71,9 +87,25 @@ SET @pay_relation_column_exists = (
);
SET @pay_relation_column_sql = IF(
@pay_relation_column_exists = 0,
- 'ALTER TABLE `third_pay_order` ADD COLUMN `fz_relation_id` bigint DEFAULT NULL COMMENT ''支付创建时绑定的平台分佣关系主键'' AFTER `mch_pay_setting_json`, ADD INDEX `idx_third_pay_order_fz_relation` (`tenant_id`, `fz_relation_id`)',
+ 'ALTER TABLE `third_pay_order` ADD COLUMN `fz_relation_id` bigint DEFAULT NULL COMMENT ''支付创建时绑定的平台分佣关系主键'' AFTER `mch_pay_setting_json`',
'SELECT 1'
);
PREPARE pay_relation_column_stmt FROM @pay_relation_column_sql;
EXECUTE pay_relation_column_stmt;
DEALLOCATE PREPARE pay_relation_column_stmt;
+
+SET @pay_relation_index_exists = (
+ SELECT COUNT(1)
+ FROM information_schema.STATISTICS
+ WHERE TABLE_SCHEMA = DATABASE()
+ AND TABLE_NAME = 'third_pay_order'
+ AND INDEX_NAME = 'idx_third_pay_order_fz_relation'
+);
+SET @pay_relation_index_sql = IF(
+ @pay_relation_index_exists = 0,
+ 'ALTER TABLE `third_pay_order` ADD INDEX `idx_third_pay_order_fz_relation` (`tenant_id`, `fz_relation_id`)',
+ 'SELECT 1'
+);
+PREPARE pay_relation_index_stmt FROM @pay_relation_index_sql;
+EXECUTE pay_relation_index_stmt;
+DEALLOCATE PREPARE pay_relation_index_stmt;
diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/HardAuthDto.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/HardAuthDto.java
index 2eac5ae6..4ea972c0 100644
--- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/HardAuthDto.java
+++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/HardAuthDto.java
@@ -1,12 +1,31 @@
package com.bsd.pjcloud.hard.api.dto;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserStudentDto;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserTeacherDto;
+import com.bsd.pjcloud.admin.api.vo.base.BaseUserIdentityQueryInterface;
import com.bsd.pjcloud.hard.api.entity.HardAuthEntity;
import lombok.Data;
import java.time.LocalDate;
+import java.util.List;
@Data
-public class HardAuthDto extends HardAuthEntity {
+public class HardAuthDto extends HardAuthEntity implements BaseUserIdentityQueryInterface {
+
+ /** 身份预筛选后用于授权记录查询的用户主键集合。 */
+ private List userIds;
+
+ /** UPMS 用户身份协议编码集合;为空时不限制身份。 */
+ private List identityTypes;
+
+ /** 学生身份扩展查询条件;对象为空或字段均为空时不限制学生资料。 */
+ private SysUserStudentDto studentInfo;
+
+ /** 老师身份扩展查询条件;对象为空或老师学号为空时不限制老师资料。 */
+ private SysUserTeacherDto teacherInfo;
+
+ /** 人脸录入状态;1 表示已录入,0 表示未录入,空值表示不限制。 */
+ private String hasFace;
private LocalDate[] createTimeList;
diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/HardBroadcastDto.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/HardBroadcastDto.java
index 52af6d62..f15ab986 100644
--- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/HardBroadcastDto.java
+++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/HardBroadcastDto.java
@@ -1,12 +1,31 @@
package com.bsd.pjcloud.hard.api.dto;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserStudentDto;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserTeacherDto;
+import com.bsd.pjcloud.admin.api.vo.base.BaseUserIdentityQueryInterface;
import com.bsd.pjcloud.hard.api.entity.HardBroadcastEntity;
import lombok.Data;
import java.time.LocalDate;
+import java.util.List;
@Data
-public class HardBroadcastDto extends HardBroadcastEntity {
+public class HardBroadcastDto extends HardBroadcastEntity implements BaseUserIdentityQueryInterface {
+
+ /** 身份预筛选后用于广播记录查询的用户主键集合。 */
+ private List userIds;
+
+ /** UPMS 用户身份协议编码集合;为空时不限制身份。 */
+ private List identityTypes;
+
+ /** 学生身份扩展查询条件;对象为空或字段均为空时不限制学生资料。 */
+ private SysUserStudentDto studentInfo;
+
+ /** 老师身份扩展查询条件;对象为空或老师学号为空时不限制老师资料。 */
+ private SysUserTeacherDto teacherInfo;
+
+ /** 人脸录入状态;1 表示已录入,0 表示未录入,空值表示不限制。 */
+ private String hasFace;
private LocalDate[] createTimeList;
diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/HardDoorFlowDto.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/HardDoorFlowDto.java
index 37ec0c8f..b2c852e4 100644
--- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/HardDoorFlowDto.java
+++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/HardDoorFlowDto.java
@@ -1,12 +1,31 @@
package com.bsd.pjcloud.hard.api.dto;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserStudentDto;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserTeacherDto;
+import com.bsd.pjcloud.admin.api.vo.base.BaseUserIdentityQueryInterface;
import com.bsd.pjcloud.hard.api.entity.HardDoorFlowEntity;
import lombok.Data;
import java.time.LocalDate;
+import java.util.List;
@Data
-public class HardDoorFlowDto extends HardDoorFlowEntity {
+public class HardDoorFlowDto extends HardDoorFlowEntity implements BaseUserIdentityQueryInterface {
+
+ /** 身份预筛选后用于开门流程查询的用户主键集合。 */
+ private List userIds;
+
+ /** UPMS 用户身份协议编码集合;为空时不限制身份。 */
+ private List identityTypes;
+
+ /** 学生身份扩展查询条件;对象为空或字段均为空时不限制学生资料。 */
+ private SysUserStudentDto studentInfo;
+
+ /** 老师身份扩展查询条件;对象为空或老师学号为空时不限制老师资料。 */
+ private SysUserTeacherDto teacherInfo;
+
+ /** 人脸录入状态;1 表示已录入,0 表示未录入,空值表示不限制。 */
+ private String hasFace;
private LocalDate[] createTimeList;
diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/HardInvoiceDto.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/HardInvoiceDto.java
index 28d6ec52..e90e5bf4 100644
--- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/HardInvoiceDto.java
+++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/HardInvoiceDto.java
@@ -1,5 +1,8 @@
package com.bsd.pjcloud.hard.api.dto;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserStudentDto;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserTeacherDto;
+import com.bsd.pjcloud.admin.api.vo.base.BaseUserIdentityQueryInterface;
import com.bsd.pjcloud.hard.api.entity.HardInvoiceEntity;
import lombok.Data;
@@ -7,12 +10,24 @@ import java.time.LocalDate;
import java.util.List;
@Data
-public class HardInvoiceDto extends HardInvoiceEntity {
+public class HardInvoiceDto extends HardInvoiceEntity implements BaseUserIdentityQueryInterface {
private List deptIds;
private List userIds;
+ /** UPMS 用户身份协议编码集合;为空时不限制身份。 */
+ private List identityTypes;
+
+ /** 学生身份扩展查询条件;对象为空或字段均为空时不限制学生资料。 */
+ private SysUserStudentDto studentInfo;
+
+ /** 老师身份扩展查询条件;对象为空或老师学号为空时不限制老师资料。 */
+ private SysUserTeacherDto teacherInfo;
+
+ /** 人脸录入状态;1 表示已录入,0 表示未录入,空值表示不限制。 */
+ private String hasFace;
+
private List merchantIds;
private String hardType;
diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/HardAfterSaleDto.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/HardAfterSaleDto.java
index 10450a42..828d157c 100644
--- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/HardAfterSaleDto.java
+++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/HardAfterSaleDto.java
@@ -1,5 +1,8 @@
package com.bsd.pjcloud.hard.api.dto.order;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserStudentDto;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserTeacherDto;
+import com.bsd.pjcloud.admin.api.vo.base.BaseUserIdentityQueryInterface;
import com.bsd.pjcloud.hard.api.entity.order.HardAfterSaleEntity;
import lombok.Data;
@@ -8,10 +11,22 @@ import java.time.LocalDateTime;
import java.util.List;
@Data
-public class HardAfterSaleDto extends HardAfterSaleEntity {
+public class HardAfterSaleDto extends HardAfterSaleEntity implements BaseUserIdentityQueryInterface {
private List userIds;
+ /** UPMS 用户身份协议编码集合;为空时不限制身份。 */
+ private List identityTypes;
+
+ /** 学生身份扩展查询条件;对象为空或字段均为空时不限制学生资料。 */
+ private SysUserStudentDto studentInfo;
+
+ /** 老师身份扩展查询条件;对象为空或老师学号为空时不限制老师资料。 */
+ private SysUserTeacherDto teacherInfo;
+
+ /** 人脸录入状态;1 表示已录入,0 表示未录入,空值表示不限制。 */
+ private String hasFace;
+
private LocalDate[] createTimeList;
private List mchIds;
diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/HardOrderDto.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/HardOrderDto.java
index a25062a1..732377b4 100644
--- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/HardOrderDto.java
+++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/HardOrderDto.java
@@ -1,6 +1,9 @@
package com.bsd.pjcloud.hard.api.dto.order;
import com.bsd.pjcloud.admin.api.vo.base.BaseIsIncludeTreeInterface;
+import com.bsd.pjcloud.admin.api.vo.base.BaseUserIdentityQueryInterface;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserStudentDto;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserTeacherDto;
import com.bsd.pjcloud.common.core.constant.enums.DateRangeTypeEnum;
import com.bsd.pjcloud.hard.api.entity.order.HardOrderEntity;
import lombok.Data;
@@ -9,8 +12,9 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
+/** 硬件订单后台查询条件,身份字段用于按订单用户的身份资料筛选。 */
@Data
-public class HardOrderDto extends HardOrderEntity implements BaseIsIncludeTreeInterface {
+public class HardOrderDto extends HardOrderEntity implements BaseIsIncludeTreeInterface, BaseUserIdentityQueryInterface {
private DateRangeTypeEnum dateRangeType = DateRangeTypeEnum.YESTERDAY;
@@ -131,4 +135,16 @@ public class HardOrderDto extends HardOrderEntity implements BaseIsIncludeTreeIn
* `apptId`或者`appt_rule_id`不为null(筛选预约订单)
*/
Boolean apptIdOrApptRuleIdNotNull = false;
+
+ /** 用户身份协议编码集合;为空表示不限制身份。 */
+ private List identityTypes;
+
+ /** 学生身份扩展筛选条件。 */
+ private SysUserStudentDto studentInfo;
+
+ /** 老师身份扩展筛选条件。 */
+ private SysUserTeacherDto teacherInfo;
+
+ /** 人脸录入状态:1 已录入、0 未录入、空值表示全部。 */
+ private String hasFace;
}
diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/spu/HardSpuRepertoryDto.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/spu/HardSpuRepertoryDto.java
index 94e3622f..5e62601a 100644
--- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/spu/HardSpuRepertoryDto.java
+++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/spu/HardSpuRepertoryDto.java
@@ -1,5 +1,8 @@
package com.bsd.pjcloud.hard.api.dto.spu;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserStudentDto;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserTeacherDto;
+import com.bsd.pjcloud.admin.api.vo.base.BaseUserIdentityQueryInterface;
import com.bsd.pjcloud.hard.api.entity.spu.HardSpuRepertoryEntity;
import lombok.Data;
@@ -7,7 +10,8 @@ import java.time.LocalDateTime;
import java.util.List;
@Data
-public class HardSpuRepertoryDto extends HardSpuRepertoryEntity {
+/** 硬件商品库存更新记录查询条件,支持按上架用户身份快速筛选。 */
+public class HardSpuRepertoryDto extends HardSpuRepertoryEntity implements BaseUserIdentityQueryInterface {
private LocalDateTime[] createTimeList;
@@ -15,4 +19,16 @@ public class HardSpuRepertoryDto extends HardSpuRepertoryEntity {
private Long cabinetId;
+ /** UPMS 用户身份协议编码集合;为空表示不限制身份。 */
+ private List identityTypes;
+
+ /** 学生身份扩展查询条件;对象为空或字段均为空时不限制学生资料。 */
+ private SysUserStudentDto studentInfo;
+
+ /** 老师身份扩展查询条件;对象为空或老师学号为空时不限制老师资料。 */
+ private SysUserTeacherDto teacherInfo;
+
+ /** 人脸录入状态;1 表示已录入,0 表示未录入,空值表示不限制。 */
+ private String hasFace;
+
}
diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/HardAuthVo.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/HardAuthVo.java
index 4baf063e..30347ec5 100644
--- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/HardAuthVo.java
+++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/HardAuthVo.java
@@ -1,6 +1,9 @@
package com.bsd.pjcloud.hard.api.vo;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserStudentDto;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserTeacherDto;
import com.bsd.pjcloud.admin.api.vo.base.BaseUserDeptInterface;
+import com.bsd.pjcloud.admin.api.vo.base.BaseUserIdentityInterface;
import com.bsd.pjcloud.hard.api.entity.HardAuthEntity;
import com.bsd.pjcloud.hard.api.vo.base.BaseHardInterface;
import lombok.Data;
@@ -8,7 +11,7 @@ import lombok.Data;
import java.util.List;
@Data
-public class HardAuthVo extends HardAuthEntity implements BaseHardInterface, BaseUserDeptInterface {
+public class HardAuthVo extends HardAuthEntity implements BaseHardInterface, BaseUserDeptInterface, BaseUserIdentityInterface {
/**
* 设备相关
@@ -26,10 +29,22 @@ public class HardAuthVo extends HardAuthEntity implements BaseHardInterface, Bas
private String userMobile;
+ /** 用户标准手机号字段,供身份组件展示;与历史 userMobile 同步回填。 */
+ private String phone;
+
private String userAvatar;
private Long userDeptId;
private String userDeptName;
+ /** 用户拥有的 UPMS 身份协议编码集合。 */
+ private List identityTypes;
+
+ /** 用户的学生身份扩展资料;无学生身份时为空。 */
+ private SysUserStudentDto studentInfo;
+
+ /** 用户的老师身份扩展资料;无老师身份时为空。 */
+ private SysUserTeacherDto teacherInfo;
+
}
diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/HardBroadcastVo.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/HardBroadcastVo.java
index 22967938..12d3a11e 100644
--- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/HardBroadcastVo.java
+++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/HardBroadcastVo.java
@@ -1,6 +1,9 @@
package com.bsd.pjcloud.hard.api.vo;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserStudentDto;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserTeacherDto;
import com.bsd.pjcloud.admin.api.vo.base.BaseUserDeptInterface;
+import com.bsd.pjcloud.admin.api.vo.base.BaseUserIdentityInterface;
import com.bsd.pjcloud.hard.api.entity.HardBroadcastEntity;
import com.bsd.pjcloud.hard.api.vo.base.BaseHardInterface;
import lombok.Data;
@@ -8,7 +11,7 @@ import lombok.Data;
import java.util.List;
@Data
-public class HardBroadcastVo extends HardBroadcastEntity implements BaseHardInterface, BaseUserDeptInterface {
+public class HardBroadcastVo extends HardBroadcastEntity implements BaseHardInterface, BaseUserDeptInterface, BaseUserIdentityInterface {
/**
* 设备相关
@@ -26,12 +29,24 @@ public class HardBroadcastVo extends HardBroadcastEntity implements BaseHardInte
private String userMobile;
+ /** 用户标准手机号字段,供身份组件展示;与历史 userMobile 同步回填。 */
+ private String phone;
+
private String userAvatar;
private Long userDeptId;
private String userDeptName;
+ /** 用户拥有的 UPMS 身份协议编码集合。 */
+ private List identityTypes;
+
+ /** 用户的学生身份扩展资料;无学生身份时为空。 */
+ private SysUserStudentDto studentInfo;
+
+ /** 用户的老师身份扩展资料;无老师身份时为空。 */
+ private SysUserTeacherDto teacherInfo;
+
/**
* 订单相关
*/
diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/HardDoorFlowVo.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/HardDoorFlowVo.java
index ec71266b..f0974aa8 100644
--- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/HardDoorFlowVo.java
+++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/HardDoorFlowVo.java
@@ -1,6 +1,9 @@
package com.bsd.pjcloud.hard.api.vo;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserStudentDto;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserTeacherDto;
import com.bsd.pjcloud.admin.api.vo.base.BaseUserDeptInterface;
+import com.bsd.pjcloud.admin.api.vo.base.BaseUserIdentityInterface;
import com.bsd.pjcloud.hard.api.entity.HardAuthEntity;
import com.bsd.pjcloud.hard.api.entity.HardDoorFlowEntity;
import com.bsd.pjcloud.hard.api.vo.base.BaseHardInterface;
@@ -9,7 +12,7 @@ import lombok.Data;
import java.util.List;
@Data
-public class HardDoorFlowVo extends HardDoorFlowEntity implements BaseHardInterface, BaseUserDeptInterface {
+public class HardDoorFlowVo extends HardDoorFlowEntity implements BaseHardInterface, BaseUserDeptInterface, BaseUserIdentityInterface {
/**
* 设备相关
@@ -27,12 +30,24 @@ public class HardDoorFlowVo extends HardDoorFlowEntity implements BaseHardInterf
private String userMobile;
+ /** 用户标准手机号字段,供身份组件展示;与历史 userMobile 同步回填。 */
+ private String phone;
+
private String userAvatar;
private Long userDeptId;
private String userDeptName;
+ /** 用户拥有的 UPMS 身份协议编码集合。 */
+ private List identityTypes;
+
+ /** 用户的学生身份扩展资料;无学生身份时为空。 */
+ private SysUserStudentDto studentInfo;
+
+ /** 用户的老师身份扩展资料;无老师身份时为空。 */
+ private SysUserTeacherDto teacherInfo;
+
private HardAuthEntity hardAuth;
diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/HardInvoiceVo.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/HardInvoiceVo.java
index 0cd57feb..77a0a1d1 100644
--- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/HardInvoiceVo.java
+++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/HardInvoiceVo.java
@@ -1,7 +1,10 @@
package com.bsd.pjcloud.hard.api.vo;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserStudentDto;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserTeacherDto;
import com.bsd.pjcloud.admin.api.vo.base.BaseDeptInterface;
-import com.bsd.pjcloud.admin.api.vo.base.BaseUserInterface;
+import com.bsd.pjcloud.admin.api.vo.base.BaseUserDeptInterface;
+import com.bsd.pjcloud.admin.api.vo.base.BaseUserIdentityInterface;
import com.bsd.pjcloud.hard.api.entity.HardInvoiceEntity;
import com.bsd.pjcloud.hard.api.vo.base.BaseHardInterface;
import com.bsd.pjcloud.hard.api.vo.base.BaseOrderInterface;
@@ -9,9 +12,10 @@ import com.bsd.pjcloud.wallet.api.vo.base.BaseMchInterface;
import lombok.Data;
import java.time.LocalDateTime;
+import java.util.List;
@Data
-public class HardInvoiceVo extends HardInvoiceEntity implements BaseDeptInterface, BaseMchInterface, BaseOrderInterface, BaseHardInterface, BaseUserInterface {
+public class HardInvoiceVo extends HardInvoiceEntity implements BaseUserDeptInterface, BaseUserIdentityInterface, BaseDeptInterface, BaseMchInterface, BaseOrderInterface, BaseHardInterface {
private Long historyId;
@@ -72,6 +76,24 @@ public class HardInvoiceVo extends HardInvoiceEntity implements BaseDeptInterfac
private String userMobile;
+ /** 用户标准手机号字段,供身份组件展示;与历史 userMobile 同步回填。 */
+ private String phone;
+
private String userAvatar;
+ /** UPMS 当前用户部门主键,用于公共用户信息组件的标准别名。 */
+ private Long userDeptId;
+
+ /** UPMS 当前用户部门名称,用于公共用户信息组件的标准别名。 */
+ private String userDeptName;
+
+ /** 用户拥有的 UPMS 身份协议编码集合。 */
+ private List identityTypes;
+
+ /** 用户的学生身份扩展资料;无学生身份时为空。 */
+ private SysUserStudentDto studentInfo;
+
+ /** 用户的老师身份扩展资料;无老师身份时为空。 */
+ private SysUserTeacherDto teacherInfo;
+
}
diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardAfterSaleVo.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardAfterSaleVo.java
index 49b3e4ad..e265efba 100644
--- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardAfterSaleVo.java
+++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardAfterSaleVo.java
@@ -1,15 +1,19 @@
package com.bsd.pjcloud.hard.api.vo.order;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserStudentDto;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserTeacherDto;
import com.bsd.pjcloud.admin.api.vo.base.BaseDeptInterface;
-import com.bsd.pjcloud.admin.api.vo.base.BaseUserInterface;
+import com.bsd.pjcloud.admin.api.vo.base.BaseUserDeptInterface;
+import com.bsd.pjcloud.admin.api.vo.base.BaseUserIdentityInterface;
import com.bsd.pjcloud.hard.api.entity.order.HardAfterSaleEntity;
import com.bsd.pjcloud.hard.api.vo.base.BaseOrderInterface;
import lombok.Data;
import java.time.LocalDateTime;
+import java.util.List;
@Data
-public class HardAfterSaleVo extends HardAfterSaleEntity implements BaseUserInterface, BaseDeptInterface, BaseOrderInterface {
+public class HardAfterSaleVo extends HardAfterSaleEntity implements BaseUserDeptInterface, BaseUserIdentityInterface, BaseDeptInterface, BaseOrderInterface {
/**
* 自动退款任务状态;为空表示当前售后仍走人工审核,非空时前端不得展示审核或取消操作。
*/
@@ -40,8 +44,26 @@ public class HardAfterSaleVo extends HardAfterSaleEntity implements BaseUserInte
private String userMobile;
+ /** 用户标准手机号字段,供身份组件展示;与历史 userMobile 同步回填。 */
+ private String phone;
+
private String userAvatar;
+ /** UPMS 当前用户部门主键,用于公共用户信息组件的标准别名。 */
+ private Long userDeptId;
+
+ /** UPMS 当前用户部门名称,用于公共用户信息组件的标准别名。 */
+ private String userDeptName;
+
+ /** 用户拥有的 UPMS 身份协议编码集合。 */
+ private List identityTypes;
+
+ /** 用户的学生身份扩展资料;无学生身份时为空。 */
+ private SysUserStudentDto studentInfo;
+
+ /** 用户的老师身份扩展资料;无老师身份时为空。 */
+ private SysUserTeacherDto teacherInfo;
+
/**
* 部门相关
*/
diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardCanteenProductApptExportRecord.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardCanteenProductApptExportRecord.java
index 96f057a1..843d866e 100644
--- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardCanteenProductApptExportRecord.java
+++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardCanteenProductApptExportRecord.java
@@ -11,6 +11,11 @@ import lombok.Data;
@Data
public class HardCanteenProductApptExportRecord {
+ /**
+ * 导出聚合层级,固定为 SUMMARY、MERCHANT 或 PRODUCT;仅用于一次查询后拆分三级结果,不持久化。
+ */
+ private String recordLevel;
+
/** 业务发生日期,格式为 yyyy-MM-dd;线下取支付日期,预约取核销日期。 */
private String statisticsDate;
diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardOrderVo.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardOrderVo.java
index ffe257dd..afa0bf53 100644
--- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardOrderVo.java
+++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardOrderVo.java
@@ -1,6 +1,9 @@
package com.bsd.pjcloud.hard.api.vo.order;
import com.bsd.pjcloud.admin.api.vo.base.BaseUserDeptInterface;
+import com.bsd.pjcloud.admin.api.vo.base.BaseUserIdentityInterface;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserStudentDto;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserTeacherDto;
import com.bsd.pjcloud.hard.api.entity.order.HardOrderEntity;
import com.bsd.pjcloud.hard.api.vo.base.BaseHardInterface;
import com.bsd.pjcloud.wallet.api.vo.base.BaseMchInterface;
@@ -11,8 +14,9 @@ import lombok.Data;
import java.time.LocalTime;
import java.util.List;
+/** 硬件订单管理端展示对象,包含公共用户主资料、部门及身份信息。 */
@Data
-public class HardOrderVo extends HardOrderEntity implements BaseUserDeptInterface, BaseMchInterface, BaseHardInterface {
+public class HardOrderVo extends HardOrderEntity implements BaseUserDeptInterface, BaseUserIdentityInterface, BaseMchInterface, BaseHardInterface {
/** 统计明细来源编码;线下订单为 OFFLINE_PURCHASE,预约已核销订单为 RESERVATION_VERIFIED。 */
private String saleSource;
@@ -28,6 +32,15 @@ public class HardOrderVo extends HardOrderEntity implements BaseUserDeptInterfac
private String userAvatar;
+ /** 用户身份协议编码集合;没有身份时为空集合或空值。 */
+ private List identityTypes;
+
+ /** 学生身份扩展资料;没有学生身份时为空。 */
+ private SysUserStudentDto studentInfo;
+
+ /** 老师身份扩展资料;没有老师身份时为空。 */
+ private SysUserTeacherDto teacherInfo;
+
/**
* 部门相关
*/
diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/spu/HardSpuRepertoryVo.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/spu/HardSpuRepertoryVo.java
index 052836be..d9157f4b 100644
--- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/spu/HardSpuRepertoryVo.java
+++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/spu/HardSpuRepertoryVo.java
@@ -1,5 +1,9 @@
package com.bsd.pjcloud.hard.api.vo.spu;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserStudentDto;
+import com.bsd.pjcloud.admin.api.dto.user.SysUserTeacherDto;
+import com.bsd.pjcloud.admin.api.vo.base.BaseUserDeptInterface;
+import com.bsd.pjcloud.admin.api.vo.base.BaseUserIdentityInterface;
import com.bsd.pjcloud.admin.api.vo.base.BaseUserInterface;
import com.bsd.pjcloud.hard.api.entity.spu.HardSpuRepertoryEntity;
import com.bsd.pjcloud.hard.api.vo.base.BaseHardInterface;
@@ -7,7 +11,8 @@ import com.bsd.pjcloud.wallet.api.vo.base.BaseMchInterface;
import lombok.Data;
@Data
-public class HardSpuRepertoryVo extends HardSpuRepertoryEntity implements BaseMchInterface, BaseUserInterface, BaseHardInterface {
+/** 硬件商品库存更新记录展示对象,包含上架用户主资料、部门和身份信息。 */
+public class HardSpuRepertoryVo extends HardSpuRepertoryEntity implements BaseMchInterface, BaseUserInterface, BaseUserDeptInterface, BaseUserIdentityInterface, BaseHardInterface {
/**
* 商品相关
@@ -45,6 +50,52 @@ public class HardSpuRepertoryVo extends HardSpuRepertoryEntity implements BaseMc
private String userAvatar;
+ /** 用户标准手机号字段,供统一身份组件展示;与历史 userMobile 字段并存。 */
+ private String phone;
+
+ /** 用户当前所属部门主键,由 UPMS 用户资料回填。 */
+ private Long userDeptId;
+
+ /** 用户当前所属部门名称,由 UPMS 用户资料回填。 */
+ private String userDeptName;
+
+ /** 用户所属部门展示名称,供用户信息单元格读取。 */
+ private String deptName;
+
+ /** 用户拥有的 UPMS 身份协议编码集合。 */
+ private java.util.List identityTypes;
+
+ /** 用户的学生身份扩展资料;无学生身份时为空。 */
+ private SysUserStudentDto studentInfo;
+
+ /** 用户的老师身份扩展资料;无老师身份时为空。 */
+ private SysUserTeacherDto teacherInfo;
+
+ /** 获取 UPMS 返回的用户当前部门主键。 */
+ @Override
+ public Long getUserDeptId() {
+ return userDeptId;
+ }
+
+ /** 保存 UPMS 返回的用户当前部门主键。 */
+ @Override
+ public void setUserDeptId(Long userDeptId) {
+ this.userDeptId = userDeptId;
+ }
+
+ /** 获取 UPMS 返回的用户当前部门名称。 */
+ @Override
+ public String getUserDeptName() {
+ return userDeptName;
+ }
+
+ /** 保存 UPMS 返回的用户当前部门名称,并同步公共展示字段。 */
+ @Override
+ public void setUserDeptName(String userDeptName) {
+ this.userDeptName = userDeptName;
+ this.deptName = userDeptName;
+ }
+
/**
* 设备相关
*/
diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardCanteenApptTotalController.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardCanteenApptTotalController.java
index 26048912..b89696d7 100644
--- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardCanteenApptTotalController.java
+++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardCanteenApptTotalController.java
@@ -8,8 +8,6 @@ import com.bsd.pjcloud.common.core.constant.enums.MerchantTypeEnum;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.excel.annotation.ResponseExcel;
import com.bsd.pjcloud.common.log.annotation.SysLog;
-import com.bsd.pjcloud.common.security.service.PjcloudUser;
-import com.bsd.pjcloud.common.security.util.SecurityUtils;
import com.bsd.pjcloud.hard.api.constant.order.HardOrderTypeEnum;
import com.bsd.pjcloud.hard.api.dto.order.HardCanteenUserApptQueryDto;
import com.bsd.pjcloud.hard.api.dto.order.HardOrderDto;
@@ -18,11 +16,8 @@ import com.bsd.pjcloud.hard.api.vo.order.HardCanteenUserApptOrderVo;
import com.bsd.pjcloud.hard.api.vo.order.HardCanteenUserApptVo;
import com.bsd.pjcloud.hard.api.vo.order.HardCanteenUserApptStatisticsVo;
import com.bsd.pjcloud.hard.api.vo.order.excel.HardCanteenUserApptExcelVo;
-import com.bsd.pjcloud.hard.api.vo.order.excel.HardCanteenProductApptExcelVo;
-import com.bsd.pjcloud.hard.excel.HardCanteenProductApptExportMergeHandler;
import com.bsd.pjcloud.hard.service.HardCanteenApptTotalService;
import com.bsd.pjcloud.hard.service.HardCanteenApptTotalExportService;
-import com.bsd.pjcloud.hard.service.HardCanteenTotalService;
import com.bsd.pjcloud.wallet.api.dto.MchManageDto;
import com.bsd.pjcloud.wallet.api.feign.RemoteMchService;
import com.bsd.pjcloud.wallet.api.vo.MchManageVo;
@@ -55,9 +50,6 @@ import java.util.List;
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class HardCanteenApptTotalController {
- /** 无可见食堂时使用的不可命中商户主键,避免空集合被 SQL 当作“不筛选”而造成越权导出。 */
- private static final Long NO_VISIBLE_CANTEEN_ID = Long.MIN_VALUE;
-
/**
* 食堂预约统计服务,负责菜品和用户维度的预约聚合。
*/
@@ -68,11 +60,6 @@ public class HardCanteenApptTotalController {
*/
private final HardCanteenApptTotalExportService canteenApptTotalExportService;
- /**
- * 通用食堂统计服务,复用部门聚合以及商户、部门信息补全能力。
- */
- private final HardCanteenTotalService canteenTotalService;
-
/**
* 商户远程服务,用于限定 App 食堂范围。
*/
@@ -106,47 +93,6 @@ public class HardCanteenApptTotalController {
/* ************************************* end app end ************************************* */
- /**
- * 按预约使用日期统计食堂预约部门。
- *
- * @param dto 预约统计筛选条件,其中 {@code apptUseDateTimeList} 表示预约使用日期范围
- * @return 按食堂聚合并包含部门、用餐时间段明细的预约统计结果
- */
- @Operation(summary = "食堂预约部门统计", description = "按预约使用日期统计食堂预约部门")
- @ApiCryptoAes
- @PostMapping("/list-by-dept")
- public R> listByDept(@RequestBody HardOrderDto dto) {
- // 预约统计必须以预约使用日期为唯一时间口径,避免调用方残留支付时间条件导致交叉筛选。
- dto.setPayTimeList(null);
- dto.setOrderType(HardOrderTypeEnum.RESERVE.getType());
- dto.setTotalIsFindChildren(true);
- dto.setApptIdOrApptRuleIdNotNull(true);
- PjcloudUser user = SecurityUtils.getUser();
- return R.ok(canteenTotalService.listByDept(dto, user, null));
- }
-
- /**
- * 分页查询后台食堂菜品预约统计。
- *
- * @param page 分页参数
- * @param dto 预约订单筛选条件
- * @return 菜品预约统计分页
- */
- @Operation(summary = "食堂菜品预约统计", description = "食堂菜品预约统计")
- @PostMapping("/proudct-reserv-total")
- public R> productReservTotal(@ParameterObject Page page, @RequestBody HardOrderDto dto) {
- // 查询商户
- MchManageDto mchManageDto = new MchManageDto();
- mchManageDto.setTypesList(List.of(MerchantTypeEnum.CANTEEN.getType()));
- List mchList = remoteMchService.scopeList(mchManageDto).getData();
- if (ObjectUtil.isNotEmpty(mchList) && ObjectUtil.isEmpty(dto.getMchIds())) {
- dto.setMchIds(mchList.stream().map(MchManageVo::getId).toList());
- }
- dto.setOrderType(HardOrderTypeEnum.RESERVE.getType());
- dto.setApptIdOrApptRuleIdNotNull(true);
- return R.ok(canteenApptTotalService.productReservTotal(page, dto, null));
- }
-
/**
* 分页查询用户在指定用餐日期范围内每天的预约情况。
*
@@ -228,67 +174,4 @@ public class HardCanteenApptTotalController {
return canteenApptTotalExportService.userReservationExport(query);
}
- /**
- * 提交菜品预约统计异步导出任务。
- *
- * @param query 菜品预约日期、餐段、食堂和部门筛选条件
- * @return 供前端轮询的初始任务进度
- */
- @Operation(summary = "菜品预约统计异步导出", description = "按日期分批生成菜品预约三级统计 Excel")
- @SysLog("菜品预约统计异步导出")
- @PreAuthorize("@pms.hasPermission('hard_canteenProductApptTotal_export')")
- @PostMapping("/product-reserv-export-task")
- public R submitProductReservationExport(@RequestBody HardOrderDto query) {
- applyCanteenMerchantScope(query);
- return R.ok(canteenApptTotalExportService.submitProductReservationExport(query), "菜品预约导出任务已提交");
- }
-
- /**
- * 同步菜品预约统计导出接口,保留给已有调用方;管理端页面使用异步任务接口。
- *
- * @param query 菜品预约统计筛选条件
- * @return 按日期餐段、商户部门和商品三级组织的自定义 Excel 行
- */
- @Operation(summary = "菜品预约统计导出", description = "导出日期餐段、商户部门和商品三级菜品预约统计")
- @SysLog("菜品预约统计导出")
- @PreAuthorize("@pms.hasPermission('hard_canteenProductApptTotal_export')")
- @ResponseExcel(name = "菜品预约统计", inMemory = true, writeHandler = HardCanteenProductApptExportMergeHandler.class)
- @PostMapping("/product-reserv-export")
- public List productReservationExport(@RequestBody HardOrderDto query) {
- applyCanteenMerchantScope(query);
- return canteenApptTotalExportService.productReservationExport(query);
- }
-
- /**
- * 为后台菜品预约导出补充当前操作者可见的食堂范围。
- *
- * 分页查询接口已有相同约束;导出任务在异步线程执行前必须冻结这份范围,避免未选择食堂时
- * 直接按空 {@code mchIds} 查询到当前权限之外的商户数据。
- *
- * @param query 菜品预约导出的请求条件;为空时保留给导出服务做参数校验
- */
- private void applyCanteenMerchantScope(HardOrderDto query) {
- if (query == null) {
- return;
- }
- MchManageDto mchManageDto = new MchManageDto();
- mchManageDto.setTypesList(List.of(MerchantTypeEnum.CANTEEN.getType()));
- R> scopeResponse = remoteMchService.scopeList(mchManageDto);
- List mchList = scopeResponse == null ? null : scopeResponse.getData();
- List visibleMerchantIds = ObjectUtil.isEmpty(mchList) ? List.of()
- : mchList.stream().map(MchManageVo::getId).filter(ObjectUtil::isNotEmpty).distinct().toList();
- if (ObjectUtil.isEmpty(visibleMerchantIds)) {
- query.setMchIds(List.of(NO_VISIBLE_CANTEEN_ID));
- return;
- }
- if (ObjectUtil.isEmpty(query.getMchIds())) {
- query.setMchIds(visibleMerchantIds);
- return;
- }
- // 即使调用方手工携带 mchIds,也只能导出当前操作者实际可见的食堂。
- List allowedMerchantIds = query.getMchIds().stream().filter(visibleMerchantIds::contains).toList();
- query.setMchIds(ObjectUtil.isEmpty(allowedMerchantIds)
- ? List.of(NO_VISIBLE_CANTEEN_ID) : allowedMerchantIds);
- }
-
}
diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardCanteenDeptTotalController.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardCanteenDeptTotalController.java
index 0b509485..1841b6cc 100644
--- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardCanteenDeptTotalController.java
+++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardCanteenDeptTotalController.java
@@ -2,6 +2,7 @@ package com.bsd.pjcloud.hard.controller.order;
import com.bsd.pjcloud.common.api.encrypt.annotation.crypto.ApiCryptoAes;
+import com.bsd.pjcloud.common.asynctask.vo.AsyncTaskProgressVO;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.data.datascope.DataScope;
import com.bsd.pjcloud.common.excel.annotation.ResponseExcel;
@@ -10,7 +11,8 @@ import com.bsd.pjcloud.common.security.service.PjcloudUser;
import com.bsd.pjcloud.common.security.util.SecurityUtils;
import com.bsd.pjcloud.hard.api.dto.order.HardOrderDto;
import com.bsd.pjcloud.hard.api.vo.order.HardCanteenProductTotalVo;
-import com.bsd.pjcloud.hard.api.vo.order.excel.HardCanteenDeptTotalExcelVo;
+import com.bsd.pjcloud.hard.api.vo.order.excel.HardCanteenProductApptExcelVo;
+import com.bsd.pjcloud.hard.excel.HardCanteenProductApptExportMergeHandler;
import com.bsd.pjcloud.hard.service.HardCanteenTotalService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
@@ -58,11 +60,25 @@ public class HardCanteenDeptTotalController {
@Operation(summary = "食堂菜品部门统计导出", description = "按日期、食堂、来源、部门和餐段导出销售统计")
@SysLog("食堂菜品部门统计导出")
@PreAuthorize("@pms.hasPermission('hard_canteenDeptTotal_export')")
- @ResponseExcel(name = "食堂菜品部门统计")
+ @ResponseExcel(name = "食堂菜品部门统计", inMemory = true,
+ writeHandler = HardCanteenProductApptExportMergeHandler.class)
@PostMapping("/export-dept")
- public List exportDept(@RequestBody HardOrderDto dto) {
- PjcloudUser user = SecurityUtils.getUser();
- return canteenTotalService.exportDept(dto, user, null);
+ public List exportDept(@RequestBody HardOrderDto dto) {
+ return canteenTotalService.exportSales(dto, null);
+ }
+
+ /**
+ * 提交食堂部门销售统计异步导出任务。
+ *
+ * @param dto 来源统计日期、来源、食堂、餐段和部门筛选
+ * @return 可供前端轮询的初始任务进度,成功终态包含文件下载地址
+ */
+ @Operation(summary = "食堂菜品部门统计异步导出", description = "按自然日分批查询并流式生成部门销售统计 Excel")
+ @SysLog("食堂菜品部门统计异步导出")
+ @PreAuthorize("@pms.hasPermission('hard_canteenDeptTotal_export')")
+ @PostMapping("/export-dept-task")
+ public R submitDepartmentSalesExport(@RequestBody HardOrderDto dto) {
+ return R.ok(canteenTotalService.submitDepartmentSalesExport(dto), "食堂菜品部门统计导出任务已提交");
}
}
diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardCanteenTotalController.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardCanteenTotalController.java
index d0ae0980..22ed964e 100644
--- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardCanteenTotalController.java
+++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardCanteenTotalController.java
@@ -1,6 +1,7 @@
package com.bsd.pjcloud.hard.controller.order;
import com.bsd.pjcloud.common.api.encrypt.annotation.crypto.ApiCryptoAes;
+import com.bsd.pjcloud.common.asynctask.vo.AsyncTaskProgressVO;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -23,7 +24,8 @@ import com.bsd.pjcloud.hard.api.vo.order.HardCarVo;
import com.bsd.pjcloud.hard.api.vo.order.app.HardAppProductByCanteenVo;
import com.bsd.pjcloud.hard.api.vo.order.app.HardAppProductByClassifyVo;
import com.bsd.pjcloud.hard.api.vo.order.app.HardAppProductLikeTopVo;
-import com.bsd.pjcloud.hard.api.vo.order.excel.HardCanteenProductTotalExcelVo;
+import com.bsd.pjcloud.hard.api.vo.order.excel.HardCanteenProductApptExcelVo;
+import com.bsd.pjcloud.hard.excel.HardCanteenProductApptExportMergeHandler;
import com.bsd.pjcloud.hard.service.HardCanteenTotalService;
import com.bsd.pjcloud.wallet.api.dto.MchManageDto;
import com.bsd.pjcloud.wallet.api.feign.RemoteMchService;
@@ -150,17 +152,28 @@ public class HardCanteenTotalController {
*/
@Operation(summary = "食堂菜品销售统计导出", description = "按日期、餐段和来源导出食堂菜品销售统计")
@SysLog("食堂菜品销售统计导出")
- @PreAuthorize("@pms.hasPermission('hard_canteenProductTotal_export')")
- @ResponseExcel(name = "食堂菜品销售统计")
+ @PreAuthorize("@pms.hasPermission('hard_canteenProductTotal_export') or "
+ + "@pms.hasPermission('hard_canteenProductApptTotal_export')")
+ @ResponseExcel(name = "食堂菜品销售统计", inMemory = true,
+ writeHandler = HardCanteenProductApptExportMergeHandler.class)
@PostMapping("/export-product")
- public List exportProduct(@RequestBody HardOrderDto dto) {
- MchManageDto mchManageDto = new MchManageDto();
- mchManageDto.setTypesList(List.of(MerchantTypeEnum.CANTEEN.getType()));
- List mchList = remoteMchService.scopeList(mchManageDto).getData();
- if (ObjectUtil.isNotEmpty(mchList) && ObjectUtil.isEmpty(dto.getMchIds())) {
- dto.setMchIds(mchList.stream().map(MchManageVo::getId).toList());
- }
- return canteenTotalService.exportProduct(dto, null);
+ public List exportProduct(@RequestBody HardOrderDto dto) {
+ return canteenTotalService.exportSales(dto, null);
+ }
+
+ /**
+ * 提交食堂菜品销售统计异步导出任务。
+ *
+ * @param dto 来源统计日期、来源、食堂、餐段和部门筛选
+ * @return 可供前端轮询的初始任务进度,成功终态包含文件下载地址
+ */
+ @Operation(summary = "食堂菜品销售统计异步导出", description = "按自然日分批查询并流式生成销售统计 Excel")
+ @SysLog("食堂菜品销售统计异步导出")
+ @PreAuthorize("@pms.hasPermission('hard_canteenProductTotal_export') or "
+ + "@pms.hasPermission('hard_canteenProductApptTotal_export')")
+ @PostMapping("/export-product-task")
+ public R submitProductSalesExport(@RequestBody HardOrderDto dto) {
+ return R.ok(canteenTotalService.submitProductSalesExport(dto), "食堂菜品销售统计导出任务已提交");
}
/**
diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/excel/HardCanteenProductApptExportMergeHandler.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/excel/HardCanteenProductApptExportMergeHandler.java
index fa9811af..7e2d5497 100644
--- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/excel/HardCanteenProductApptExportMergeHandler.java
+++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/excel/HardCanteenProductApptExportMergeHandler.java
@@ -47,7 +47,7 @@ public class HardCanteenProductApptExportMergeHandler implements RowWriteHandler
/** 日期分组边框线型,参考模板使用中等线宽突出每天的数据边界。 */
private static final BorderStyle DATE_GROUP_BORDER_STYLE = BorderStyle.MEDIUM;
- /** 日期分组边框颜色,按预约日期顺序交替使用蓝色和橙色。 */
+ /** 日期分组边框颜色,按统计日期顺序交替使用蓝色和橙色。 */
private static final byte[][] DATE_GROUP_BORDER_COLORS = {
{0x5B, (byte) 0x9B, (byte) 0xD5},
{(byte) 0xED, 0x7D, 0x31}
@@ -75,13 +75,13 @@ public class HardCanteenProductApptExportMergeHandler implements RowWriteHandler
/** 最近写出的数据行号,用于工作簿结束时收尾最后一组。 */
private int lastDataRow = -1;
- /** 当前预约日期分组序号,同一天的所有餐段共用一个序号和边框颜色。 */
+ /** 当前统计日期分组序号,同一天的所有餐段和来源块共用一个序号和边框颜色。 */
private int dateGroupIndex = -1;
- /** 当前预约日期的单元格值标识,用于判断新餐段是否仍属于同一天。 */
+ /** 当前统计日期的单元格值标识,用于判断新餐段或来源块是否仍属于同一天。 */
private String currentDateKey;
- /** 当前导出工作表;本处理器只注册到菜品预约导出的单个工作表。 */
+ /** 当前导出工作表;本处理器只注册到菜品销售统计导出的单个工作表。 */
private Sheet currentSheet;
/**
@@ -203,14 +203,14 @@ public class HardCanteenProductApptExportMergeHandler implements RowWriteHandler
}
/**
- * 为当前预约日期设置整组外框:当天首个餐段首行加顶部边框,当天所有行加左右边框,末行加底部边框。
+ * 为当前统计日期设置整组外框:当天首个来源块首行加顶部边框,当天所有行加左右边框,末行加底部边框。
*
* 流式工作簿会刷出较早的数据行,分组结束时无法可靠回读整组行,因此左右边框必须随行写出;
* 新日期开始时,最近写出的上一天末行仍在流式窗口内,可补齐上一天的本色底边。
*
* @param row 当前数据行
* @param holder 当前工作簿持有者,用于创建彩色边框样式
- * @param groupIndex 当前预约日期分组序号,未识别到日期时为负数
+ * @param groupIndex 当前统计日期分组序号,未识别到日期时为负数
* @param topBorder 是否为当天首行并设置顶部边框
* @param bottomBorder 是否为当天末行并设置底部边框
*/
@@ -284,10 +284,10 @@ public class HardCanteenProductApptExportMergeHandler implements RowWriteHandler
}
/**
- * 获取日期汇总单元格的稳定值标识;同一预约日期的不同餐段会得到相同标识。
+ * 获取日期汇总单元格的稳定值标识;同一统计日期的不同餐段和来源块会得到相同标识。
*
* @param row 当前日期餐段汇总行
- * @param columnIndex 预约日期所在列索引
+ * @param columnIndex 统计日期所在列索引
* @return 用于判断日期是否变化的非空值标识
*/
private String getCellValueKey(Row row, int columnIndex) {
diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/mapper/HardDoorFlowMapper.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/mapper/HardDoorFlowMapper.java
index 16a98893..8f918f78 100644
--- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/mapper/HardDoorFlowMapper.java
+++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/mapper/HardDoorFlowMapper.java
@@ -20,6 +20,7 @@ public interface HardDoorFlowMapper extends PjcloudBaseMapper wrapper = new MPJLambdaQueryWrapper()
.selectAll(HardDoorFlowEntity.class)
.eq(ObjectUtil.isNotEmpty(dto.getUserId()), HardDoorFlowEntity::getUserId, dto.getUserId())
+ .in(ObjectUtil.isNotEmpty(dto.getUserIds()), HardDoorFlowEntity::getUserId, dto.getUserIds())
.eq(ObjectUtil.isNotEmpty(dto.getBizType()), HardDoorFlowEntity::getBizType, dto.getBizType())
.eq(ObjectUtil.isNotEmpty(dto.getHardDoorId()), HardDoorFlowEntity::getHardDoorId, dto.getHardDoorId())
.eq(ObjectUtil.isNotEmpty(dto.getCabinetNo()), HardDoorFlowEntity::getCabinetNo, dto.getCabinetNo())
@@ -35,4 +36,4 @@ public interface HardDoorFlowMapper extends PjcloudBaseMapper
Page scopePageList(Page page, @Param("dto") HardInvoiceDto dto, @Param("dataScope") DataScope dataScope);
-}
\ No newline at end of file
+}
diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/mapper/order/HardCarMapper.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/mapper/order/HardCarMapper.java
index ef4e2303..9a1e5485 100644
--- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/mapper/order/HardCarMapper.java
+++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/mapper/order/HardCarMapper.java
@@ -49,8 +49,8 @@ public interface HardCarMapper extends PjcloudBaseMapper {
/**
* 查询菜品多维统计行且不应用数据库分页。
*
- * 同一次查询通过 ROLLUP 返回当日合计、餐段合计和餐段来源三层数据,保证交易人数在每个层级独立去重;
- * 服务层按完整自然日分页,导出仅选择餐段来源层。
+ * 同一次查询通过统一来源 CTE 返回当日合计、来源合计、餐段合计和餐段来源四层数据,
+ * 保证交易人数在每个层级独立去重;服务层按完整自然日分页。
*
* @param dto 统计日期、来源、食堂、餐段和部门筛选
* @param dataScope 订单所属部门数据范围;为空时不附加范围条件
@@ -70,28 +70,15 @@ public interface HardCarMapper extends PjcloudBaseMapper {
Page productTotalByApptUseDate(Page page, @Param("dto") HardOrderDto dto, DataScope dataScope);
/**
- * 查询菜品销售三级导出的日期餐段汇总及来源拆分。
+ * 一次查询菜品销售三级导出的汇总、商户部门和商品聚合行。
+ *
+ * SQL 只构造一次来源 CTE,再按记录层级分别聚合,避免三个查询重复扫描订单、购物车和退款数据。
*
* @param dto 统计日期、来源、餐段、食堂和部门筛选条件
- * @return 每个日期餐段的跨来源合计及各来源统计,交易人数在当前层级独立去重
+ * @param dataScope 订单所属部门数据范围;为空时不附加范围条件
+ * @return 按层级、日期、餐段、来源、商户部门和商品稳定排序的三级聚合行
*/
- List listSalesExportSummary(@Param("dto") HardOrderDto dto);
-
- /**
- * 查询菜品销售三级导出的商户部门汇总及来源拆分。
- *
- * @param dto 统计日期、来源、餐段、食堂和部门筛选条件
- * @return 每个日期餐段下商户部门的跨来源合计及各来源统计
- */
- List listSalesExportMerchant(@Param("dto") HardOrderDto dto);
-
- /**
- * 查询菜品销售三级导出的商品汇总及来源拆分。
- *
- * @param dto 统计日期、来源、餐段、食堂和部门筛选条件
- * @return 每个商户部门块下商品的跨来源合计及各来源统计
- */
- List listSalesExportProduct(@Param("dto") HardOrderDto dto);
+ List listSalesExportRows(@Param("dto") HardOrderDto dto, DataScope dataScope);
Page proudctQuery(Page page, @Param("dto") HardCarDto dto);
diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardCanteenApptTotalExportService.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardCanteenApptTotalExportService.java
index 53172794..2ec1b525 100644
--- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardCanteenApptTotalExportService.java
+++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardCanteenApptTotalExportService.java
@@ -2,8 +2,6 @@ package com.bsd.pjcloud.hard.service;
import com.bsd.pjcloud.common.asynctask.vo.AsyncTaskProgressVO;
import com.bsd.pjcloud.hard.api.dto.order.HardCanteenUserApptQueryDto;
-import com.bsd.pjcloud.hard.api.dto.order.HardOrderDto;
-import com.bsd.pjcloud.hard.api.vo.order.excel.HardCanteenProductApptExcelVo;
import com.bsd.pjcloud.hard.api.vo.order.excel.HardCanteenUserApptExcelVo;
import java.util.List;
@@ -31,20 +29,4 @@ public interface HardCanteenApptTotalExportService {
* @return 用户预约导出行
*/
List userReservationExport(HardCanteenUserApptQueryDto query);
-
- /**
- * 提交菜品预约异步导出任务。
- *
- * @param query 菜品预约统计筛选条件
- * @return 可供前端轮询的初始任务进度
- */
- AsyncTaskProgressVO submitProductReservationExport(HardOrderDto query);
-
- /**
- * 查询菜品预约自定义 Excel 行,供同步下载和异步写入复用。
- *
- * @param query 菜品预约统计筛选条件
- * @return 按日期餐段、商户部门、商品三级结构排列的导出行
- */
- List productReservationExport(HardOrderDto query);
}
diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardCanteenTotalService.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardCanteenTotalService.java
index 7502b74e..ab70e8e0 100644
--- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardCanteenTotalService.java
+++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardCanteenTotalService.java
@@ -2,6 +2,7 @@ package com.bsd.pjcloud.hard.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.bsd.pjcloud.common.asynctask.vo.AsyncTaskProgressVO;
import com.bsd.pjcloud.common.data.datascope.DataScope;
import com.bsd.pjcloud.common.security.service.PjcloudUser;
import com.bsd.pjcloud.hard.api.dto.order.HardCarDto;
@@ -78,4 +79,20 @@ public interface HardCanteenTotalService {
* @return 日期餐段、商户部门、商品三级排列的导出行
*/
List exportSales(HardOrderDto dto, DataScope dataScope);
+
+ /**
+ * 提交食堂菜品销售统计异步导出任务。
+ *
+ * @param dto 统计日期、来源、餐段、食堂和部门筛选条件
+ * @return 可供前端轮询的初始任务进度,成功终态包含文件下载地址
+ */
+ AsyncTaskProgressVO submitProductSalesExport(HardOrderDto dto);
+
+ /**
+ * 提交食堂部门销售统计异步导出任务。
+ *
+ * @param dto 统计日期、来源、餐段、食堂和部门筛选条件
+ * @return 可供前端轮询的初始任务进度,成功终态包含文件下载地址
+ */
+ AsyncTaskProgressVO submitDepartmentSalesExport(HardOrderDto dto);
}
diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardAuthServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardAuthServiceImpl.java
index ee9821de..c0fd9052 100644
--- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardAuthServiceImpl.java
+++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardAuthServiceImpl.java
@@ -34,6 +34,7 @@ import com.bsd.pjcloud.hard.api.vo.order.HardAppCreateThirdDoorOrderVo;
import com.bsd.pjcloud.hard.mapper.HardAuthMapper;
import com.bsd.pjcloud.hard.mapper.manager.HardManagerMapper;
import com.bsd.pjcloud.hard.service.*;
+import com.bsd.pjcloud.hard.support.HardUserIdentitySupport;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -62,8 +63,12 @@ public class HardAuthServiceImpl extends ServiceImpl scopePageJoin(Page page, HardAuthDto dto, DataScope dataScope) {
+ prepareIdentityQuery(dto);
Page pageList = baseMapper.scopePageJoin(page, dto, dataScope);
disposeList(pageList.getRecords(), dto);
return pageList;
@@ -237,14 +242,18 @@ public class HardAuthServiceImpl extends ServiceImpl userIds = list.stream().map(HardAuthVo::getUserId).distinct().toList();
- if (ObjectUtil.isNotEmpty(userIds)) {
- R> userList = remoteUserService.getUserDeptByIds(new ArrayList<>(userIds));
- if (ObjectUtil.isNotEmpty(userList.getData())) {
- DataUtil.convertUserDeptList(list, CollectionUtils.convertMap(userList.getData(), UserVO::getUserId));
- }
- }
+ userIdentitySupport.fillUserInfo(list, true);
+ }
+
+ /** 将授权用户身份条件预筛选为授权记录可直接使用的用户主键集合。 */
+ private void prepareIdentityQuery(HardAuthDto dto) {
+ if (!userIdentitySupport.hasIdentityQuery(dto)) return;
+ dto.setUserIds(userIdentitySupport.findIdentityUserIds(dto, dto.getUserId(), dto.getUserIds()));
+ dto.setUserId(null);
+ dto.setIdentityTypes(null);
+ dto.setStudentInfo(null);
+ dto.setTeacherInfo(null);
+ dto.setHasFace(null);
}
/**
@@ -268,4 +277,4 @@ public class HardAuthServiceImpl extends ServiceImpl scopePageJoin(Page page, HardBroadcastDto dto, DataScope dataScope) {
+ prepareIdentityQuery(dto);
Page pageList = baseMapper.scopePageJoin(page, dto, dataScope);
disposeList(pageList.getRecords(), dto);
return pageList;
@@ -310,14 +315,18 @@ public class HardBroadcastServiceImpl extends ServiceImpl userIds = list.stream().map(HardBroadcastVo::getUserId).distinct().toList();
- if (ObjectUtil.isNotEmpty(userIds)) {
- R> userList = remoteUserService.getUserDeptByIds(new ArrayList<>(userIds));
- if (ObjectUtil.isNotEmpty(userList.getData())) {
- DataUtil.convertUserDeptList(list, CollectionUtils.convertMap(userList.getData(), UserVO::getUserId));
- }
- }
+ userIdentitySupport.fillUserInfo(list, true);
+ }
+
+ /** 将广播预定用户身份条件预筛选为广播记录可直接使用的用户主键集合。 */
+ private void prepareIdentityQuery(HardBroadcastDto dto) {
+ if (!userIdentitySupport.hasIdentityQuery(dto)) return;
+ dto.setUserIds(userIdentitySupport.findIdentityUserIds(dto, dto.getUserId(), dto.getUserIds()));
+ dto.setUserId(null);
+ dto.setIdentityTypes(null);
+ dto.setStudentInfo(null);
+ dto.setTeacherInfo(null);
+ dto.setHasFace(null);
}
}
diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardDoorFlowServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardDoorFlowServiceImpl.java
index eb2a746f..301eeb9b 100644
--- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardDoorFlowServiceImpl.java
+++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardDoorFlowServiceImpl.java
@@ -41,6 +41,7 @@ import com.bsd.pjcloud.hard.mapper.HardDoorFlowMapper;
import com.bsd.pjcloud.hard.mapper.HardThirdMiOrderMapper;
import com.bsd.pjcloud.hard.mapper.manager.HardManagerMapper;
import com.bsd.pjcloud.hard.service.*;
+import com.bsd.pjcloud.hard.support.HardUserIdentitySupport;
import com.bsd.pjcloud.thirdparty.api.feign.RemoterThirdDoorApiService;
import com.bsd.pjcloud.thirdparty.api.feign.RemoterThirdKsService;
import com.bsd.pjcloud.thirdparty.dto.door.*;
@@ -88,11 +89,15 @@ public class HardDoorFlowServiceImpl extends ServiceImpl scopePageJoin(Page page, HardDoorFlowDto dto, DataScope dataScope) {
+ prepareIdentityQuery(dto);
Page pageList = baseMapper.scopePageJoin(page, dto, dataScope);
disposeList(pageList.getRecords(), dto);
return pageList;
@@ -427,14 +432,18 @@ public class HardDoorFlowServiceImpl extends ServiceImpl userIds = list.stream().map(HardDoorFlowVo::getUserId).distinct().toList();
- if (ObjectUtil.isNotEmpty(userIds)) {
- R> userList = remoteUserService.getUserDeptByIds(new ArrayList<>(userIds));
- if (ObjectUtil.isNotEmpty(userList.getData())) {
- DataUtil.convertUserDeptList(list, CollectionUtils.convertMap(userList.getData(), UserVO::getUserId));
- }
- }
+ userIdentitySupport.fillUserInfo(list, true);
+ }
+
+ /** 将开门流程用户身份条件预筛选为流程记录可直接使用的用户主键集合。 */
+ private void prepareIdentityQuery(HardDoorFlowDto dto) {
+ if (!userIdentitySupport.hasIdentityQuery(dto)) return;
+ dto.setUserIds(userIdentitySupport.findIdentityUserIds(dto, dto.getUserId(), dto.getUserIds()));
+ dto.setUserId(null);
+ dto.setIdentityTypes(null);
+ dto.setStudentInfo(null);
+ dto.setTeacherInfo(null);
+ dto.setHasFace(null);
}
private void disposeList(HardDoorFlowVo detail) {
@@ -458,4 +467,4 @@ public class HardDoorFlowServiceImpl extends ServiceImpl scopeJoinPageList(Page page, HardInvoiceDto dto) {
+ prepareIdentityQuery(dto);
Page list = baseMapper.scopePageList(page, dto, DataScope.of());
if (ObjectUtil.isNotEmpty(list.getRecords())) {
// 部门
@@ -102,14 +107,7 @@ public class HardInvoiceServiceImpl extends ServiceImpl userIds = list.getRecords().stream().map(HardInvoiceVo::getOrderUserId).filter(ObjectUtil::isNotEmpty).distinct().toList();
- if (ObjectUtil.isNotEmpty(userIds)) {
- R> userList = userService.getUserById(new ArrayList<>(userIds));
- if (ObjectUtil.isNotEmpty(userList.getData())) {
- DataUtil.convertUserList(list.getRecords(), CollectionUtils.convertMap(userList.getData(), SysUser::getUserId));
- }
- }
+ userIdentitySupport.fillUserInfo(list.getRecords(), true);
// 商户
List mchIds = list.getRecords().stream().map(HardInvoiceVo::getMerchantId).distinct().toList();
if (ObjectUtil.isNotEmpty(mchIds)) {
@@ -122,6 +120,20 @@ public class HardInvoiceServiceImpl extends ServiceImpl pageJoin(Page page, HardAfterSaleDto dto) {
+ prepareIdentityQuery(dto);
Page list = baseMapper.pageJoin(page, dto);
if (ObjectUtil.isNotEmpty(list.getRecords())) {
- // 用户
- List userIds = list.getRecords().stream().map(HardAfterSaleVo::getUserId).filter(ObjectUtil::isNotEmpty).distinct().toList();
- if (ObjectUtil.isNotEmpty(userIds)) {
- R> userList = userService.getUserById(new ArrayList<>(userIds));
- if (ObjectUtil.isNotEmpty(userList.getData())) {
- DataUtil.convertUserList(list.getRecords(), CollectionUtils.convertMap(userList.getData(), SysUser::getUserId));
- }
- }
+ userIdentitySupport.fillUserInfo(list.getRecords(), true);
// 部门
List deptIds = list.getRecords().stream().map(HardAfterSaleVo::getDeptId).filter(ObjectUtil::isNotEmpty).distinct().toList();
if (ObjectUtil.isNotEmpty(deptIds)) {
@@ -151,16 +149,10 @@ public class HardAfterSaleServiceImpl extends ServiceImpl scopePageList(Page page, HardAfterSaleDto dto) {
+ prepareIdentityQuery(dto);
Page list = baseMapper.scopePageJoinList(page, dto, null);
if (ObjectUtil.isNotEmpty(list.getRecords())) {
- // 用户
- List userIds = list.getRecords().stream().map(HardAfterSaleVo::getUserId).filter(ObjectUtil::isNotEmpty).distinct().toList();
- if (ObjectUtil.isNotEmpty(userIds)) {
- R> userList = userService.getUserById(new ArrayList<>(userIds));
- if (ObjectUtil.isNotEmpty(userList.getData())) {
- DataUtil.convertUserList(list.getRecords(), CollectionUtils.convertMap(userList.getData(), SysUser::getUserId));
- }
- }
+ userIdentitySupport.fillUserInfo(list.getRecords(), true);
// 部门
List deptIds = list.getRecords().stream().map(HardAfterSaleVo::getDeptId).filter(ObjectUtil::isNotEmpty).distinct().toList();
if (ObjectUtil.isNotEmpty(deptIds)) {
@@ -174,6 +166,21 @@ public class HardAfterSaleServiceImpl extends ServiceImpl菜品导出以预约使用日期为批次边界,每完成一个日期就推进一次 Redis 进度;Excel 数据始终通过
- * Easy Excel 追加写入,避免把整个日期范围的商品明细一次性保留在内存中。
+ * 按权限用户分批查询每日预约并流式写入 Excel,避免长日期范围和大量用户一次性占用过多内存。
*/
@Slf4j
@Validated
@@ -131,33 +120,9 @@ public class HardCanteenApptTotalExportServiceImpl implements HardCanteenApptTot
private static final DateTimeFormatter EXCEL_DATE_TIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
- /** 菜品预约导出进度的 Redis Key 命名空间。 */
- private static final String PRODUCT_EXPORT_REDIS_KEY = "hard:canteen-product-appt:export";
-
- /** 菜品预约导出任务名称。 */
- private static final String PRODUCT_EXPORT_TASK_NAME = "菜品预约统计导出";
-
- /** 异步任务创建时的占位总数,真实日期数量在后台查询后更新。 */
- private static final int INITIAL_PROGRESS_TOTAL = 1;
-
- /** 菜品预约导出工作表名称。 */
- private static final String PRODUCT_EXPORT_SHEET_NAME = "菜品预约统计";
-
- /** 文件中心存储目录。 */
- private static final String EXPORT_FILE_DIR = "hard-export";
-
- /** 文件中心业务类型编码。 */
- private static final String PRODUCT_EXPORT_FILE_TYPE = "HARD_CANTEEN_PRODUCT_APPT_EXPORT";
-
- /** Excel 文件 MIME 类型。 */
- private static final String EXCEL_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
-
/** 用餐时间段字典类型。 */
private static final String MEAL_SEGMENT_DICT_TYPE = "daily_menu";
- /** Excel 文件名时间格式。 */
- private static final DateTimeFormatter FILE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
-
/** 用户预约查询允许的最大自然月跨度,开始和结束日期均计入查询范围。 */
private static final long MAX_QUERY_MONTHS = 2L;
@@ -206,9 +171,6 @@ public class HardCanteenApptTotalExportServiceImpl implements HardCanteenApptTot
/** 菜品预约聚合 Mapper。 */
private final HardCarMapper hardCarMapper;
- /** 每日菜谱远程服务,用于展开套餐组成。 */
- private final RemoteDailyMenuService remoteDailyMenuService;
-
/** 部门远程服务,用于补齐预约人所属部门名称。 */
private final RemoteDeptService remoteDeptService;
@@ -1303,459 +1265,4 @@ public class HardCanteenApptTotalExportServiceImpl implements HardCanteenApptTot
}
}
- /**
- * 提交菜品预约异步导出任务。
- *
- * @param query 菜品预约统计筛选条件
- * @return 初始异步任务进度
- */
- @Override
- public AsyncTaskProgressVO submitProductReservationExport(HardOrderDto query) {
- HardOrderDto querySnapshot = copyProductQuery(query);
- return asyncTaskExecutorService.submitWithResult(PRODUCT_EXPORT_REDIS_KEY, PRODUCT_EXPORT_TASK_NAME,
- INITIAL_PROGRESS_TOTAL, reporter -> exportProducts(querySnapshot, reporter));
- }
-
- /**
- * 同步查询菜品预约自定义 Excel 行。
- *
- * @param query 菜品预约统计筛选条件
- * @return 三级层级导出行
- */
- @Override
- public List productReservationExport(HardOrderDto query) {
- HardOrderDto querySnapshot = copyProductQuery(query);
- List rows = new ArrayList<>();
- for (LocalDate date : getQueryDates(querySnapshot)) {
- HardOrderDto dateQuery = copyProductQuery(querySnapshot);
- dateQuery.setApptUseDateTimeList(new LocalDateTime[]{date.atStartOfDay(), date.atTime(23, 59, 59)});
- rows.addAll(buildProductRows(dateQuery, date));
- }
- return rows;
- }
-
- /**
- * 后台按日期批次生成菜品预约工作簿并上传文件中心。
- *
- * @param query 后台线程独占的查询快照
- * @param reporter 当前异步任务进度报告器
- * @return 文件中心结果
- */
- private AsyncTaskResult exportProducts(HardOrderDto query, AsyncTaskProgressReporter reporter) {
- List dates = getQueryDates(query);
- reporter.updateTotal(Math.max(1, dates.size() + 1), "已确定 " + dates.size() + " 个用餐日期,开始分批导出");
- Path outputPath = null;
- String fileName = "菜品预约统计_" + LocalDateTime.now().format(FILE_TIME_FORMATTER) + ".xlsx";
- try {
- outputPath = Files.createTempFile("canteen-product-appt-export-", ".xlsx");
- long rowCount;
- try (ExcelWriter writer = EasyExcel.write(outputPath.toFile(), HardCanteenProductApptExcelVo.class)
- .registerWriteHandler(new HardCanteenProductApptExportMergeHandler()).build()) {
- WriteSheet sheet = EasyExcel.writerSheet(PRODUCT_EXPORT_SHEET_NAME).build();
- rowCount = 0L;
- for (LocalDate date : dates) {
- HardOrderDto dateQuery = copyProductQuery(query);
- dateQuery.setApptUseDateTimeList(new LocalDateTime[]{date.atStartOfDay(), date.atTime(23, 59, 59)});
- List batchRows = buildProductRows(dateQuery, date);
- if (!batchRows.isEmpty()) {
- writer.write(batchRows, sheet);
- rowCount += batchRows.size();
- }
- reporter.advance("已处理 " + date + ",写入 " + rowCount + " 行");
- }
- if (rowCount == 0L) {
- throw new IllegalStateException("没有符合当前条件的菜品预约数据");
- }
- }
- String fileUrl = upload(outputPath, fileName);
- reporter.advance("菜品预约导出文件已生成");
- return AsyncTaskResult.file(fileUrl, fileName);
- } catch (IllegalStateException exception) {
- throw exception;
- } catch (Exception exception) {
- log.error("菜品预约统计导出失败,fileName:{}", fileName, exception);
- throw new IllegalStateException("生成菜品预约导出文件失败", exception);
- } finally {
- if (outputPath != null) {
- try {
- Files.deleteIfExists(outputPath);
- } catch (Exception exception) {
- log.warn("删除菜品预约导出临时文件失败,path:{}", outputPath, exception);
- }
- }
- }
- }
-
- /**
- * 查询一个日期批次的三层数据并转换为 Excel 行。
- *
- * @param query 日期批次查询条件
- * @param fixedDate 已知的批次日期;同步查询传空时从查询结果读取
- * @return 当前批次的汇总、商户部门、商品明细和商品统计行
- */
- private List buildProductRows(HardOrderDto query, LocalDate fixedDate) {
- List summaries = hardCarMapper.listApptExportSummary(query);
- List merchants = hardCarMapper.listApptExportMerchant(query);
- List products = hardCarMapper.listApptExportProduct(query);
- if (summaries == null || summaries.isEmpty()) {
- return List.of();
- }
- Map merchantNames = fetchMerchantNames(merchants);
- Map departmentNames = fetchDepartmentNames(merchants);
- Map hourNames = fetchHourNames();
- Map menuMap = fetchDailyMenus(products, fixedDate);
- Map> merchantGroups = merchants.stream()
- .collect(Collectors.groupingBy(this::summaryGroupKey, LinkedHashMap::new, Collectors.toList()));
- Map> productGroups = products.stream()
- .collect(Collectors.groupingBy(this::groupKey, LinkedHashMap::new, Collectors.toList()));
- List rows = new ArrayList<>();
- for (HardCanteenProductTotalVo summary : summaries) {
- LocalDate date = fixedDate != null ? fixedDate
- : summary.getCreateTime() == null ? null : summary.getCreateTime().toLocalDate();
- if (date == null) {
- log.warn("菜品预约导出汇总记录缺少预约日期,hourType:{},跳过该记录", summary.getHourType());
- continue;
- }
- String hourType = summary.getHourType();
- String summaryKey = date + "|" + hourType;
- List merchantRows = merchantGroups.getOrDefault(summaryKey, List.of());
- Map> detailRowsByMerchant = new LinkedHashMap<>();
- Map detailCountsByMerchant = new HashMap<>();
- Map detailPricesByMerchant = new HashMap<>();
- long summaryProductCount = 0L;
- long summaryTotalPrice = 0L;
- for (HardCanteenProductApptExportRecord merchant : merchantRows) {
- String merchantKey = groupKey(merchant);
- List detailRows = productGroups
- .getOrDefault(merchantKey, List.of()).stream()
- .flatMap(product -> toProductRows(product,
- menuMap.get(menuKey(product, fixedDate != null ? fixedDate : date))).stream())
- .toList();
- List statisticRows = buildProductStatistics(detailRows);
- long detailCount = detailRows.stream().mapToLong(detail -> valueOrZero(detail.getProductCount())).sum();
- long detailPrice = detailRows.stream().mapToLong(detail -> yuanToCents(detail.getProductTotalPrice())).sum();
- // 每日菜谱查询失败时仍保留 SQL 的有效预约统计,避免导出因远程菜谱暂时不可用而丢失金额。
- if (detailRows.isEmpty()) {
- detailCount = valueOrZero(merchant.getPayCount());
- detailPrice = valueOrZero(merchant.getTotalPrice());
- }
- List merchantProductRows = new ArrayList<>(
- detailRows.size() + statisticRows.size());
- merchantProductRows.addAll(detailRows);
- merchantProductRows.addAll(statisticRows);
- detailRowsByMerchant.put(merchantKey, merchantProductRows);
- detailCountsByMerchant.put(merchantKey, detailCount);
- detailPricesByMerchant.put(merchantKey, detailPrice);
- summaryProductCount += detailCount;
- summaryTotalPrice += detailPrice;
- }
- if (merchantRows.isEmpty()) {
- summaryProductCount = valueOrZero(summary.getPayCount());
- summaryTotalPrice = valueOrZero(summary.getTotalPrice());
- }
- HardCanteenProductApptExcelVo summaryRow = new HardCanteenProductApptExcelVo();
- summaryRow.setSummaryDate(date);
- summaryRow.setSummaryHourType(resolveHourName(hourType, hourNames));
- summaryRow.setSummaryPayUserCount(valueOrZero(summary.getPayUserCount()));
- summaryRow.setSummaryTimeCount(valueOrZero(summary.getTimeCount()));
- // 汇总数量必须与下方已展开的商品明细一致;套餐按组成商品份数计入,而不是只计套餐份数。
- summaryRow.setSummaryPayCount(summaryProductCount);
- summaryRow.setSummaryTotalPrice(centsToYuan(summaryTotalPrice));
- rows.add(summaryRow);
- for (HardCanteenProductApptExportRecord merchant : merchantRows) {
- HardCanteenProductApptExcelVo merchantRow = new HardCanteenProductApptExcelVo();
- merchantRow.setMerchantName(merchantNames.getOrDefault(merchant.getMerchantId(), "商户信息未记录"));
- merchantRow.setDepartmentName(departmentNames.getOrDefault(merchant.getUserDeptId(), "部门信息未记录"));
- merchantRow.setMerchantDate(date);
- merchantRow.setMerchantHourType(resolveHourName(hourType, hourNames));
- merchantRow.setMerchantPayUserCount(valueOrZero(merchant.getPayUserCount()));
- merchantRow.setMerchantTimeCount(valueOrZero(merchant.getTimeCount()));
- String merchantKey = groupKey(merchant);
- merchantRow.setMerchantPayCount(detailCountsByMerchant.getOrDefault(merchantKey,
- valueOrZero(merchant.getPayCount())));
- merchantRow.setMerchantTotalPrice(centsToYuan(detailPricesByMerchant.getOrDefault(merchantKey,
- valueOrZero(merchant.getTotalPrice()))));
- rows.add(merchantRow);
- rows.addAll(detailRowsByMerchant.getOrDefault(merchantKey, List.of()));
- }
- }
- return rows;
- }
-
- /**
- * 将商品聚合行按预约当天每日菜谱中的套餐组成展开。
- *
- * 组成商品数量按“套餐预约数量 × 套餐内份数”计算;组成商品金额按菜谱单价权重使用最大余数法分摊,
- * 从而保证所有组成行金额之和严格等于套餐订单快照金额,并避免极小金额因四舍五入产生负数尾差。
- *
- * @param product 订单购物车商品聚合记录,数量和金额均为套餐商品口径
- * @param menu 与商户、预约日期、餐段和商品 SPU 匹配的每日菜谱;为空时保留原商品行
- * @return 单品原始行,或按套餐组成顺序生成的商品明细行
- */
- private List toProductRows(HardCanteenProductApptExportRecord product, DailyMenuAppVO menu) {
- List items = menu != null && menu.getComboItems() != null
- && !menu.getComboItems().isEmpty() ? menu.getComboItems() : List.of();
- if (items.isEmpty()) {
- return List.of(toProductRow(product.getProductName(), valueOrZero(product.getPayCount()),
- product.getTotalPrice()));
- }
- long packageCount = valueOrZero(product.getPayCount());
- long totalCents = valueOrZero(product.getTotalPrice());
- long sign = totalCents < 0 ? -1L : 1L;
- long absoluteTotalCents = Math.abs(totalCents);
- long baseAmount = items.stream().mapToLong(item -> Math.max(0, valueOrZero(item.getPrice()))
- * Math.max(1, valueOrZero(item.getQuantity()))).sum();
- long[] itemPrices = new long[items.size()];
- long[] itemWeights = new long[items.size()];
- long allocated = 0L;
- List remainderIndexes = new ArrayList<>();
- List result = new ArrayList<>();
- for (int index = 0; index < items.size(); index++) {
- OnlineSpuComboItemVO item = items.get(index);
- long itemWeight = Math.max(0, valueOrZero(item.getPrice())) * Math.max(1, valueOrZero(item.getQuantity()));
- itemWeights[index] = itemWeight;
- if (baseAmount > 0) {
- BigDecimal exactPrice = BigDecimal.valueOf(absoluteTotalCents)
- .multiply(BigDecimal.valueOf(itemWeight))
- .divide(BigDecimal.valueOf(baseAmount), 6, RoundingMode.DOWN);
- itemPrices[index] = exactPrice.setScale(0, RoundingMode.FLOOR).longValue();
- allocated += itemPrices[index];
- remainderIndexes.add(index);
- }
- }
- if (baseAmount > 0) {
- long remainder = absoluteTotalCents - allocated;
- // 每项先向下取整,再按小数余数从大到小补分,保证分摊值不为负且合计不丢分。
- remainderIndexes.sort(Comparator.comparing((Integer index) -> {
- BigDecimal exactPrice = BigDecimal.valueOf(absoluteTotalCents)
- .multiply(BigDecimal.valueOf(itemWeights[index]))
- .divide(BigDecimal.valueOf(baseAmount), 6, RoundingMode.DOWN);
- return exactPrice.subtract(BigDecimal.valueOf(itemPrices[index]));
- }).reversed());
- for (int index = 0; index < remainderIndexes.size() && remainder > 0; index++, remainder--) {
- itemPrices[remainderIndexes.get(index)]++;
- }
- } else if (!items.isEmpty()) {
- itemPrices[items.size() - 1] = absoluteTotalCents;
- }
- for (int index = 0; index < items.size(); index++) {
- OnlineSpuComboItemVO item = items.get(index);
- long itemCount = packageCount * Math.max(1, valueOrZero(item.getQuantity()));
- long itemPrice = itemPrices[index] * sign;
- result.add(toProductRow(StrUtil.blankToDefault(item.getName(), "未命名商品"), itemCount, itemPrice));
- }
- return result;
- }
-
- /** 创建单个商品明细行。 */
- private HardCanteenProductApptExcelVo toProductRow(String name, long count, long totalPrice) {
- HardCanteenProductApptExcelVo row = new HardCanteenProductApptExcelVo();
- row.setProductName(name);
- row.setProductCount(count);
- row.setProductTotalPrice(centsToYuan(totalPrice));
- return row;
- }
-
- /**
- * 在当前商户部门的商品明细右侧写入按商品名称合并后的统计结果。
- *
- * 套餐组成商品可能由多个套餐分别展开成同名明细,因此不能按原套餐 SPU 汇总;这里使用压缩空白后的名称作为键,
- * 保留首次出现的展示名称,并使用整数分累计金额,确保统计总数量、总价格与左侧有效明细严格一致。
- *
- * @param detailRows 同一预约日期、餐段、商户和部门下,已经扣减完成退款并完成套餐展开的商品明细
- * @return 独立的商品统计行,顺序与商品名称首次出现的顺序一致
- */
- private List buildProductStatistics(
- List detailRows) {
- Map statistics = new LinkedHashMap<>();
- for (HardCanteenProductApptExcelVo detailRow : detailRows) {
- String displayName = StrUtil.blankToDefault(detailRow.getProductName(), "未命名商品").trim();
- String statisticKey = normalizeProductName(displayName);
- HardCanteenProductApptExcelVo statistic = statistics.computeIfAbsent(statisticKey, key -> {
- HardCanteenProductApptExcelVo row = new HardCanteenProductApptExcelVo();
- row.setStatisticProductName(displayName);
- row.setStatisticProductCount(0L);
- row.setStatisticProductTotalPrice(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
- return row;
- });
- statistic.setStatisticProductCount(valueOrZero(statistic.getStatisticProductCount())
- + valueOrZero(detailRow.getProductCount()));
- long statisticTotalPrice = yuanToCents(statistic.getStatisticProductTotalPrice())
- + yuanToCents(detailRow.getProductTotalPrice());
- statistic.setStatisticProductTotalPrice(centsToYuan(statisticTotalPrice));
- }
- return new ArrayList<>(statistics.values());
- }
-
- /**
- * 规范化商品名称中的换行和连续空白,用于合并仅排版不同的同名套餐组成商品。
- *
- * @param productName 首次出现的商品展示名称
- * @return 去除首尾空白并把连续空白压缩为单个空格的统计键
- */
- private String normalizeProductName(String productName) {
- return productName.replaceAll("\\s+", " ").trim();
- }
-
- /** 查询当前批次涉及的每日菜谱,并以商户、日期、餐段和 SPU 组成缓存键。 */
- private Map fetchDailyMenus(List products,
- LocalDate fixedDate) {
- Map result = new HashMap<>();
- LocalDate date = fixedDate;
- if (date == null) return result;
- List merchantIds = products.stream().map(HardCanteenProductApptExportRecord::getMerchantId)
- .filter(Objects::nonNull).distinct().toList();
- for (Long merchantId : merchantIds) {
- for (String hourType : products.stream().filter(item -> Objects.equals(item.getMerchantId(), merchantId))
- .map(HardCanteenProductApptExportRecord::getHourType).filter(StrUtil::isNotBlank).distinct().toList()) {
- try {
- Integer hour = Integer.valueOf(hourType);
- R> response = remoteDailyMenuService.getCanteenList(date.toString(), hour, null, merchantId);
- if (response != null && response.isOk() && response.getData() != null) {
- response.getData().forEach(menu -> {
- if (menu.getSpuId() != null) result.put(merchantId + "|" + date + "|" + hourType + "|" + menu.getSpuId(), menu);
- });
- }
- } catch (Exception exception) {
- log.warn("查询每日菜谱套餐组成失败,保留原商品行,merchantId:{},date:{},hourType:{}",
- merchantId, date, hourType, exception.getMessage());
- }
- }
- }
- return result;
- }
-
- /** 批量查询商户名称。 */
- private Map fetchMerchantNames(List rows) {
- List ids = rows.stream().map(HardCanteenProductApptExportRecord::getMerchantId).filter(Objects::nonNull).distinct().toList();
- if (ids.isEmpty()) return Map.of();
- try {
- R> response = remoteMchService.getByIds(ids);
- if (response == null || !response.isOk() || response.getData() == null) return Map.of();
- return response.getData().stream().filter(item -> item.getId() != null)
- .collect(Collectors.toMap(MchManageVo::getId, MchManageVo::getName, (left, right) -> left));
- } catch (Exception exception) {
- log.warn("查询导出商户名称失败,使用空名称", exception);
- return Map.of();
- }
- }
-
- /** 批量查询部门名称。 */
- private Map fetchDepartmentNames(List rows) {
- List ids = rows.stream().map(HardCanteenProductApptExportRecord::getUserDeptId).filter(Objects::nonNull).distinct().toList();
- if (ids.isEmpty()) return Map.of();
- try {
- R