feat:钱包模块导出都改为分批,前端显示导出进度条的模式。
fix:修改各个统计模块统计的数量不一致的情况。 fix:修改支付宝分账退款失败的情况
This commit is contained in:
1 parent
f9e50d0af8
commit
fc35443b90
34 files changed
+2620
-187
No files matched your search
+5
-2
@@ -22,10 +22,13 @@ import java.util.List;
|
||||
@Schema(description = "食堂用户预约查询条件")
|
||||
public class HardCanteenUserApptQueryDto implements BaseIsIncludeTreeInterface, BaseUserIdentityQueryInterface {
|
||||
|
||||
/** 用餐日期闭区间,固定为开始、结束两个服务端本地日期,业务层限制跨度最多为两个自然月。 */
|
||||
/**
|
||||
* 统计日期闭区间,固定为开始、结束两个服务端本地日期,业务层限制跨度最多为两个自然月。
|
||||
* 核销状态为 VERIFIED 时匹配实际核销时间,其他状态匹配预约用餐日期。
|
||||
*/
|
||||
@NotNull(message = "用餐日期范围不能为空")
|
||||
@Size(min = 2, max = 2, message = "用餐日期范围必须包含开始和结束日期")
|
||||
@Schema(description = "用餐日期范围,固定为开始日期和结束日期", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@Schema(description = "统计日期范围,已核销取核销日期,其他状态取预约用餐日期", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDate[] apptUseDateList;
|
||||
|
||||
/** 用户姓名、登录账号或手机号的模糊查询关键字;为空时不限制。 */
|
||||
|
||||
+5
@@ -3,6 +3,7 @@ package com.bsd.pjcloud.hard.api.vo.order;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
@@ -20,6 +21,10 @@ public class HardCanteenUserApptOrderVo {
|
||||
@Schema(description = "用餐时间段字典编码")
|
||||
private String hourType;
|
||||
|
||||
/** 订单预约用餐日期;已核销按核销日查询时用于保留原预约日期,历史未记录时为空。 */
|
||||
@Schema(description = "预约用餐日期")
|
||||
private LocalDate apptUseDate;
|
||||
|
||||
/** 下单时保存的商品描述;历史数据未记录时为空。 */
|
||||
@Schema(description = "订餐内容")
|
||||
private String productDesc;
|
||||
|
||||
+2
-2
@@ -92,8 +92,8 @@ public class HardCanteenUserApptVo implements BaseUserDeptInterface, BaseUserIde
|
||||
@Schema(description = "老师身份扩展信息")
|
||||
private SysUserTeacherDto teacherInfo;
|
||||
|
||||
/** 本行对应的用餐日期。 */
|
||||
@Schema(description = "用餐日期")
|
||||
/** 本行统计日期;已核销筛选取实际核销日期,其他筛选取预约用餐日期。 */
|
||||
@Schema(description = "统计日期,已核销取核销日期,其他状态取预约用餐日期")
|
||||
private LocalDate apptUseDate;
|
||||
|
||||
/** 订餐状态,RESERVED 表示当前筛选口径存在有效预约,NOT_RESERVED 表示不存在。 */
|
||||
|
||||
+6
-6
@@ -9,7 +9,7 @@ import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 用户和用餐日期维度的食堂预约导出行。
|
||||
* 用户和统计日期维度的食堂预约导出行。
|
||||
*/
|
||||
@Data
|
||||
@ColumnWidth(22)
|
||||
@@ -63,8 +63,8 @@ public class HardCanteenUserApptExcelVo {
|
||||
@ExcelProperty({"老师身份", "老师学号"})
|
||||
private String teacherNo;
|
||||
|
||||
/** 查询指定的用餐日期。 */
|
||||
@ExcelProperty({"预约信息", "用餐日期"})
|
||||
/** 查询指定的统计日期;已核销筛选取核销日期,其他状态取预约用餐日期。 */
|
||||
@ExcelProperty({"预约信息", "统计日期"})
|
||||
private LocalDate apptUseDate;
|
||||
|
||||
/** 是否存在符合当前食堂、餐段筛选的有效预约。 */
|
||||
@@ -103,12 +103,12 @@ public class HardCanteenUserApptExcelVo {
|
||||
@ExcelProperty({"金额统计", "餐段总金额(元)"})
|
||||
private String mealSegmentTotalPrices;
|
||||
|
||||
/** 用户本行日期内有效预约商品总价,单位为元并保留两位小数。 */
|
||||
@ExcelProperty({"金额统计", "当日总金额(元)"})
|
||||
/** 用户本行统计日期内完成退款扣减后的预约商品净额,单位为元并保留两位小数。 */
|
||||
@ExcelProperty({"金额统计", "统计日净额(元)"})
|
||||
@NumberFormat("#,##0.00")
|
||||
private BigDecimal totalPrice;
|
||||
|
||||
/** 用户当天有效预约的整体核销状态。 */
|
||||
/** 用户在本行统计日期和当前筛选口径内有效预约的整体核销状态。 */
|
||||
@ExcelProperty({"核销信息", "核销状态"})
|
||||
private String verificationStatus;
|
||||
|
||||
|
||||
+20
@@ -16,6 +16,7 @@ import com.bsd.pjcloud.hard.api.vo.order.HardCanteenProductApptExportRecord;
|
||||
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.mapper.order.model.HardCanteenReservationSaleFact;
|
||||
import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
@@ -67,6 +68,14 @@ public interface HardCarMapper extends PjcloudBaseMapper<HardCarEntity> {
|
||||
*/
|
||||
List<HardCanteenProductTotalVo> productTotalByDeptStats(@Param("dto") HardOrderDto dto, DataScope dataScope);
|
||||
|
||||
/**
|
||||
* 查询预约核销商品事实,复用菜品销售统计的核销时间和完成退款净额口径。
|
||||
*
|
||||
* @param dto 核销日期、食堂、餐段和用户范围;调用方必须把来源限制为预约核销
|
||||
* @return 按核销时间、订单和商品稳定排序的有效商品事实,全额退款订单不返回记录
|
||||
*/
|
||||
List<HardCanteenReservationSaleFact> listReservationVerifiedSaleFacts(@Param("dto") HardOrderDto dto);
|
||||
|
||||
Page<HardCanteenProductTotalVo> productTotalByApptUseDate(Page page, @Param("dto") HardOrderDto dto, DataScope dataScope);
|
||||
|
||||
/**
|
||||
@@ -133,6 +142,17 @@ public interface HardCarMapper extends PjcloudBaseMapper<HardCarEntity> {
|
||||
.orderByAsc(HardCarEntity::getId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按父购物车批量查询完成退款扣减后的预约商品快照。
|
||||
*
|
||||
* <p>部分退款仅保留剩余份数和金额,全额退款商品不返回;该结果用于待核销、部分核销及预约日统计,
|
||||
* 避免人员页面继续累计原始购物车快照。</p>
|
||||
*
|
||||
* @param parentCarIds 预约订单关联的父购物车主键集合
|
||||
* @return 退款后仍有有效数量的商品快照;传入空集合时调用方应直接返回空列表
|
||||
*/
|
||||
List<HardCarEntity> listEffectiveReservationCars(@Param("parentCarIds") List<Long> parentCarIds);
|
||||
|
||||
/**
|
||||
* 菜品销量按照分类查询
|
||||
*/
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.bsd.pjcloud.hard.mapper.order.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 食堂预约核销商品事实。
|
||||
*
|
||||
* <p>数据直接来自菜品销售统计的预约核销分支,日期取订单核销时间,数量和金额均已扣除状态为 50 的
|
||||
* 完成退款;仅用于 Hardware 服务内部统一人员统计与商品统计口径,不作为外部接口模型。</p>
|
||||
*/
|
||||
@Data
|
||||
public class HardCanteenReservationSaleFact {
|
||||
|
||||
/** 订单实际核销完成时间,用于把人员统计行归入对应核销日期。 */
|
||||
private LocalDateTime statisticsTime;
|
||||
|
||||
/** 已核销预约订单主键,用于订单去重及关联页面预约明细。 */
|
||||
private Long orderId;
|
||||
|
||||
/** 实际用餐用户主键,用于人员筛选和人数去重。 */
|
||||
private Long userId;
|
||||
|
||||
/** 订单餐段协议编码,来源于核销时的订单快照。 */
|
||||
private String hourType;
|
||||
|
||||
/** 订单商品主键,历史数据无法识别商品时可为空。 */
|
||||
private Long productId;
|
||||
|
||||
/** 下单时保存的商品名称,历史未记录时可为空。 */
|
||||
private String productName;
|
||||
|
||||
/** 完成退款扣减后的有效商品份数,最小为零。 */
|
||||
private Integer productCount;
|
||||
|
||||
/** 完成退款扣减后的有效商品金额,单位为分,最小为零。 */
|
||||
private Long totalPrice;
|
||||
}
|
||||
+135
-16
@@ -27,6 +27,7 @@ import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.hard.api.constant.order.HardHourTypeEnum;
|
||||
import com.bsd.pjcloud.hard.api.constant.order.HardOrderStatusEnum;
|
||||
import com.bsd.pjcloud.hard.api.constant.order.HardOrderTypeEnum;
|
||||
import com.bsd.pjcloud.hard.api.constant.order.HardSaleSourceEnum;
|
||||
import com.bsd.pjcloud.hard.api.dto.order.HardCanteenUserApptQueryDto;
|
||||
import com.bsd.pjcloud.hard.api.dto.order.HardOrderDto;
|
||||
import com.bsd.pjcloud.hard.api.entity.order.HardCarEntity;
|
||||
@@ -36,6 +37,7 @@ import com.bsd.pjcloud.hard.api.vo.order.HardOrderVo;
|
||||
import com.bsd.pjcloud.hard.api.vo.order.excel.HardCanteenUserApptExcelVo;
|
||||
import com.bsd.pjcloud.hard.mapper.order.HardCarMapper;
|
||||
import com.bsd.pjcloud.hard.mapper.order.HardOrderMapper;
|
||||
import com.bsd.pjcloud.hard.mapper.order.model.HardCanteenReservationSaleFact;
|
||||
import com.bsd.pjcloud.hard.service.HardCanteenApptTotalExportService;
|
||||
import com.bsd.pjcloud.wallet.api.feign.RemoteMchService;
|
||||
import com.bsd.pjcloud.wallet.api.vo.MchManageVo;
|
||||
@@ -499,15 +501,15 @@ public class HardCanteenApptTotalExportServiceImpl implements HardCanteenApptTot
|
||||
private void validateQuery(HardCanteenUserApptQueryDto query) {
|
||||
if (query == null || query.getApptUseDateList() == null || query.getApptUseDateList().length != 2
|
||||
|| query.getApptUseDateList()[0] == null || query.getApptUseDateList()[1] == null) {
|
||||
throw new IllegalArgumentException("用餐日期范围必须包含开始和结束日期");
|
||||
throw new IllegalArgumentException("统计日期范围必须包含开始和结束日期");
|
||||
}
|
||||
LocalDate startDate = query.getApptUseDateList()[0];
|
||||
LocalDate endDate = query.getApptUseDateList()[1];
|
||||
if (endDate.isBefore(startDate)) {
|
||||
throw new IllegalArgumentException("用餐结束日期不能早于开始日期");
|
||||
throw new IllegalArgumentException("统计结束日期不能早于开始日期");
|
||||
}
|
||||
if (endDate.isAfter(startDate.plusMonths(MAX_QUERY_MONTHS))) {
|
||||
throw new IllegalArgumentException("用餐日期范围最多查询两个月");
|
||||
throw new IllegalArgumentException("统计日期范围最多查询两个月");
|
||||
}
|
||||
if (StrUtil.isNotBlank(query.getReservationStatus())
|
||||
&& !RESERVATION_STATUS_VALUES.contains(query.getReservationStatus())) {
|
||||
@@ -520,7 +522,7 @@ public class HardCanteenApptTotalExportServiceImpl implements HardCanteenApptTot
|
||||
}
|
||||
|
||||
/**
|
||||
* 展开查询范围内的全部自然日,开始和结束日期均包含在结果中。
|
||||
* 展开统计范围内的全部自然日,开始和结束日期均包含在结果中。
|
||||
*
|
||||
* @param query 已通过日期范围校验的查询条件
|
||||
* @return 按日期升序排列的查询日期
|
||||
@@ -697,9 +699,9 @@ public class HardCanteenApptTotalExportServiceImpl implements HardCanteenApptTot
|
||||
}
|
||||
Map<Long, String> merchantNameMap = fetchMerchantNameMap(orders);
|
||||
Map<Long, Map<LocalDate, List<HardCanteenUserApptOrderVo>>> orderMap = orders.stream()
|
||||
.filter(order -> order.getUserId() != null && order.getApptUseDate() != null)
|
||||
.filter(order -> order.getUserId() != null && resolveStatisticsDate(order, query) != null)
|
||||
.collect(Collectors.groupingBy(HardOrderVo::getUserId, LinkedHashMap::new,
|
||||
Collectors.groupingBy(HardOrderVo::getApptUseDate, LinkedHashMap::new,
|
||||
Collectors.groupingBy(order -> resolveStatisticsDate(order, query), LinkedHashMap::new,
|
||||
Collectors.mapping(order -> toOrderVo(order, merchantNameMap), Collectors.toList()))));
|
||||
|
||||
return users.stream().filter(user -> user.getUserId() != null).flatMap(user -> queryDates.stream().map(date -> {
|
||||
@@ -754,16 +756,54 @@ public class HardCanteenApptTotalExportServiceImpl implements HardCanteenApptTot
|
||||
new ArrayList<>(userIds.subList(start, end)));
|
||||
orders.addAll(hardOrderMapper.scopeList(orderQuery, null));
|
||||
}
|
||||
return orders;
|
||||
return retainEffectiveReservationOrders(orders, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分批读取预约订单关联的购物车快照,避免统计请求因用户范围过大而生成超长 IN 条件。
|
||||
* 只保留完成退款后仍有有效商品的预约订单,并把导出金额改为退款后的净商品金额。
|
||||
*
|
||||
* @param orders 已按订单状态和业务日期查询出的预约订单
|
||||
* @param query 当前导出筛选条件
|
||||
* @return 完成退款后仍有商品的订单,订单总价已更新为净额
|
||||
*/
|
||||
private List<HardOrderVo> retainEffectiveReservationOrders(List<HardOrderVo> orders,
|
||||
HardCanteenUserApptQueryDto query) {
|
||||
if (orders.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
Map<Long, Long> effectiveTotalPriceMap;
|
||||
if (isVerifiedFilter(query)) {
|
||||
Set<Long> orderIds = orders.stream().map(HardOrderVo::getId)
|
||||
.filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
effectiveTotalPriceMap = fetchReservationVerifiedSaleFacts(
|
||||
orders.stream().map(HardOrderVo::getUserId).filter(Objects::nonNull).distinct().toList(), query)
|
||||
.stream().filter(fact -> fact.getOrderId() != null && orderIds.contains(fact.getOrderId()))
|
||||
.collect(Collectors.groupingBy(HardCanteenReservationSaleFact::getOrderId,
|
||||
Collectors.summingLong(fact -> number(fact.getTotalPrice()))));
|
||||
} else {
|
||||
Map<Long, List<HardCarEntity>> carMap = fetchEffectiveReservationCars(orders);
|
||||
effectiveTotalPriceMap = orders.stream()
|
||||
.filter(order -> order.getId() != null && order.getParentCarId() != null)
|
||||
.filter(order -> carMap.containsKey(order.getParentCarId()))
|
||||
.collect(Collectors.toMap(HardOrderVo::getId,
|
||||
order -> carMap.get(order.getParentCarId()).stream()
|
||||
.map(HardCarEntity::getTotalPrice)
|
||||
.mapToLong(HardCanteenApptTotalExportServiceImpl::number).sum(),
|
||||
(left, right) -> left, LinkedHashMap::new));
|
||||
}
|
||||
return orders.stream().filter(order -> order.getId() != null)
|
||||
.filter(order -> effectiveTotalPriceMap.containsKey(order.getId()))
|
||||
.peek(order -> order.setTotalPrice(Math.toIntExact(effectiveTotalPriceMap.get(order.getId()))))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 分批读取预约订单关联的完成退款后有效商品,避免生成超长父购物车 IN 条件。
|
||||
*
|
||||
* @param orders 当前筛选口径下的预约订单
|
||||
* @return 父购物车主键到商品快照的映射
|
||||
* @return 父购物车主键到退款后有效商品的映射
|
||||
*/
|
||||
private Map<Long, List<HardCarEntity>> fetchReservationCars(List<HardOrderVo> orders) {
|
||||
private Map<Long, List<HardCarEntity>> fetchEffectiveReservationCars(List<HardOrderVo> orders) {
|
||||
List<Long> parentCarIds = orders.stream().map(HardOrderVo::getParentCarId)
|
||||
.filter(Objects::nonNull).distinct().toList();
|
||||
if (parentCarIds.isEmpty()) {
|
||||
@@ -772,12 +812,41 @@ public class HardCanteenApptTotalExportServiceImpl implements HardCanteenApptTot
|
||||
Map<Long, List<HardCarEntity>> result = new LinkedHashMap<>();
|
||||
for (int start = 0; start < parentCarIds.size(); start += ORDER_USER_BATCH_SIZE) {
|
||||
int end = Math.min(start + ORDER_USER_BATCH_SIZE, parentCarIds.size());
|
||||
hardCarMapper.listByParentCarIds(parentCarIds.subList(start, end))
|
||||
hardCarMapper.listEffectiveReservationCars(parentCarIds.subList(start, end))
|
||||
.forEach(car -> result.computeIfAbsent(car.getParentCarId(), key -> new ArrayList<>()).add(car));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按用户分批查询菜品销售统计中的预约核销商品事实。
|
||||
*
|
||||
* @param userIds 已经过 UPMS 数据权限和人员条件限制的用户主键
|
||||
* @param query 核销日期、食堂和餐段筛选条件
|
||||
* @return 完成退款扣减后仍有商品的预约核销事实
|
||||
*/
|
||||
private List<HardCanteenReservationSaleFact> fetchReservationVerifiedSaleFacts(
|
||||
List<Long> userIds, HardCanteenUserApptQueryDto query) {
|
||||
if (userIds.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
List<HardCanteenReservationSaleFact> facts = new ArrayList<>();
|
||||
for (int start = 0; start < userIds.size(); start += ORDER_USER_BATCH_SIZE) {
|
||||
int end = Math.min(start + ORDER_USER_BATCH_SIZE, userIds.size());
|
||||
HardOrderDto factQuery = new HardOrderDto();
|
||||
factQuery.setUserIds(new ArrayList<>(userIds.subList(start, end)));
|
||||
factQuery.setMerchantId(query.getMerchantId());
|
||||
factQuery.setHourType(query.getHourType());
|
||||
factQuery.setSaleSourceCodes(List.of(HardSaleSourceEnum.RESERVATION_VERIFIED.getCode()));
|
||||
factQuery.setStatisticsDateTimeList(new LocalDateTime[]{
|
||||
query.getApptUseDateList()[0].atStartOfDay(),
|
||||
query.getApptUseDateList()[1].atTime(LocalTime.MAX)
|
||||
});
|
||||
facts.addAll(hardCarMapper.listReservationVerifiedSaleFacts(factQuery));
|
||||
}
|
||||
return facts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造用户预约列表、候选用户预查询和导出明细共用的订单条件。
|
||||
*
|
||||
@@ -795,15 +864,64 @@ public class HardCanteenApptTotalExportServiceImpl implements HardCanteenApptTot
|
||||
orderQuery.setMerchantId(query.getMerchantId());
|
||||
orderQuery.setHourType(query.getHourType());
|
||||
orderQuery.setOrderType(HardOrderTypeEnum.RESERVE.getType());
|
||||
orderQuery.setOrderStatusCodes(VALID_RESERVATION_ORDER_STATUSES);
|
||||
orderQuery.setApptIdOrApptRuleIdNotNull(true);
|
||||
orderQuery.setApptUseDateTimeList(new java.time.LocalDateTime[]{
|
||||
query.getApptUseDateList()[0].atStartOfDay(),
|
||||
query.getApptUseDateList()[1].atTime(LocalTime.MAX)
|
||||
});
|
||||
LocalDateTime[] dateTimeRange = new LocalDateTime[]{
|
||||
query.getApptUseDateList()[0].atStartOfDay(), query.getApptUseDateList()[1].atTime(LocalTime.MAX)};
|
||||
if (isVerifiedFilter(query)) {
|
||||
orderQuery.setOrderStatusCodes(List.of(HardOrderStatusEnum.CANCEL.getType()));
|
||||
orderQuery.setVerificationTimeList(dateTimeRange);
|
||||
} else {
|
||||
orderQuery.setOrderStatusCodes(isUnverifiedFilter(query)
|
||||
? List.of(HardOrderStatusEnum.PAY.getType()) : VALID_RESERVATION_ORDER_STATUSES);
|
||||
orderQuery.setApptUseDateTimeList(dateTimeRange);
|
||||
}
|
||||
return orderQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析导出行的业务统计日期。
|
||||
*
|
||||
* @param order 预约订单
|
||||
* @param query 当前核销状态筛选
|
||||
* @return 已核销筛选返回实际核销日期,其他情况返回预约用餐日期
|
||||
*/
|
||||
private LocalDate resolveStatisticsDate(HardOrderVo order, HardCanteenUserApptQueryDto query) {
|
||||
if (isVerifiedFilter(query)) {
|
||||
return order.getVerificationTime() == null ? null : order.getVerificationTime().toLocalDate();
|
||||
}
|
||||
return order.getApptUseDate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前导出是否要求按单笔已核销订单统计。
|
||||
*
|
||||
* @param query 导出查询条件
|
||||
* @return 核销状态为 VERIFIED 时返回 true
|
||||
*/
|
||||
private boolean isVerifiedFilter(HardCanteenUserApptQueryDto query) {
|
||||
return Objects.equals(query.getVerificationStatus(), VERIFICATION_STATUS_VERIFIED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前导出是否要求按单笔待核销订单统计。
|
||||
*
|
||||
* @param query 导出查询条件
|
||||
* @return 核销状态为 UNVERIFIED 时返回 true
|
||||
*/
|
||||
private boolean isUnverifiedFilter(HardCanteenUserApptQueryDto query) {
|
||||
return Objects.equals(query.getVerificationStatus(), VERIFICATION_STATUS_UNVERIFIED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将可能为空的商品金额安全归一为长整型零值。
|
||||
*
|
||||
* @param value 商品金额,单位为分
|
||||
* @return 原数值的长整型表示;空值返回零
|
||||
*/
|
||||
private static long number(Number value) {
|
||||
return value == null ? 0L : value.longValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量补齐订单关联的食堂名称;商户服务异常时保留订单并返回空名称,避免影响预约事实查询。
|
||||
*
|
||||
@@ -843,6 +961,7 @@ public class HardCanteenApptTotalExportServiceImpl implements HardCanteenApptTot
|
||||
HardCanteenUserApptOrderVo detail = new HardCanteenUserApptOrderVo();
|
||||
detail.setId(order.getId());
|
||||
detail.setHourType(order.getHourType());
|
||||
detail.setApptUseDate(order.getApptUseDate());
|
||||
detail.setProductDesc(order.getProductDesc());
|
||||
detail.setMerchantName(merchantNameMap.get(order.getMerchantId()));
|
||||
detail.setTotalPrice(order.getTotalPrice());
|
||||
|
||||
+179
-19
@@ -13,6 +13,7 @@ import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.data.datascope.DataScope;
|
||||
import com.bsd.pjcloud.hard.api.constant.order.HardOrderStatusEnum;
|
||||
import com.bsd.pjcloud.hard.api.constant.order.HardOrderTypeEnum;
|
||||
import com.bsd.pjcloud.hard.api.constant.order.HardSaleSourceEnum;
|
||||
import com.bsd.pjcloud.hard.api.dto.order.HardCanteenUserApptQueryDto;
|
||||
import com.bsd.pjcloud.hard.api.dto.order.HardOrderDto;
|
||||
import com.bsd.pjcloud.hard.api.entity.order.HardCarEntity;
|
||||
@@ -27,6 +28,7 @@ import com.bsd.pjcloud.hard.api.vo.order.HardCanteenUserApptStatisticsVo;
|
||||
import com.bsd.pjcloud.hard.api.vo.order.HardOrderVo;
|
||||
import com.bsd.pjcloud.hard.mapper.order.HardCarMapper;
|
||||
import com.bsd.pjcloud.hard.mapper.order.HardOrderMapper;
|
||||
import com.bsd.pjcloud.hard.mapper.order.model.HardCanteenReservationSaleFact;
|
||||
import com.bsd.pjcloud.hard.service.HardCanteenApptTotalService;
|
||||
import com.bsd.pjcloud.wallet.api.feign.RemoteMchService;
|
||||
import com.bsd.pjcloud.wallet.api.vo.MchManageVo;
|
||||
@@ -260,8 +262,8 @@ public class HardCanteenApptTotalServiceImpl implements HardCanteenApptTotalServ
|
||||
/**
|
||||
* 按当前用户预约查询条件生成每日和查询范围总计。
|
||||
*
|
||||
* <p>统计先复用用户预约列表的权限用户、有效订单和状态筛选口径,再读取订单购物车快照计算菜品份数,
|
||||
* 保证抽屉中的人数、订单和菜品不会因为分页而少算或多算。</p>
|
||||
* <p>统计先复用用户预约列表的权限用户、有效订单和状态筛选口径,再读取完成退款扣减后的有效商品。
|
||||
* 已核销筛选直接复用菜品销售统计的核销事实,保证人数、订单、份数和金额不会因整行状态或分页产生偏差。</p>
|
||||
*
|
||||
* @param query 用户、身份、日期范围、食堂、餐段和状态筛选条件
|
||||
* @return 按日期升序排列并包含范围总计的统计结果
|
||||
@@ -276,7 +278,7 @@ public class HardCanteenApptTotalServiceImpl implements HardCanteenApptTotalServ
|
||||
List<HardCanteenUserApptVo> rows = filterRows(buildRows(users, query, queryDates, orders), query);
|
||||
Map<Long, HardOrderVo> orderMap = orders.stream().filter(order -> order.getId() != null)
|
||||
.collect(Collectors.toMap(HardOrderVo::getId, Function.identity(), (left, right) -> left));
|
||||
Map<Long, List<HardCarEntity>> carMap = fetchReservationCars(orders);
|
||||
Map<Long, List<HardCarEntity>> carMap = fetchReservationCars(orders, query);
|
||||
|
||||
Map<LocalDate, StatisticsBucket> dailyBuckets = new LinkedHashMap<>();
|
||||
queryDates.forEach(date -> dailyBuckets.put(date, new StatisticsBucket()));
|
||||
@@ -304,15 +306,15 @@ public class HardCanteenApptTotalServiceImpl implements HardCanteenApptTotalServ
|
||||
private void validateQuery(HardCanteenUserApptQueryDto query) {
|
||||
if (query == null || query.getApptUseDateList() == null || query.getApptUseDateList().length != 2
|
||||
|| query.getApptUseDateList()[0] == null || query.getApptUseDateList()[1] == null) {
|
||||
throw new IllegalArgumentException("用餐日期范围必须包含开始和结束日期");
|
||||
throw new IllegalArgumentException("统计日期范围必须包含开始和结束日期");
|
||||
}
|
||||
LocalDate startDate = query.getApptUseDateList()[0];
|
||||
LocalDate endDate = query.getApptUseDateList()[1];
|
||||
if (endDate.isBefore(startDate)) {
|
||||
throw new IllegalArgumentException("用餐结束日期不能早于开始日期");
|
||||
throw new IllegalArgumentException("统计结束日期不能早于开始日期");
|
||||
}
|
||||
if (endDate.isAfter(startDate.plusMonths(MAX_QUERY_MONTHS))) {
|
||||
throw new IllegalArgumentException("用餐日期范围最多查询两个月");
|
||||
throw new IllegalArgumentException("统计日期范围最多查询两个月");
|
||||
}
|
||||
if (StrUtil.isNotBlank(query.getReservationStatus())
|
||||
&& !RESERVATION_STATUS_VALUES.contains(query.getReservationStatus())) {
|
||||
@@ -325,7 +327,7 @@ public class HardCanteenApptTotalServiceImpl implements HardCanteenApptTotalServ
|
||||
}
|
||||
|
||||
/**
|
||||
* 展开查询范围内的全部自然日,开始和结束日期均包含在结果中。
|
||||
* 展开统计范围内的全部自然日,开始和结束日期均包含在结果中。
|
||||
*
|
||||
* @param query 已通过日期范围校验的查询条件
|
||||
* @return 按日期升序排列的查询日期
|
||||
@@ -621,9 +623,9 @@ public class HardCanteenApptTotalServiceImpl implements HardCanteenApptTotalServ
|
||||
}
|
||||
Map<Long, String> merchantNameMap = fetchMerchantNameMap(orders);
|
||||
Map<Long, Map<LocalDate, List<HardCanteenUserApptOrderVo>>> orderMap = orders.stream()
|
||||
.filter(order -> order.getUserId() != null && order.getApptUseDate() != null)
|
||||
.filter(order -> order.getUserId() != null && resolveStatisticsDate(order, query) != null)
|
||||
.collect(Collectors.groupingBy(HardOrderVo::getUserId, LinkedHashMap::new,
|
||||
Collectors.groupingBy(HardOrderVo::getApptUseDate, LinkedHashMap::new,
|
||||
Collectors.groupingBy(order -> resolveStatisticsDate(order, query), LinkedHashMap::new,
|
||||
Collectors.mapping(order -> toOrderVo(order, merchantNameMap), Collectors.toList()))));
|
||||
|
||||
return users.stream().filter(user -> user.getUserId() != null).flatMap(user -> queryDates.stream().map(date -> {
|
||||
@@ -678,16 +680,78 @@ public class HardCanteenApptTotalServiceImpl implements HardCanteenApptTotalServ
|
||||
new ArrayList<>(userIds.subList(start, end)));
|
||||
orders.addAll(hardOrderMapper.scopeList(orderQuery, null));
|
||||
}
|
||||
return orders;
|
||||
return retainEffectiveReservationOrders(orders, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分批读取预约订单关联的购物车快照,避免统计请求因用户范围过大而生成超长 IN 条件。
|
||||
* 只保留完成退款后仍有有效商品的预约订单,并把订单展示金额改为退款后的净商品金额。
|
||||
*
|
||||
* <p>已核销筛选直接使用菜品销售统计事实判定有效订单;其他预约状态读取退款后有效商品快照。
|
||||
* 这样全额退款订单不会继续计入人员、订单和金额,部分退款只保留实际剩余值。</p>
|
||||
*
|
||||
* @param orders 已按订单状态和业务日期查询出的预约订单
|
||||
* @param query 当前人员页面筛选条件
|
||||
* @return 完成退款后仍有商品的订单,订单总价已更新为净额
|
||||
*/
|
||||
private List<HardOrderVo> retainEffectiveReservationOrders(List<HardOrderVo> orders,
|
||||
HardCanteenUserApptQueryDto query) {
|
||||
if (orders.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
Map<Long, Long> effectiveTotalPriceMap;
|
||||
if (isVerifiedFilter(query)) {
|
||||
Set<Long> orderIds = orders.stream().map(HardOrderVo::getId)
|
||||
.filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
effectiveTotalPriceMap = fetchReservationVerifiedSaleFacts(
|
||||
orders.stream().map(HardOrderVo::getUserId).filter(Objects::nonNull).distinct().toList(), query)
|
||||
.stream().filter(fact -> fact.getOrderId() != null && orderIds.contains(fact.getOrderId()))
|
||||
.collect(Collectors.groupingBy(HardCanteenReservationSaleFact::getOrderId,
|
||||
Collectors.summingLong(fact -> number(fact.getTotalPrice()))));
|
||||
} else {
|
||||
Map<Long, List<HardCarEntity>> carMap = fetchEffectiveReservationCars(orders);
|
||||
effectiveTotalPriceMap = orders.stream()
|
||||
.filter(order -> order.getId() != null && order.getParentCarId() != null)
|
||||
.filter(order -> carMap.containsKey(order.getParentCarId()))
|
||||
.collect(Collectors.toMap(HardOrderVo::getId,
|
||||
order -> carMap.get(order.getParentCarId()).stream()
|
||||
.map(HardCarEntity::getTotalPrice).mapToLong(HardCanteenApptTotalServiceImpl::number).sum(),
|
||||
(left, right) -> left, LinkedHashMap::new));
|
||||
}
|
||||
return orders.stream().filter(order -> order.getId() != null)
|
||||
.filter(order -> effectiveTotalPriceMap.containsKey(order.getId()))
|
||||
.peek(order -> order.setTotalPrice(Math.toIntExact(effectiveTotalPriceMap.get(order.getId()))))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 分批读取预约订单关联的退款后有效商品,避免统计请求生成超长父购物车 IN 条件。
|
||||
*
|
||||
* @param orders 当前筛选口径下的预约订单
|
||||
* @return 父购物车主键到商品快照的映射
|
||||
* @param query 当前人员页面筛选条件;已核销时用于切换到销售统计事实
|
||||
* @return 父购物车主键到有效商品快照的映射
|
||||
*/
|
||||
private Map<Long, List<HardCarEntity>> fetchReservationCars(List<HardOrderVo> orders) {
|
||||
private Map<Long, List<HardCarEntity>> fetchReservationCars(List<HardOrderVo> orders,
|
||||
HardCanteenUserApptQueryDto query) {
|
||||
if (isVerifiedFilter(query)) {
|
||||
Map<Long, HardOrderVo> orderMap = orders.stream().filter(order -> order.getId() != null)
|
||||
.collect(Collectors.toMap(HardOrderVo::getId, Function.identity(), (left, right) -> left));
|
||||
return fetchReservationVerifiedSaleFacts(
|
||||
orders.stream().map(HardOrderVo::getUserId).filter(Objects::nonNull).distinct().toList(), query)
|
||||
.stream().filter(fact -> fact.getOrderId() != null && orderMap.containsKey(fact.getOrderId()))
|
||||
.map(fact -> toEffectiveCar(fact, orderMap.get(fact.getOrderId())))
|
||||
.collect(Collectors.groupingBy(HardCarEntity::getParentCarId, LinkedHashMap::new,
|
||||
Collectors.toList()));
|
||||
}
|
||||
return fetchEffectiveReservationCars(orders);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按父购物车分批读取完成退款扣减后的预约商品。
|
||||
*
|
||||
* @param orders 当前筛选口径下的预约订单
|
||||
* @return 父购物车主键到退款后有效商品的映射
|
||||
*/
|
||||
private Map<Long, List<HardCarEntity>> fetchEffectiveReservationCars(List<HardOrderVo> orders) {
|
||||
List<Long> parentCarIds = orders.stream().map(HardOrderVo::getParentCarId)
|
||||
.filter(Objects::nonNull).distinct().toList();
|
||||
if (parentCarIds.isEmpty()) {
|
||||
@@ -696,12 +760,58 @@ public class HardCanteenApptTotalServiceImpl implements HardCanteenApptTotalServ
|
||||
Map<Long, List<HardCarEntity>> result = new LinkedHashMap<>();
|
||||
for (int start = 0; start < parentCarIds.size(); start += ORDER_USER_BATCH_SIZE) {
|
||||
int end = Math.min(start + ORDER_USER_BATCH_SIZE, parentCarIds.size());
|
||||
hardCarMapper.listByParentCarIds(parentCarIds.subList(start, end))
|
||||
hardCarMapper.listEffectiveReservationCars(parentCarIds.subList(start, end))
|
||||
.forEach(car -> result.computeIfAbsent(car.getParentCarId(), key -> new ArrayList<>()).add(car));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按用户分批查询菜品销售统计中的预约核销商品事实。
|
||||
*
|
||||
* @param userIds 已经过 UPMS 数据权限和人员条件限制的用户主键
|
||||
* @param query 核销日期、食堂和餐段筛选条件
|
||||
* @return 完成退款扣减后仍有商品的预约核销事实
|
||||
*/
|
||||
private List<HardCanteenReservationSaleFact> fetchReservationVerifiedSaleFacts(
|
||||
List<Long> userIds, HardCanteenUserApptQueryDto query) {
|
||||
if (userIds.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
List<HardCanteenReservationSaleFact> facts = new ArrayList<>();
|
||||
for (int start = 0; start < userIds.size(); start += ORDER_USER_BATCH_SIZE) {
|
||||
int end = Math.min(start + ORDER_USER_BATCH_SIZE, userIds.size());
|
||||
HardOrderDto factQuery = new HardOrderDto();
|
||||
factQuery.setUserIds(new ArrayList<>(userIds.subList(start, end)));
|
||||
factQuery.setMerchantId(query.getMerchantId());
|
||||
factQuery.setHourType(query.getHourType());
|
||||
factQuery.setSaleSourceCodes(List.of(HardSaleSourceEnum.RESERVATION_VERIFIED.getCode()));
|
||||
factQuery.setStatisticsDateTimeList(new java.time.LocalDateTime[]{
|
||||
query.getApptUseDateList()[0].atStartOfDay(),
|
||||
query.getApptUseDateList()[1].atTime(LocalTime.MAX)
|
||||
});
|
||||
facts.addAll(hardCarMapper.listReservationVerifiedSaleFacts(factQuery));
|
||||
}
|
||||
return facts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将销售统计商品事实转换为人员统计聚合器使用的有效购物车快照。
|
||||
*
|
||||
* @param fact 已扣完成退款的预约核销商品事实
|
||||
* @param order 商品所属预约订单
|
||||
* @return 仅包含统计所需父购物车、商品、份数和金额的内存对象
|
||||
*/
|
||||
private HardCarEntity toEffectiveCar(HardCanteenReservationSaleFact fact, HardOrderVo order) {
|
||||
HardCarEntity car = new HardCarEntity();
|
||||
car.setParentCarId(order.getParentCarId());
|
||||
car.setProductId(fact.getProductId());
|
||||
car.setProductName(fact.getProductName());
|
||||
car.setProductCount(fact.getProductCount());
|
||||
car.setTotalPrice(fact.getTotalPrice());
|
||||
return car;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造用户预约列表、候选用户预查询和导出明细共用的订单条件。
|
||||
*
|
||||
@@ -719,15 +829,64 @@ public class HardCanteenApptTotalServiceImpl implements HardCanteenApptTotalServ
|
||||
orderQuery.setMerchantId(query.getMerchantId());
|
||||
orderQuery.setHourType(query.getHourType());
|
||||
orderQuery.setOrderType(HardOrderTypeEnum.RESERVE.getType());
|
||||
orderQuery.setOrderStatusCodes(VALID_RESERVATION_ORDER_STATUSES);
|
||||
orderQuery.setApptIdOrApptRuleIdNotNull(true);
|
||||
orderQuery.setApptUseDateTimeList(new java.time.LocalDateTime[]{
|
||||
query.getApptUseDateList()[0].atStartOfDay(),
|
||||
query.getApptUseDateList()[1].atTime(LocalTime.MAX)
|
||||
});
|
||||
java.time.LocalDateTime[] dateTimeRange = new java.time.LocalDateTime[]{
|
||||
query.getApptUseDateList()[0].atStartOfDay(), query.getApptUseDateList()[1].atTime(LocalTime.MAX)};
|
||||
if (isVerifiedFilter(query)) {
|
||||
orderQuery.setOrderStatusCodes(List.of(HardOrderStatusEnum.CANCEL.getType()));
|
||||
orderQuery.setVerificationTimeList(dateTimeRange);
|
||||
} else {
|
||||
orderQuery.setOrderStatusCodes(isUnverifiedFilter(query)
|
||||
? List.of(HardOrderStatusEnum.PAY.getType()) : VALID_RESERVATION_ORDER_STATUSES);
|
||||
orderQuery.setApptUseDateTimeList(dateTimeRange);
|
||||
}
|
||||
return orderQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析当前订单在人员页面中的统计日期。
|
||||
*
|
||||
* @param order 预约订单
|
||||
* @param query 当前核销状态筛选
|
||||
* @return 已核销筛选返回实际核销日期,其他情况返回预约用餐日期
|
||||
*/
|
||||
private LocalDate resolveStatisticsDate(HardOrderVo order, HardCanteenUserApptQueryDto query) {
|
||||
if (isVerifiedFilter(query)) {
|
||||
return order.getVerificationTime() == null ? null : order.getVerificationTime().toLocalDate();
|
||||
}
|
||||
return order.getApptUseDate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前查询是否要求按单笔已核销订单统计。
|
||||
*
|
||||
* @param query 人员页面查询条件
|
||||
* @return 核销状态为 VERIFIED 时返回 true
|
||||
*/
|
||||
private boolean isVerifiedFilter(HardCanteenUserApptQueryDto query) {
|
||||
return Objects.equals(query.getVerificationStatus(), VERIFICATION_STATUS_VERIFIED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前查询是否要求按单笔待核销订单统计。
|
||||
*
|
||||
* @param query 人员页面查询条件
|
||||
* @return 核销状态为 UNVERIFIED 时返回 true
|
||||
*/
|
||||
private boolean isUnverifiedFilter(HardCanteenUserApptQueryDto query) {
|
||||
return Objects.equals(query.getVerificationStatus(), VERIFICATION_STATUS_UNVERIFIED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将可能为空的数值安全归一为长整型零值。
|
||||
*
|
||||
* @param value 商品数量或金额
|
||||
* @return 原数值的长整型表示;空值返回零
|
||||
*/
|
||||
private static long number(Number value) {
|
||||
return value == null ? 0L : value.longValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量补齐订单关联的食堂名称;商户服务异常时保留订单并返回空名称,避免影响预约事实查询。
|
||||
*
|
||||
@@ -767,6 +926,7 @@ public class HardCanteenApptTotalServiceImpl implements HardCanteenApptTotalServ
|
||||
HardCanteenUserApptOrderVo detail = new HardCanteenUserApptOrderVo();
|
||||
detail.setId(order.getId());
|
||||
detail.setHourType(order.getHourType());
|
||||
detail.setApptUseDate(order.getApptUseDate());
|
||||
detail.setProductDesc(order.getProductDesc());
|
||||
detail.setMerchantName(merchantNameMap.get(order.getMerchantId()));
|
||||
detail.setTotalPrice(order.getTotalPrice());
|
||||
|
||||
+38
@@ -356,6 +356,44 @@
|
||||
</if>
|
||||
</sql>
|
||||
|
||||
<!-- 人员已核销统计直接读取菜品统计的预约核销事实,避免再次实现核销日期和退款扣减规则。 -->
|
||||
<select id="listReservationVerifiedSaleFacts"
|
||||
resultType="com.bsd.pjcloud.hard.mapper.order.model.HardCanteenReservationSaleFact">
|
||||
select sale_stats.stat_time as statisticsTime,
|
||||
sale_stats.order_id as orderId,
|
||||
sale_stats.user_id as userId,
|
||||
sale_stats.hour_type as hourType,
|
||||
sale_stats.product_id as productId,
|
||||
sale_stats.product_name as productName,
|
||||
sale_stats.product_count as productCount,
|
||||
sale_stats.total_price as totalPrice
|
||||
from <include refid="canteenSaleStatsSource"/>
|
||||
<where>
|
||||
<include refid="canteenSaleStatsWhere"/>
|
||||
and sale_stats.sale_source = 'RESERVATION_VERIFIED'
|
||||
</where>
|
||||
order by sale_stats.stat_time, sale_stats.order_id, sale_stats.product_id
|
||||
</select>
|
||||
|
||||
<!-- 非已核销人员统计按父购物车读取同一套完成退款净商品,外层限制避免影响其他预约统计调用。 -->
|
||||
<select id="listEffectiveReservationCars" resultMap="hardCarMap">
|
||||
select a.parent_car_id,
|
||||
a.product_id,
|
||||
a.product_name,
|
||||
a.product_pic,
|
||||
a.product_price,
|
||||
a.product_count,
|
||||
a.total_price,
|
||||
a.create_time,
|
||||
a.del_flag
|
||||
from <include refid="apptEffectiveCarSource"/>
|
||||
where a.parent_car_id in
|
||||
<foreach collection="parentCarIds" item="parentCarId" open="(" close=")" separator=",">
|
||||
#{parentCarId}
|
||||
</foreach>
|
||||
order by a.parent_car_id, a.product_id
|
||||
</select>
|
||||
|
||||
<select id="productTotalByDept" resultType="com.bsd.pjcloud.hard.api.vo.order.HardCanteenProductTotalVo">
|
||||
select DATE_FORMAT(a.create_time, '%Y-%m-%d') as createTime,
|
||||
o.user_dept_id,
|
||||
|
||||
+6
-3
@@ -665,11 +665,14 @@ public class ThirdAppPlusAliPayServiceImpl implements ThirdAppPlusAliPayService
|
||||
* 判断支付宝退款查询是否尚未找到当前稳定退款请求。
|
||||
*
|
||||
* @param response 支付宝退款查询响应
|
||||
* @return 成功响应缺少退款状态,或返回明确的退款/交易不存在错误码时返回 {@code true}
|
||||
* @return 仅在没有任何退款状态且成功响应未给出记录,或返回明确的退款/交易不存在错误码时返回 {@code true}
|
||||
*/
|
||||
private boolean isAlipayRefundQueryMissing(AlipayTradeFastpayRefundQueryResponse response) {
|
||||
return response != null && (response.isSuccess() && StrUtil.isBlank(response.getRefundStatus())
|
||||
|| ALIPAY_REFUND_NOT_FOUND_CODES.contains(response.getSubCode()));
|
||||
if (response == null || StrUtil.isNotBlank(response.getRefundStatus())) {
|
||||
// refundStatus 是支付宝退款查询的业务事实;即使 subCode 为空,也不能覆盖明确的退款终态。
|
||||
return false;
|
||||
}
|
||||
return response.isSuccess() || ALIPAY_REFUND_NOT_FOUND_CODES.contains(response.getSubCode());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+13
-11
@@ -233,7 +233,9 @@ public class ThirdAppPlusServiceImpl implements ThirdAppPlusService {
|
||||
*/
|
||||
private final RemoteHardOrderService remoteHardOrderService;
|
||||
|
||||
/** 支付创建前验证并固定商户平台分佣关系。 */
|
||||
/**
|
||||
* 支付创建前验证并固定商户平台分佣关系。
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
private ThirdMchFzRelationService mchFzRelationService;
|
||||
|
||||
@@ -475,7 +477,7 @@ public class ThirdAppPlusServiceImpl implements ThirdAppPlusService {
|
||||
* 远程 DTO 中的商户 ID 只用于一致性校验,不能覆盖订单归属。
|
||||
*
|
||||
* @param userId 当前付款用户 ID
|
||||
* @param dto 小程序支付请求
|
||||
* @param dto 小程序支付请求
|
||||
* @return 已校验的商城收款商户
|
||||
*/
|
||||
private MchManageVo resolveShopPayMerchant(Long userId, ThirdMiniAppPayDto dto) {
|
||||
@@ -747,7 +749,7 @@ public class ThirdAppPlusServiceImpl implements ThirdAppPlusService {
|
||||
* 解析默认关系并生成订单级渠道配置快照,二级商户与支付宝授权均不得再从支付配置 JSON 读取。
|
||||
*
|
||||
* @param paySetting 已由商户服务确认启用的支付配置
|
||||
* @param payDto 即将提交渠道服务的内部支付参数
|
||||
* @param payDto 即将提交渠道服务的内部支付参数
|
||||
* @return 由关系事实生成的独立支付配置快照
|
||||
*/
|
||||
private MchPaySettingVo applyDefaultPaymentRelation(MchPaySettingVo paySetting, ThirdAppPlusPayDto payDto) {
|
||||
@@ -1223,18 +1225,18 @@ public class ThirdAppPlusServiceImpl implements ThirdAppPlusService {
|
||||
if (query == null) {
|
||||
return;
|
||||
}
|
||||
boolean refundSuccess = "REFUND_SUCCESS".equals(query.getRefundStatus());
|
||||
boolean retryable = (query.isSuccess() && StrUtil.isBlank(query.getRefundStatus()))
|
||||
|| StrUtil.isBlank(query.getSubCode())
|
||||
|| List.of("ACQ.SYSTEM_ERROR", "ACQ.TRADE_SETTLE_ERROR", "ACQ.FREQUENCY_LIMITED")
|
||||
.contains(query.getSubCode());
|
||||
if (!refundSuccess && retryable) {
|
||||
String refundStatus = query.getRefundStatus();
|
||||
boolean refundSuccess = "REFUND_SUCCESS".equals(refundStatus);
|
||||
boolean refundProcessing = "REFUND_PROCESSING".equals(refundStatus) || "PROCESSING".equals(refundStatus);
|
||||
// subCode 为空是支付宝成功查询的正常返回形式,只有退款状态也为空时才可作为未知结果重试。
|
||||
boolean refundStatusMissing = StrUtil.isBlank(refundStatus);
|
||||
boolean retryable = refundStatusMissing && (query.isSuccess() || StrUtil.isBlank(query.getSubCode()) || List.of("ACQ.SYSTEM_ERROR", "ACQ.TRADE_SETTLE_ERROR", "ACQ.FREQUENCY_LIMITED").contains(query.getSubCode()));
|
||||
if (!refundSuccess && (refundProcessing || retryable)) {
|
||||
return;
|
||||
}
|
||||
ThirdPayOrderEntity updateObj = new ThirdPayOrderEntity();
|
||||
updateObj.setRefundCallbackRes(JSONUtil.toJsonStr(query));
|
||||
updateObj.setStatus(refundSuccess ? ThirdPayOrderStatusEnum.REFUND_SUCCESS.getType()
|
||||
: ThirdPayOrderStatusEnum.REFUND_FAILED.getType());
|
||||
updateObj.setStatus(refundSuccess ? ThirdPayOrderStatusEnum.REFUND_SUCCESS.getType() : ThirdPayOrderStatusEnum.REFUND_FAILED.getType());
|
||||
if (!refundSuccess) {
|
||||
updateObj.setIsBizCallback(YesNoEnum.YES.getCode());
|
||||
}
|
||||
|
||||
+18
@@ -445,6 +445,24 @@ class ThirdAppPlusAliPayServiceImplTest {
|
||||
verify(alipayClient, never()).execute(any(AlipayTradeRefundRequest.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证支付宝明确返回退款成功时,即使 subCode 为空也保留退款业务事实,不会误触发退款重提。
|
||||
*/
|
||||
@Test
|
||||
void queryRefundShouldKeepSuccessWhenSubCodeIsBlank() throws Exception {
|
||||
ThirdPayOrderEntity payOrder = refundingOrderWithRequest("refund-blank-sub-code");
|
||||
AlipayTradeFastpayRefundQueryResponse success = new AlipayTradeFastpayRefundQueryResponse();
|
||||
success.setCode("10000");
|
||||
success.setMsg("Success");
|
||||
success.setRefundStatus("REFUND_SUCCESS");
|
||||
success.setRefundAmount("1.00");
|
||||
success.setSendBackFee("1.00");
|
||||
when(alipayClient.execute(any(AlipayTradeFastpayRefundQueryRequest.class))).thenReturn(success);
|
||||
|
||||
Assertions.assertSame(success, service.queryRefund(payOrder));
|
||||
verify(alipayClient, never()).execute(any(AlipayTradeRefundRequest.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void precreateShouldSetRequiredParametersAndSaveUnpaidOrder() throws Exception {
|
||||
AlipayTradePrecreateResponse response = successPrecreateResponse("https://qr.alipay.com/test-content");
|
||||
|
||||
+29
@@ -2,6 +2,7 @@ package com.bsd.pjcloud.thirdparty.service.impl.appplus;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.alipay.api.response.AlipayTradeCancelResponse;
|
||||
import com.alipay.api.response.AlipayTradeFastpayRefundQueryResponse;
|
||||
import com.alipay.api.response.AlipayTradeQueryResponse;
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.bsd.pjcloud.admin.api.feign.RemoteTokenService;
|
||||
@@ -645,6 +646,34 @@ class ThirdAppPlusServiceImplOrderCodePayTest {
|
||||
Assertions.assertEquals(ThirdPayOrderStatusEnum.REFUND_FAILED.getType(), updateCaptor.getAllValues().get(2).getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证支付宝退款成功以 refundStatus 为准,subCode 为空不会阻止订单落库和响应快照回填。
|
||||
*/
|
||||
@Test
|
||||
void queryRefundResultShouldMapAlipaySuccessWhenSubCodeIsBlank() {
|
||||
ThirdPayOrderEntity refundingOrder = refundingOrder(205L, "refund-alipay-success");
|
||||
refundingOrder.setPayPlatformType(AppPlusPlatformTypeEnum.ALI_PAY.getType());
|
||||
AlipayTradeFastpayRefundQueryResponse query = new AlipayTradeFastpayRefundQueryResponse();
|
||||
query.setCode("10000");
|
||||
query.setMsg("Success");
|
||||
query.setRefundStatus("REFUND_SUCCESS");
|
||||
query.setRefundAmount("1.00");
|
||||
when(thirdMiniAppAliPayService.queryRefund(refundingOrder)).thenReturn(query);
|
||||
when(thirdPayOrderService.update(any(ThirdPayOrderEntity.class), any(Wrapper.class))).thenReturn(true);
|
||||
ThirdPayOrderEntity latest = refundingOrder(205L, "refund-alipay-success");
|
||||
latest.setPayPlatformType(AppPlusPlatformTypeEnum.ALI_PAY.getType());
|
||||
latest.setStatus(ThirdPayOrderStatusEnum.REFUND_SUCCESS.getType());
|
||||
latest.setIsBizCallback(YesNoEnum.YES.getCode());
|
||||
when(thirdPayOrderService.getById(205L)).thenReturn(latest);
|
||||
|
||||
service.queryRefundResult(refundingOrder);
|
||||
|
||||
ArgumentCaptor<ThirdPayOrderEntity> updateCaptor = ArgumentCaptor.forClass(ThirdPayOrderEntity.class);
|
||||
verify(thirdPayOrderService).update(updateCaptor.capture(), any(Wrapper.class));
|
||||
Assertions.assertEquals(ThirdPayOrderStatusEnum.REFUND_SUCCESS.getType(), updateCaptor.getValue().getStatus());
|
||||
Assertions.assertTrue(updateCaptor.getValue().getRefundCallbackRes().contains("REFUND_SUCCESS"));
|
||||
}
|
||||
|
||||
private void mockNoExistingOrder() {
|
||||
when(thirdPayOrderService.count(any(Wrapper.class))).thenReturn(0L);
|
||||
when(thirdPayOrderService.getOne(any(Wrapper.class))).thenReturn(null);
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package com.bsd.pjcloud.wallet.api.vo.export;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.alibaba.excel.annotation.format.NumberFormat;
|
||||
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||
import com.alibaba.excel.annotation.write.style.ContentFontStyle;
|
||||
import com.alibaba.excel.annotation.write.style.HeadFontStyle;
|
||||
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 钱包账户余额及月度钱包记录导出行。
|
||||
*
|
||||
* <p>账户余额导出使用当前累计金额,月度记录导出使用指定月份末的历史余额;两种场景共享完整用户、
|
||||
* 身份和钱包二级表头,不依赖固定 Excel 模板。</p>
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ColumnWidth(15)
|
||||
@HeadRowHeight(15)
|
||||
@HeadFontStyle(fontHeightInPoints = 10)
|
||||
@ContentFontStyle(fontHeightInPoints = 10)
|
||||
public class WalletManageExportExcelVo extends WalletUserExportExcelVo {
|
||||
|
||||
/** 当前钱包在本次导出结果中的一基序号。 */
|
||||
@ExcelProperty(value = {"钱包", "序号"}, index = 10)
|
||||
private String lineNumber;
|
||||
|
||||
/** 月度记录查询月份,账户当前余额导出时为空。 */
|
||||
@ExcelProperty(value = {"钱包", "统计月份"}, index = 11)
|
||||
private String recordMonth;
|
||||
|
||||
/** 钱包类型中文名称;类型资料缺失时为空。 */
|
||||
@ExcelProperty(value = {"钱包", "钱包类型"}, index = 12)
|
||||
private String walletTypeName;
|
||||
|
||||
/** 钱包实体卡号;未绑定实体卡时为空。 */
|
||||
@ExcelProperty(value = {"钱包", "卡号"}, index = 13)
|
||||
private String cardNumber;
|
||||
|
||||
/** 当前或指定月份末钱包余额,单位为元并保留两位小数。 */
|
||||
@ExcelProperty(value = {"金额", "余额(元)"}, index = 14)
|
||||
@NumberFormat("#,##0.00")
|
||||
private BigDecimal balance;
|
||||
|
||||
/** 钱包累计消费金额,单位为元并保留两位小数。 */
|
||||
@ExcelProperty(value = {"金额", "累计消费(元)"}, index = 15)
|
||||
@NumberFormat("#,##0.00")
|
||||
private BigDecimal totalExpense;
|
||||
|
||||
/** 钱包累计充值金额,单位为元并保留两位小数。 */
|
||||
@ExcelProperty(value = {"金额", "累计充值(元)"}, index = 16)
|
||||
@NumberFormat("#,##0.00")
|
||||
private BigDecimal totalRecharge;
|
||||
|
||||
/** 钱包冻结状态中文名称;空值表示历史数据未记录。 */
|
||||
@ExcelProperty(value = {"钱包", "冻结状态"}, index = 17)
|
||||
private String freezeStatus;
|
||||
|
||||
/** 指定月份末余额对应的最后流水时间,格式为 yyyy-MM-dd HH:mm:ss。 */
|
||||
@ExcelProperty(value = {"钱包", "最后变动时间"}, index = 18)
|
||||
@ColumnWidth(22)
|
||||
private String lastTopUpTime;
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package com.bsd.pjcloud.wallet.api.vo.export;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.alibaba.excel.annotation.format.NumberFormat;
|
||||
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||
import com.alibaba.excel.annotation.write.style.ContentFontStyle;
|
||||
import com.alibaba.excel.annotation.write.style.HeadFontStyle;
|
||||
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 批量充值扣款水单导出行,按用户、身份、审批和金额组织二级表头。
|
||||
*
|
||||
* <p>用户与身份资料由 UPMS 完整导出接口按批补齐,金额统一转换为元;扣款和食堂退还场景保持负数,
|
||||
* 以便财务人员直接汇总。</p>
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ColumnWidth(15)
|
||||
@HeadRowHeight(15)
|
||||
@HeadFontStyle(fontHeightInPoints = 10)
|
||||
@ContentFontStyle(fontHeightInPoints = 10)
|
||||
public class WalletRechargeImportExportExcelVo extends WalletUserExportExcelVo {
|
||||
|
||||
/** 当前水单明细在本次导出结果中的一基序号。 */
|
||||
@ExcelProperty(value = {"水单", "序号"}, index = 10)
|
||||
private String lineNumber;
|
||||
|
||||
/** 批量充值或扣款所属月份,格式为 yyyy-MM。 */
|
||||
@ExcelProperty(value = {"水单", "业务月份"}, index = 11)
|
||||
private String importMonth;
|
||||
|
||||
/** 批量处理业务类型中文名称;未知历史编码保留原值。 */
|
||||
@ExcelProperty(value = {"水单", "业务类型"}, index = 12)
|
||||
private String bizTypeName;
|
||||
|
||||
/** 钱包类型中文名称;类型资料缺失时为空。 */
|
||||
@ExcelProperty(value = {"钱包", "钱包类型"}, index = 13)
|
||||
private String walletTypeName;
|
||||
|
||||
/** 操作前钱包金额,单位为元并保留两位小数。 */
|
||||
@ExcelProperty(value = {"金额", "操作前金额(元)"}, index = 14)
|
||||
@NumberFormat("#,##0.00")
|
||||
private BigDecimal beforePrice;
|
||||
|
||||
/** 本次操作金额,单位为元并保留两位小数;扣款和退还场景为负数。 */
|
||||
@ExcelProperty(value = {"金额", "操作金额(元)"}, index = 15)
|
||||
@NumberFormat("#,##0.00")
|
||||
private BigDecimal price;
|
||||
|
||||
/** 财务统计标签原始字典值,多个值使用英文逗号分隔。 */
|
||||
@ExcelProperty(value = {"水单", "财务标签"}, index = 16)
|
||||
private String tags;
|
||||
|
||||
/** 审批流程状态原始字典值,便于与管理端字典核对。 */
|
||||
@ExcelProperty(value = {"审批", "审批状态"}, index = 17)
|
||||
private String processStatus;
|
||||
|
||||
/** 审批处理时间,格式为 yyyy-MM-dd HH:mm:ss。 */
|
||||
@ExcelProperty(value = {"审批", "审批时间"}, index = 18)
|
||||
@ColumnWidth(22)
|
||||
private String processTime;
|
||||
|
||||
/** 水单明细创建时间,格式为 yyyy-MM-dd HH:mm:ss。 */
|
||||
@ExcelProperty(value = {"水单", "创建时间"}, index = 19)
|
||||
@ColumnWidth(22)
|
||||
private String createTime;
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.bsd.pjcloud.wallet.api.vo.export;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 个人充值统计导出行。
|
||||
*
|
||||
* <p>在钱包流水公共用户、身份和金额列基础上补充所属食堂,保留历史个人充值统计
|
||||
* 的财务核对维度;导出使用 EasyExcel 二级表头,不依赖固定模板。</p>
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class WalletRechargePersonExportExcelVo extends WalletTransactionExportExcelVo {
|
||||
|
||||
/** 流水所属食堂名称,来源于钱包商户部门视图;无法匹配时为空。 */
|
||||
@ExcelProperty(value = {"食堂", "所属食堂"}, index = 20)
|
||||
private String canteenName;
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package com.bsd.pjcloud.wallet.api.vo.export;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.alibaba.excel.annotation.format.NumberFormat;
|
||||
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||
import com.alibaba.excel.annotation.write.style.ContentFontStyle;
|
||||
import com.alibaba.excel.annotation.write.style.HeadFontStyle;
|
||||
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 钱包流水导出行,按用户、身份、钱包和流水业务域组织二级表头。
|
||||
*
|
||||
* <p>充值、记账扣款、消费、退款、食堂退还及个人充值统计共用该结构;金额统一以元展示,
|
||||
* 未识别的历史业务编码保留原值,避免导出过程丢失业务信息。</p>
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ColumnWidth(15)
|
||||
@HeadRowHeight(15)
|
||||
@HeadFontStyle(fontHeightInPoints = 10)
|
||||
@ContentFontStyle(fontHeightInPoints = 10)
|
||||
public class WalletTransactionExportExcelVo extends WalletUserExportExcelVo {
|
||||
|
||||
/** 当前流水在本次导出结果中的一基序号。 */
|
||||
@ExcelProperty(value = {"流水", "序号"}, index = 10)
|
||||
private String lineNumber;
|
||||
|
||||
/** 钱包类型中文名称;类型资料缺失时为空。 */
|
||||
@ExcelProperty(value = {"钱包", "钱包类型"}, index = 11)
|
||||
private String walletTypeName;
|
||||
|
||||
/** 钱包卡号;未绑定实体卡时为空。 */
|
||||
@ExcelProperty(value = {"钱包", "卡号"}, index = 12)
|
||||
private String cardNumber;
|
||||
|
||||
/** 流水业务类型中文名称;未知历史编码保留原值。 */
|
||||
@ExcelProperty(value = {"流水", "业务类型"}, index = 13)
|
||||
private String bizTypeName;
|
||||
|
||||
/** 流水业务标题,来源于交易发生时保存的标题快照。 */
|
||||
@ExcelProperty(value = {"流水", "标题"}, index = 14)
|
||||
private String title;
|
||||
|
||||
/** 本次交易金额,单位为元并保留两位小数,支出保留负数。 */
|
||||
@ExcelProperty(value = {"流水", "交易金额(元)"}, index = 15)
|
||||
@NumberFormat("#,##0.00")
|
||||
private BigDecimal price;
|
||||
|
||||
/** 交易完成后的钱包余额,单位为元并保留两位小数。 */
|
||||
@ExcelProperty(value = {"流水", "交易后余额(元)"}, index = 16)
|
||||
@NumberFormat("#,##0.00")
|
||||
private BigDecimal balance;
|
||||
|
||||
/** 财务统计标签原始字典值,多个值使用英文逗号分隔。 */
|
||||
@ExcelProperty(value = {"流水", "财务标签"}, index = 17)
|
||||
private String tags;
|
||||
|
||||
/** 后台补充的流水业务备注;历史记录允许为空。 */
|
||||
@ExcelProperty(value = {"流水", "备注"}, index = 18)
|
||||
@ColumnWidth(24)
|
||||
private String remark;
|
||||
|
||||
/** 流水创建时间,格式为 yyyy-MM-dd HH:mm:ss。 */
|
||||
@ExcelProperty(value = {"流水", "交易时间"}, index = 19)
|
||||
@ColumnWidth(22)
|
||||
private String createTime;
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.bsd.pjcloud.wallet.api.vo.export;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 钱包业务用户导出的公共用户与身份列。
|
||||
*
|
||||
* <p>用户资料统一来源于 UPMS 完整导出接口,业务数据只在用户已经删除或历史资料缺失时作为展示回退。
|
||||
* 该对象仅定义二级表头的公共列,不参与数据库持久化和业务接口传输。</p>
|
||||
*/
|
||||
@Data
|
||||
public abstract class WalletUserExportExcelVo {
|
||||
|
||||
/** 用户姓名,优先使用 UPMS 姓名,其次使用钱包业务历史展示名。 */
|
||||
@ExcelProperty(value = {"用户", "姓名"}, index = 0)
|
||||
private String userName;
|
||||
|
||||
/** 用户绑定手机号,优先使用 UPMS 当前手机号。 */
|
||||
@ExcelProperty(value = {"用户", "手机号"}, index = 1)
|
||||
private String phone;
|
||||
|
||||
/** 用户当前所属部门名称,UPMS 资料缺失时回退业务记录中的部门名称。 */
|
||||
@ExcelProperty(value = {"用户", "部门"}, index = 2)
|
||||
private String deptName;
|
||||
|
||||
/** 用户当前岗位名称,多个岗位使用中文逗号分隔。 */
|
||||
@ExcelProperty(value = {"用户", "岗位"}, index = 3)
|
||||
private String postNames;
|
||||
|
||||
/** 用户拥有的身份中文名称,多个身份使用中文逗号分隔。 */
|
||||
@ExcelProperty(value = {"身份", "身份类型"}, index = 4)
|
||||
private String identityTypeNames;
|
||||
|
||||
/** 学生身份学号;用户没有学生身份时为空。 */
|
||||
@ExcelProperty(value = {"身份", "学号"}, index = 5)
|
||||
private String studentNo;
|
||||
|
||||
/** 学生身份学校名称;用户没有学生身份时为空。 */
|
||||
@ExcelProperty(value = {"身份", "学校"}, index = 6)
|
||||
private String schoolName;
|
||||
|
||||
/** 学生身份班级名称;用户没有学生身份时为空。 */
|
||||
@ExcelProperty(value = {"身份", "班级"}, index = 7)
|
||||
private String className;
|
||||
|
||||
/** 学生身份监护人手机号,多个号码沿用 UPMS 业务存储格式。 */
|
||||
@ExcelProperty(value = {"身份", "监护人手机号"}, index = 8)
|
||||
private String guardianPhones;
|
||||
|
||||
/** 老师身份工号或学号;用户没有老师身份时为空。 */
|
||||
@ExcelProperty(value = {"身份", "老师学号"}, index = 9)
|
||||
private String teacherNo;
|
||||
}
|
||||
+5
-5
@@ -25,8 +25,8 @@ import com.bsd.pjcloud.wallet.api.vo.WalletQbTotalVo;
|
||||
import com.bsd.pjcloud.wallet.mapper.WalletManageMapper;
|
||||
import com.bsd.pjcloud.wallet.mapper.mch.view.MchDeptViewMapper;
|
||||
import com.bsd.pjcloud.wallet.service.WalletCodeUseService;
|
||||
import com.bsd.pjcloud.wallet.service.WalletManageExportService;
|
||||
import com.bsd.pjcloud.wallet.service.WalletManageService;
|
||||
import com.bsd.pjcloud.wallet.service.WalletUserExportService;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import org.springdoc.core.annotations.ParameterObject;
|
||||
@@ -51,8 +51,8 @@ public class WalletManageController {
|
||||
private final WalletManageService walletManageService;
|
||||
private final WalletCodeUseService walletCodeUseService;
|
||||
|
||||
/** 钱包管理异步导出服务,负责提交任务并生成可轮询的文件结果。 */
|
||||
private final WalletManageExportService walletManageExportService;
|
||||
/** 钱包用户导出服务,统一生成余额和月度记录二级表头文件。 */
|
||||
private final WalletUserExportService walletUserExportService;
|
||||
|
||||
private final WalletManageMapper walletManageMapper;
|
||||
private final MchDeptViewMapper mchDeptViewMapper;
|
||||
@@ -371,7 +371,7 @@ public class WalletManageController {
|
||||
@PostMapping("/export")
|
||||
@PreAuthorize("@pms.hasPermission('wallet_walletManage_export')")
|
||||
public R<AsyncTaskProgressVO> export(@RequestBody WalletManageDto dto) {
|
||||
return R.ok(walletManageExportService.submitExport(dto), "账户余额导出任务已提交");
|
||||
return R.ok(walletUserExportService.submitWalletBalanceExport(dto), "账户余额导出任务已提交");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -383,6 +383,6 @@ public class WalletManageController {
|
||||
@PostMapping("/export-qb")
|
||||
// @PreAuthorize("@pms.hasPermission('wallet_walletManage_export')")
|
||||
public R<AsyncTaskProgressVO> exportQb(@RequestBody WalletManageBalanceDto dto) {
|
||||
return R.ok(walletManageExportService.submitQbExport(dto), "钱包记录导出任务已提交");
|
||||
return R.ok(walletUserExportService.submitWalletRecordExport(dto), "钱包记录导出任务已提交");
|
||||
}
|
||||
}
|
||||
+121
-47
@@ -5,6 +5,7 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.asynctask.vo.AsyncTaskProgressVO;
|
||||
import com.bsd.pjcloud.common.data.datascope.DataScope;
|
||||
import com.bsd.pjcloud.common.data.datascope.DataScopeFuncEnum;
|
||||
import com.bsd.pjcloud.common.log.annotation.SysLog;
|
||||
@@ -23,6 +24,8 @@ import com.bsd.pjcloud.wallet.api.vo.WalletTypeTotalVo;
|
||||
import com.bsd.pjcloud.wallet.api.vo.transaction.WalletTransactionVo;
|
||||
import com.bsd.pjcloud.wallet.mapper.WalletTransactionMapper;
|
||||
import com.bsd.pjcloud.wallet.service.WalletTransactionService;
|
||||
import com.bsd.pjcloud.wallet.service.WalletTransactionExportScene;
|
||||
import com.bsd.pjcloud.wallet.service.WalletUserExportService;
|
||||
import com.bsd.pjcloud.wallet.support.WalletMessageContextHolder;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
@@ -54,6 +57,9 @@ public class WalletTransactionController {
|
||||
|
||||
private final WalletTransactionMapper walletTransactionMapper;
|
||||
|
||||
/** 钱包用户导出服务,负责异步分片、用户身份补齐和自定义二级表头文件生成。 */
|
||||
private final WalletUserExportService walletUserExportService;
|
||||
|
||||
/* ************************************* start 远程调用 start ************************************* */
|
||||
@Inner
|
||||
@PostMapping("/add-balance")
|
||||
@@ -217,18 +223,16 @@ public class WalletTransactionController {
|
||||
walletTransactionService.exportCzTotal(response, dto, DataScope.of().func(DataScopeFuncEnum.COUNT));
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用自定义二级表头同步导出个人充值统计明细,保留旧接口供现有调用方兼容使用。
|
||||
*
|
||||
* @param response 当前 HTTP 文件下载响应
|
||||
* @param dto 页面当前充值金额、时间、标签和部门筛选条件;金额单位为元
|
||||
*/
|
||||
@Operation(summary = "后台充值统计按照个人(导出)", description = "后台充值统计按照个人(导出)")
|
||||
@PostMapping("/export-cz-total-person")
|
||||
public void exportCzTotalPerson(HttpServletResponse response, @RequestBody WalletTransactionDto dto) {
|
||||
dto.setBizTypes(WalletBizEnum.getTopUpCodes());
|
||||
dto.setType(WalletTransactionDto.TYPE_INCOME);
|
||||
if (ObjectUtil.isNotEmpty(dto.getPrice())) {
|
||||
dto.setPrice(dto.getPrice() * 100);
|
||||
}
|
||||
if (ObjectUtil.isNotEmpty(dto.getPriceList())) {
|
||||
dto.setPriceList(dto.getPriceList().stream().map(item -> item * 100).toList());
|
||||
}
|
||||
walletTransactionService.exportCzTotalPerson(response, dto, DataScope.of().func(DataScopeFuncEnum.COUNT));
|
||||
walletUserExportService.exportTransaction(response, dto, WalletTransactionExportScene.RECHARGE_PERSON_TOTAL);
|
||||
}
|
||||
|
||||
@Operation(summary = "后台充值统计", description = "后台充值统计")
|
||||
@@ -330,19 +334,30 @@ public class WalletTransactionController {
|
||||
return R.ok(walletTransactionService.removeBatchByIds(CollUtil.toList(ids)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用自定义二级表头同步导出充值流水,保留旧接口供现有调用方兼容使用。
|
||||
*
|
||||
* @param response 当前 HTTP 文件下载响应
|
||||
* @param dto 页面当前充值流水筛选条件
|
||||
*/
|
||||
@Operation(summary = "充值导出", description = "充值导出")
|
||||
@PostMapping("/export-cz")
|
||||
@PreAuthorize("@pms.hasPermission('wallet_walletTransaction_export')")
|
||||
public void exportCz(HttpServletResponse response, @RequestBody WalletTransactionDto dto) {
|
||||
dto.setBizTypes(WalletBizEnum.getTopUpCodes());
|
||||
dto.setType(WalletTransactionDto.TYPE_INCOME);
|
||||
walletTransactionService.prepareIdentityQuery(dto);
|
||||
walletUserExportService.exportTransaction(response, dto, WalletTransactionExportScene.RECHARGE);
|
||||
}
|
||||
|
||||
Long count = walletTransactionMapper.count(dto, DataScope.of().func(DataScopeFuncEnum.COUNT));
|
||||
if (count > 5000) throw new RuntimeException("导出数据超出5000条,请缩小查询范围");
|
||||
|
||||
List<WalletTransactionVo> list = walletTransactionService.scopeList(dto, DataScope.of());
|
||||
walletTransactionService.exportByTemplate(response, dto, "file/个人充值明细.xlsx", list);
|
||||
/**
|
||||
* 提交充值流水异步分片导出任务,由后台按稳定顺序分页生成二级表头工作簿。
|
||||
*
|
||||
* @param dto 页面当前充值流水筛选条件
|
||||
* @return 包含任务编号和初始进度的统一响应
|
||||
*/
|
||||
@PostMapping("/export-cz-async")
|
||||
@PreAuthorize("@pms.hasPermission('wallet_walletTransaction_export')")
|
||||
public R<AsyncTaskProgressVO> submitCzExport(@RequestBody WalletTransactionDto dto) {
|
||||
return R.ok(walletUserExportService.submitTransactionExport(dto, WalletTransactionExportScene.RECHARGE),
|
||||
"充值记录导出任务已提交");
|
||||
}
|
||||
|
||||
@Operation(summary = "充值统计", description = "充值统计")
|
||||
@@ -353,19 +368,30 @@ public class WalletTransactionController {
|
||||
return R.ok(walletTransactionService.walletTypetotal(dto, DataScope.of()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用自定义二级表头同步导出记账扣款流水,保留旧接口供现有调用方兼容使用。
|
||||
*
|
||||
* @param response 当前 HTTP 文件下载响应
|
||||
* @param dto 页面当前记账扣款筛选条件
|
||||
*/
|
||||
@Operation(summary = "记账扣款导出", description = "记账扣款导出")
|
||||
@PostMapping("/export-jz")
|
||||
@PreAuthorize("@pms.hasPermission('wallet_walletTransaction_export')")
|
||||
public void exportJz(HttpServletResponse response, @RequestBody WalletTransactionDto dto) {
|
||||
dto.setBizTypes(WalletBizEnum.getTopUpCodes());
|
||||
dto.setType(WalletTransactionDto.TYPE_EXPENSE);
|
||||
walletTransactionService.prepareIdentityQuery(dto);
|
||||
walletUserExportService.exportTransaction(response, dto, WalletTransactionExportScene.ACCOUNTING);
|
||||
}
|
||||
|
||||
Long count = walletTransactionMapper.count(dto, DataScope.of().func(DataScopeFuncEnum.COUNT));
|
||||
if (count > 5000) throw new RuntimeException("导出数据超出5000条,请缩小查询范围");
|
||||
|
||||
List<WalletTransactionVo> list = walletTransactionService.scopeList(dto, DataScope.of());
|
||||
walletTransactionService.exportByTemplate(response, dto, "file/个人记账扣款明细.xlsx", list);
|
||||
/**
|
||||
* 提交记账扣款异步分片导出任务,由后台按稳定顺序分页生成二级表头工作簿。
|
||||
*
|
||||
* @param dto 页面当前记账扣款筛选条件
|
||||
* @return 包含任务编号和初始进度的统一响应
|
||||
*/
|
||||
@PostMapping("/export-jz-async")
|
||||
@PreAuthorize("@pms.hasPermission('wallet_walletTransaction_export')")
|
||||
public R<AsyncTaskProgressVO> submitJzExport(@RequestBody WalletTransactionDto dto) {
|
||||
return R.ok(walletUserExportService.submitTransactionExport(dto, WalletTransactionExportScene.ACCOUNTING),
|
||||
"记账扣款导出任务已提交");
|
||||
}
|
||||
|
||||
@Operation(summary = "记账扣款统计", description = "记账扣款统计")
|
||||
@@ -378,18 +404,30 @@ public class WalletTransactionController {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 使用自定义二级表头同步导出消费流水,保留旧接口供现有调用方兼容使用。
|
||||
*
|
||||
* @param response 当前 HTTP 文件下载响应
|
||||
* @param dto 页面当前消费流水筛选条件
|
||||
*/
|
||||
@Operation(summary = "消费导出", description = "消费导出")
|
||||
@PostMapping("/export-xf")
|
||||
@PreAuthorize("@pms.hasPermission('wallet_walletTransaction_export')")
|
||||
public void exportXf(HttpServletResponse response, @RequestBody WalletTransactionDto dto) {
|
||||
dto.setBizTypes(List.of(WalletBizEnum.PAYMENT.getCode()));
|
||||
walletTransactionService.prepareIdentityQuery(dto);
|
||||
walletUserExportService.exportTransaction(response, dto, WalletTransactionExportScene.CONSUMPTION);
|
||||
}
|
||||
|
||||
Long count = walletTransactionMapper.count(dto, DataScope.of().func(DataScopeFuncEnum.COUNT));
|
||||
if (count > 5000) throw new RuntimeException("导出数据超出5000条,请缩小查询范围");
|
||||
|
||||
List<WalletTransactionVo> list = walletTransactionService.scopeList(dto, DataScope.of());
|
||||
walletTransactionService.exportByTemplate(response, dto, "file/个人消费明细.xlsx", list);
|
||||
/**
|
||||
* 提交消费流水异步分片导出任务,由后台按稳定顺序分页生成二级表头工作簿。
|
||||
*
|
||||
* @param dto 页面当前消费流水筛选条件
|
||||
* @return 包含任务编号和初始进度的统一响应
|
||||
*/
|
||||
@PostMapping("/export-xf-async")
|
||||
@PreAuthorize("@pms.hasPermission('wallet_walletTransaction_export')")
|
||||
public R<AsyncTaskProgressVO> submitXfExport(@RequestBody WalletTransactionDto dto) {
|
||||
return R.ok(walletUserExportService.submitTransactionExport(dto, WalletTransactionExportScene.CONSUMPTION),
|
||||
"消费记录导出任务已提交");
|
||||
}
|
||||
|
||||
@Operation(summary = "消费统计", description = "消费统计")
|
||||
@@ -400,19 +438,30 @@ public class WalletTransactionController {
|
||||
return R.ok(walletTransactionService.walletTypetotal(dto, dataScope));
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用自定义二级表头同步导出退款流水,保留旧接口供现有调用方兼容使用。
|
||||
*
|
||||
* @param response 当前 HTTP 文件下载响应
|
||||
* @param dto 页面当前退款流水筛选条件
|
||||
*/
|
||||
@Operation(summary = "退款导出", description = "退款导出")
|
||||
@PostMapping("/export-tk")
|
||||
@PreAuthorize("@pms.hasPermission('wallet_walletTransaction_export')")
|
||||
public void exportTk(HttpServletResponse response, @RequestBody WalletTransactionDto dto) {
|
||||
dto.setBizTypes(List.of(WalletBizEnum.REFUND.getCode()));
|
||||
dto.setIsFindOrder(true);
|
||||
walletTransactionService.prepareIdentityQuery(dto);
|
||||
walletUserExportService.exportTransaction(response, dto, WalletTransactionExportScene.REFUND);
|
||||
}
|
||||
|
||||
Long count = walletTransactionMapper.count(dto, DataScope.of().func(DataScopeFuncEnum.COUNT));
|
||||
if (count > 5000) throw new RuntimeException("导出数据超出5000条,请缩小查询范围");
|
||||
|
||||
List<WalletTransactionVo> list = walletTransactionService.scopeList(dto, DataScope.of());
|
||||
walletTransactionService.exportByTemplate(response, dto, "file/个人退款明细.xlsx", list);
|
||||
/**
|
||||
* 提交退款流水异步分片导出任务,由后台按稳定顺序分页生成二级表头工作簿。
|
||||
*
|
||||
* @param dto 页面当前退款流水筛选条件
|
||||
* @return 包含任务编号和初始进度的统一响应
|
||||
*/
|
||||
@PostMapping("/export-tk-async")
|
||||
@PreAuthorize("@pms.hasPermission('wallet_walletTransaction_export')")
|
||||
public R<AsyncTaskProgressVO> submitTkExport(@RequestBody WalletTransactionDto dto) {
|
||||
return R.ok(walletUserExportService.submitTransactionExport(dto, WalletTransactionExportScene.REFUND),
|
||||
"退款记录导出任务已提交");
|
||||
}
|
||||
|
||||
@Operation(summary = "退款统计", description = "退款统计")
|
||||
@@ -424,19 +473,44 @@ public class WalletTransactionController {
|
||||
return R.ok(walletTransactionService.walletTypetotal(dto, dataScope));
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用自定义二级表头同步导出食堂退还流水,保留旧接口供现有调用方兼容使用。
|
||||
*
|
||||
* @param response 当前 HTTP 文件下载响应
|
||||
* @param dto 页面当前食堂退还流水筛选条件
|
||||
*/
|
||||
@Operation(summary = "后台退还各自食堂记录导出", description = "后台退还各自食堂记录导出")
|
||||
@PostMapping("/export-th")
|
||||
@PreAuthorize("@pms.hasPermission('wallet_walletTransaction_export')")
|
||||
public void exportTh(HttpServletResponse response, @RequestBody WalletTransactionDto dto) {
|
||||
dto.setBizTypes(List.of(WalletBizEnum.RETURN_CANTEEN.getCode()));
|
||||
dto.setIsFindOrder(false);
|
||||
walletTransactionService.prepareIdentityQuery(dto);
|
||||
walletUserExportService.exportTransaction(response, dto, WalletTransactionExportScene.RETURN_CANTEEN);
|
||||
}
|
||||
|
||||
Long count = walletTransactionMapper.count(dto, DataScope.of().func(DataScopeFuncEnum.COUNT));
|
||||
if (count > 5000) throw new RuntimeException("导出数据超出5000条,请缩小查询范围");
|
||||
/**
|
||||
* 提交食堂退还流水异步分片导出任务,由后台按稳定顺序分页生成二级表头工作簿。
|
||||
*
|
||||
* @param dto 页面当前食堂退还流水筛选条件
|
||||
* @return 包含任务编号和初始进度的统一响应
|
||||
*/
|
||||
@PostMapping("/export-th-async")
|
||||
@PreAuthorize("@pms.hasPermission('wallet_walletTransaction_export')")
|
||||
public R<AsyncTaskProgressVO> submitThExport(@RequestBody WalletTransactionDto dto) {
|
||||
return R.ok(walletUserExportService.submitTransactionExport(dto, WalletTransactionExportScene.RETURN_CANTEEN),
|
||||
"食堂退还记录导出任务已提交");
|
||||
}
|
||||
|
||||
List<WalletTransactionVo> list = walletTransactionService.scopeList(dto, DataScope.of());
|
||||
walletTransactionService.exportByTemplate(response, dto, "file/退还各自食堂明细.xlsx", list);
|
||||
/**
|
||||
* 提交个人充值统计异步分片导出任务,并保留历史金额及排除部门统计口径。
|
||||
*
|
||||
* @param dto 页面当前充值金额、时间、标签和部门筛选条件;金额单位为元
|
||||
* @return 包含任务编号和初始进度的统一响应
|
||||
*/
|
||||
@PostMapping("/export-cz-total-person-async")
|
||||
@PreAuthorize("@pms.hasPermission('wallet_walletTransaction_export')")
|
||||
public R<AsyncTaskProgressVO> submitCzTotalPersonExport(@RequestBody WalletTransactionDto dto) {
|
||||
return R.ok(walletUserExportService.submitTransactionExport(dto,
|
||||
WalletTransactionExportScene.RECHARGE_PERSON_TOTAL),
|
||||
"个人充值统计导出任务已提交");
|
||||
}
|
||||
|
||||
@Operation(summary = "退还各自食堂统计", description = "退还各自食堂统计")
|
||||
|
||||
+26
-65
@@ -1,31 +1,19 @@
|
||||
package com.bsd.pjcloud.wallet.controller.recharge;
|
||||
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.alibaba.excel.ExcelWriter;
|
||||
import com.alibaba.excel.write.metadata.WriteSheet;
|
||||
import com.alibaba.excel.write.metadata.fill.FillConfig;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.bsd.pjcloud.common.core.exception.ServiceException;
|
||||
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.idempotent.annotation.Idempotent;
|
||||
import com.bsd.pjcloud.common.log.annotation.SysLog;
|
||||
import com.bsd.pjcloud.wallet.api.constant.WalletBizRechargeEnum;
|
||||
import com.bsd.pjcloud.wallet.api.constant.WalletTypeEnum;
|
||||
import com.bsd.pjcloud.wallet.api.dto.recharge.WalletRechargeImportDto;
|
||||
import com.bsd.pjcloud.wallet.api.entity.recharge.WalletRechargeImportEntity;
|
||||
import com.bsd.pjcloud.wallet.api.vo.recharge.WalletRechargeImportVo;
|
||||
import com.bsd.pjcloud.wallet.mapper.recharge.WalletRechargeImportMapper;
|
||||
import com.bsd.pjcloud.wallet.service.WalletRechargeImportService;
|
||||
import com.bsd.pjcloud.wallet.service.WalletUserExportService;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import com.bsd.pjcloud.common.excel.annotation.ResponseExcel;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import org.springdoc.core.annotations.ParameterObject;
|
||||
@@ -33,15 +21,9 @@ import org.springframework.http.HttpHeaders;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 充值导入表
|
||||
*
|
||||
@@ -56,7 +38,9 @@ import java.util.Map;
|
||||
public class WalletRechargeImportController {
|
||||
|
||||
private final WalletRechargeImportService walletRechargeImportService;
|
||||
private final WalletRechargeImportMapper walletRechargeImportMapper;
|
||||
|
||||
/** 钱包用户导出服务,负责充值水单的异步分片和用户身份补齐。 */
|
||||
private final WalletUserExportService walletUserExportService;
|
||||
|
||||
@Operation(summary = "恢复", description = "恢复")
|
||||
@Idempotent(expireTime = 5, info = "正在处理中,请勿重复操作")
|
||||
@@ -112,51 +96,28 @@ public class WalletRechargeImportController {
|
||||
return R.ok(walletRechargeImportService.removeBatchByIds(CollUtil.toList(ids)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用自定义二级表头同步导出充值扣款水单,保留旧接口供现有调用方兼容使用。
|
||||
*
|
||||
* @param response 当前 HTTP 文件下载响应
|
||||
* @param dto 页面当前水单、审批、用户身份及部门筛选条件
|
||||
*/
|
||||
@ResponseExcel
|
||||
@PostMapping("/export")
|
||||
@PreAuthorize("@pms.hasPermission('wallet_walletRechargeImport_export')")
|
||||
public void export(HttpServletResponse response, @RequestBody WalletRechargeImportDto dto) throws IOException {
|
||||
DataScope dataScope = DataScope.of();
|
||||
walletRechargeImportService.prepareIdentityQuery(dto);
|
||||
Long count = walletRechargeImportMapper.count(dto, dataScope);
|
||||
if (count > 5000) throw new RuntimeException("导出数据超出5000条,请缩小查询范围");
|
||||
List<WalletRechargeImportVo> list = walletRechargeImportService.scopeList(dto, dataScope);
|
||||
try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).withTemplate(ResourceUtil.getStream("file/批量充值扣款流水明细.xlsx")).build()) {
|
||||
WriteSheet writeSheet = EasyExcel.writerSheet().build();
|
||||
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
if (ObjectUtil.isNotEmpty(dto.getCreateTimeList())) {
|
||||
map.put("timeSpan", " 到 ");
|
||||
map.put("startTime", LocalDateTimeUtil.format(dto.getCreateTimeList()[0], DatePattern.NORM_DATETIME_PATTERN));
|
||||
map.put("endTime", LocalDateTimeUtil.format(dto.getCreateTimeList()[1], DatePattern.NORM_DATETIME_PATTERN));
|
||||
}
|
||||
if (ObjectUtil.isNotEmpty(list)) {
|
||||
BigDecimal priceStrSum = BigDecimal.ZERO;
|
||||
Long lineNumber = 1L;
|
||||
for (WalletRechargeImportVo v : list) {
|
||||
v.setLineNumber(lineNumber);
|
||||
lineNumber++;
|
||||
if (ObjectUtil.isNotEmpty(v.getPrice())) {
|
||||
if (WalletBizRechargeEnum.reductionCodes().contains(v.getBizType()) && v.getPrice() > 0) {
|
||||
v.setPrice(v.getPrice() * -1);
|
||||
}
|
||||
priceStrSum = priceStrSum.add(BigDecimal.valueOf(v.getPrice()));
|
||||
v.setPriceStr(BigDecimal.valueOf(v.getPrice()).divide(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP).toString());
|
||||
}
|
||||
if (ObjectUtil.isNotEmpty(v.getImportTime())) {
|
||||
v.setImportTimeStr(LocalDateTimeUtil.format(v.getImportTime(), DatePattern.NORM_MONTH_PATTERN));
|
||||
}
|
||||
if (ObjectUtil.isNotEmpty(v.getBizType())) {
|
||||
v.setBizType(WalletBizRechargeEnum.valueOfByType(v.getBizType()).getDesc());
|
||||
}
|
||||
if (ObjectUtil.isNotEmpty(v.getWalletTypeUniqueCode())) {
|
||||
v.setWalletTypeUniqueCode(WalletTypeEnum.valueOfByCode(v.getWalletTypeUniqueCode()).getDesc());
|
||||
}
|
||||
}
|
||||
map.put("priceStrSum", priceStrSum.divide(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP).toString());
|
||||
}
|
||||
excelWriter.fill(map, writeSheet);
|
||||
excelWriter.fill(list, fillConfig, writeSheet);
|
||||
}
|
||||
public void export(HttpServletResponse response, @RequestBody WalletRechargeImportDto dto) {
|
||||
walletUserExportService.exportRechargeImport(response, dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交充值扣款水单异步分片导出任务,由后台补齐用户身份并实时上报生成进度。
|
||||
*
|
||||
* @param dto 页面当前水单、审批、用户身份及部门筛选条件
|
||||
* @return 包含任务编号和初始进度的统一响应
|
||||
*/
|
||||
@PostMapping("/export-async")
|
||||
@PreAuthorize("@pms.hasPermission('wallet_walletRechargeImport_export')")
|
||||
public R<AsyncTaskProgressVO> submitExport(@RequestBody WalletRechargeImportDto dto) {
|
||||
return R.ok(walletUserExportService.submitRechargeImportExport(dto), "充值扣款水单导出任务已提交");
|
||||
}
|
||||
}
|
||||
+8
-1
@@ -25,6 +25,12 @@ import java.util.List;
|
||||
@Mapper
|
||||
public interface WalletManageMapper extends PjcloudBaseMapper<WalletManageEntity> {
|
||||
|
||||
/**
|
||||
* 构建钱包账户通用筛选条件,并以钱包主键作为最终排序条件保证分片导出顺序稳定。
|
||||
*
|
||||
* @param dto 钱包账户、用户、部门和身份预筛选后的查询条件
|
||||
* @return 已应用业务筛选和稳定排序的钱包查询包装器
|
||||
*/
|
||||
private LambdaQueryWrapper<WalletManageEntity> commonQuery(WalletManageDto dto) {
|
||||
LambdaQueryWrapper<WalletManageEntity> wrapper = new LambdaQueryWrapper<WalletManageEntity>()
|
||||
.eq(ObjectUtil.isNotEmpty(dto.getUserId()), WalletManageEntity::getUserId, dto.getUserId())
|
||||
@@ -43,7 +49,8 @@ public interface WalletManageMapper extends PjcloudBaseMapper<WalletManageEntity
|
||||
)
|
||||
.orderByDesc(WalletManageEntity::getCreateTime)
|
||||
.orderByDesc(WalletManageEntity::getUserId)
|
||||
.orderByAsc(WalletManageEntity::getWalletTypeId);
|
||||
.orderByAsc(WalletManageEntity::getWalletTypeId)
|
||||
.orderByDesc(WalletManageEntity::getId);
|
||||
if (ObjectUtil.isNotEmpty(dto.getIsInclude())) {
|
||||
if (dto.getIsInclude()) {
|
||||
wrapper.in(ObjectUtil.isNotEmpty(dto.getDeptIds()), WalletManageEntity::getDeptId, dto.getDeptIds());
|
||||
|
||||
+29
-1
@@ -29,6 +29,12 @@ import java.util.Objects;
|
||||
@Mapper
|
||||
public interface WalletTransactionMapper extends PjcloudBaseMapper<WalletTransactionEntity> {
|
||||
|
||||
/**
|
||||
* 构建钱包流水通用筛选条件;排序末尾使用流水主键保证深分页期间顺序稳定。
|
||||
*
|
||||
* @param dto 钱包流水筛选条件
|
||||
* @return 已应用业务、用户、部门、时间和标签条件的查询包装器
|
||||
*/
|
||||
default LambdaQueryWrapper<WalletTransactionEntity> commonWrapperQuery(WalletTransactionDto dto) {
|
||||
LambdaQueryWrapper<WalletTransactionEntity> wrapper = new LambdaQueryWrapper<WalletTransactionEntity>()
|
||||
.eq(ObjectUtil.isNotEmpty(dto.getWalletId()), WalletTransactionEntity::getWalletId, dto.getWalletId())
|
||||
@@ -44,7 +50,8 @@ public interface WalletTransactionMapper extends PjcloudBaseMapper<WalletTransac
|
||||
ObjectUtil.isNotEmpty(dto.getCreateTimeList()) ? dto.getCreateTimeList()[1] : null
|
||||
)
|
||||
.orderByDesc(WalletTransactionEntity::getCreateTime)
|
||||
.orderByDesc(WalletTransactionEntity::getUserId);
|
||||
.orderByDesc(WalletTransactionEntity::getUserId)
|
||||
.orderByDesc(WalletTransactionEntity::getId);
|
||||
if (ObjectUtil.isNotEmpty(dto.getIsInclude())) {
|
||||
if (dto.getIsInclude()) {
|
||||
wrapper.in(ObjectUtil.isNotEmpty(dto.getDeptIds()), WalletTransactionEntity::getDeptId,
|
||||
@@ -84,6 +91,27 @@ public interface WalletTransactionMapper extends PjcloudBaseMapper<WalletTransac
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按钱包流水通用条件分页查询个人充值统计导出数据,并排除固定非统计部门。
|
||||
*
|
||||
* @param page 分页参数;仅第一页由调用方开启总数查询
|
||||
* @param dto 已冻结用户身份和个人充值统计筛选条件
|
||||
* @param dataScope 当前操作者的数据权限范围
|
||||
* @param excludedDeptIds 个人充值统计固定排除的部门主键
|
||||
* @return 携带流水基础字段的导出分页,记录顺序与列表查询保持一致
|
||||
*/
|
||||
default Page<WalletTransactionVo> scopeRechargePersonExportPage(Page page, WalletTransactionDto dto,
|
||||
DataScope dataScope, List<Long> excludedDeptIds) {
|
||||
LambdaQueryWrapper<WalletTransactionEntity> wrapper = commonWrapperQuery(dto)
|
||||
.eq(ObjectUtil.isNotEmpty(dto.getPrice()), WalletTransactionEntity::getPrice, dto.getPrice())
|
||||
.in(ObjectUtil.isNotEmpty(dto.getPriceList()), WalletTransactionEntity::getPrice, dto.getPriceList())
|
||||
.notIn(excludedDeptIds != null && !excludedDeptIds.isEmpty(),
|
||||
WalletTransactionEntity::getDeptId, excludedDeptIds);
|
||||
Page list = selectPageByScope(page, wrapper, dataScope);
|
||||
list.setRecords(BeanUtils.toBean(list.getRecords(), WalletTransactionVo.class));
|
||||
return list;
|
||||
}
|
||||
|
||||
WalletTypeCountVo walletTypetotal(@Param("dto") WalletTransactionDto dto, @Param("dataScope") DataScope dataScope);
|
||||
|
||||
List<WalletTypeCountVo> walletTypetotalByDept(@Param("dto") WalletTransactionDto dto, @Param("dataScope") DataScope dataScope);
|
||||
|
||||
+8
@@ -16,6 +16,14 @@ import java.util.List;
|
||||
@Mapper
|
||||
public interface WalletRechargeImportMapper extends PjcloudBaseMapper<WalletRechargeImportEntity> {
|
||||
|
||||
/**
|
||||
* 按数据权限分页查询充值扣款水单,XML 使用创建时间和水单主键倒序保证分片顺序稳定。
|
||||
*
|
||||
* @param page 分页参数;异步导出仅在第一页查询总数
|
||||
* @param dto 水单、审批、用户和部门筛选条件
|
||||
* @param dataScope 当前操作者的数据权限范围
|
||||
* @return 已关联充值申请字段的水单分页结果
|
||||
*/
|
||||
Page<WalletRechargeImportVo> scopePageList(Page page, @Param("dto") WalletRechargeImportDto dto,
|
||||
DataScope dataScope);
|
||||
|
||||
|
||||
+23
@@ -82,12 +82,35 @@ public interface WalletManageService extends IService<WalletManageEntity> {
|
||||
|
||||
Page<WalletManageVo> scopePageList(Page page, WalletManageDto dto, DataScope dataScope);
|
||||
|
||||
/**
|
||||
* 分页查询账户余额导出所需的钱包业务资料。
|
||||
*
|
||||
* <p>调用方必须先冻结身份筛选。本方法保留数据权限、部门和钱包类型装配,但不执行普通列表的
|
||||
* UPMS 用户身份回填,避免统一导出服务在同一分片重复查询用户。</p>
|
||||
*
|
||||
* @param page 分页参数;仅第一页执行总数查询
|
||||
* @param dto 已冻结身份的账户余额查询条件
|
||||
* @param dataScope 当前操作者的数据权限范围
|
||||
* @return 不重复查询用户资料的钱包分页
|
||||
*/
|
||||
Page<WalletManageVo> scopeExportPageList(Page page, WalletManageDto dto, DataScope dataScope);
|
||||
|
||||
Page<WalletManageVo> appPageList(PjcloudUser user, Page page, WalletManageDto dto, DataScope dataScope);
|
||||
|
||||
List<WalletManageVo> scopeList(WalletManageDto dto, DataScope dataScope);
|
||||
|
||||
Page<WalletManageVo> scopeQbPage(Page page, WalletManageBalanceDto dto, DataScope dataScope);
|
||||
|
||||
/**
|
||||
* 分页计算月末钱包余额并装配导出所需业务资料。
|
||||
*
|
||||
* @param page 分页参数;仅第一页执行总数查询
|
||||
* @param dto 已冻结身份且包含统计月份的钱包记录查询条件
|
||||
* @param dataScope 当前操作者的数据权限范围
|
||||
* @return 不重复查询用户资料的月度钱包记录分页
|
||||
*/
|
||||
Page<WalletManageVo> scopeQbExportPage(Page page, WalletManageBalanceDto dto, DataScope dataScope);
|
||||
|
||||
WalletQbTotalVo qbTotal(WalletManageBalanceDto dto, DataScope dataScope);
|
||||
|
||||
/**
|
||||
|
||||
+13
@@ -25,6 +25,19 @@ public interface WalletRechargeImportService extends IService<WalletRechargeImpo
|
||||
|
||||
Page<WalletRechargeImportVo> scopePageList(Page page, WalletRechargeImportDto dto, DataScope dataScope);
|
||||
|
||||
/**
|
||||
* 分页查询统一导出所需的充值扣款水单业务资料。
|
||||
*
|
||||
* <p>调用方必须先冻结身份筛选。本方法保留数据权限、审批关联字段和钱包类型装配,但不执行普通
|
||||
* 列表的 UPMS 用户身份回填,完整用户资料由统一导出服务按同一分片一次性查询。</p>
|
||||
*
|
||||
* @param page 分页参数;仅第一页执行总数查询
|
||||
* @param dto 已冻结身份的水单筛选条件
|
||||
* @param dataScope 当前操作者的数据权限范围
|
||||
* @return 不重复查询用户资料的水单分页
|
||||
*/
|
||||
Page<WalletRechargeImportVo> scopeExportPageList(Page page, WalletRechargeImportDto dto, DataScope dataScope);
|
||||
|
||||
List<WalletRechargeImportVo> scopeList(WalletRechargeImportDto dto, DataScope dataScope);
|
||||
|
||||
Boolean recover(Long id);
|
||||
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
package com.bsd.pjcloud.wallet.service;
|
||||
|
||||
import com.bsd.pjcloud.wallet.api.constant.WalletBizEnum;
|
||||
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 钱包流水导出场景定义。
|
||||
*
|
||||
* <p>各场景固定 Redis 命名空间、任务名称、文件名和流水筛选条件;这些编码属于前后端异步进度协议,
|
||||
* 已发布后不得随意修改。</p>
|
||||
*/
|
||||
public enum WalletTransactionExportScene {
|
||||
|
||||
/** 个人充值明细,限定充值类业务和正向收入流水。 */
|
||||
RECHARGE("wallet:user:export:cz", "充值记录导出", "充值记录", List.copyOf(WalletBizEnum.getTopUpCodes()),
|
||||
WalletTransactionDto.TYPE_INCOME, false),
|
||||
|
||||
/** 个人记账扣款明细,限定充值类业务和负向支出流水。 */
|
||||
ACCOUNTING("wallet:user:export:jz", "记账扣款导出", "记账扣款记录", List.copyOf(WalletBizEnum.getTopUpCodes()),
|
||||
WalletTransactionDto.TYPE_EXPENSE, false),
|
||||
|
||||
/** 充值统计页面的个人充值明细,沿用页面金额、时间、标签和部门筛选。 */
|
||||
RECHARGE_PERSON_TOTAL("wallet:user:export:cz-total", "个人充值统计导出", "个人充值统计",
|
||||
List.copyOf(WalletBizEnum.getTopUpCodes()), WalletTransactionDto.TYPE_INCOME, false),
|
||||
|
||||
/** 个人消费明细,限定钱包支付流水并保留关联订单号。 */
|
||||
CONSUMPTION("wallet:user:export:xf", "消费记录导出", "消费记录",
|
||||
List.of(WalletBizEnum.PAYMENT.getCode()), null, true),
|
||||
|
||||
/** 个人退款明细,限定钱包退款流水并保留关联订单号。 */
|
||||
REFUND("wallet:user:export:tk", "退款记录导出", "退款记录",
|
||||
List.of(WalletBizEnum.REFUND.getCode()), null, true),
|
||||
|
||||
/** 各自食堂退还明细,限定食堂退还业务且不解析为订单。 */
|
||||
RETURN_CANTEEN("wallet:user:export:th", "食堂退还记录导出", "食堂退还记录",
|
||||
List.of(WalletBizEnum.RETURN_CANTEEN.getCode()), null, false);
|
||||
|
||||
/** 前端进度轮询使用的固定 Redis Key 业务命名空间。 */
|
||||
private final String redisKey;
|
||||
|
||||
/** 异步进度弹窗展示的任务名称。 */
|
||||
private final String taskName;
|
||||
|
||||
/** 导出文件和工作表使用的业务名称,不包含时间戳和扩展名。 */
|
||||
private final String fileNamePrefix;
|
||||
|
||||
/** 当前场景允许查询的钱包流水业务编码集合。 */
|
||||
private final List<String> bizTypes;
|
||||
|
||||
/** 收支方向编码;为空表示不额外限制金额正负。 */
|
||||
private final Integer transactionType;
|
||||
|
||||
/** 是否将关联业务编号按订单号语义补充到分页展示对象。 */
|
||||
private final boolean findOrder;
|
||||
|
||||
/**
|
||||
* 创建固定钱包流水导出场景。
|
||||
*
|
||||
* @param redisKey 前端进度轮询使用的固定命名空间
|
||||
* @param taskName 异步任务展示名称
|
||||
* @param fileNamePrefix 导出文件和工作表业务名称
|
||||
* @param bizTypes 钱包流水业务编码集合
|
||||
* @param transactionType 收支方向编码,允许为空
|
||||
* @param findOrder 是否解析关联订单号
|
||||
*/
|
||||
WalletTransactionExportScene(String redisKey, String taskName, String fileNamePrefix, List<String> bizTypes,
|
||||
Integer transactionType, boolean findOrder) {
|
||||
this.redisKey = redisKey;
|
||||
this.taskName = taskName;
|
||||
this.fileNamePrefix = fileNamePrefix;
|
||||
this.bizTypes = bizTypes;
|
||||
this.transactionType = transactionType;
|
||||
this.findOrder = findOrder;
|
||||
}
|
||||
|
||||
/** @return 前端进度轮询使用的固定 Redis Key 业务命名空间 */
|
||||
public String getRedisKey() {
|
||||
return redisKey;
|
||||
}
|
||||
|
||||
/** @return 异步进度弹窗展示的任务名称 */
|
||||
public String getTaskName() {
|
||||
return taskName;
|
||||
}
|
||||
|
||||
/** @return 导出文件和工作表使用的业务名称 */
|
||||
public String getFileNamePrefix() {
|
||||
return fileNamePrefix;
|
||||
}
|
||||
|
||||
/** @return 当前场景允许查询的钱包流水业务编码集合 */
|
||||
public List<String> getBizTypes() {
|
||||
return bizTypes;
|
||||
}
|
||||
|
||||
/** @return 收支方向编码;为空表示不限制金额正负 */
|
||||
public Integer getTransactionType() {
|
||||
return transactionType;
|
||||
}
|
||||
|
||||
/** @return 是否按订单语义解析关联业务编号 */
|
||||
public boolean isFindOrder() {
|
||||
return findOrder;
|
||||
}
|
||||
}
|
||||
+25
@@ -26,6 +26,31 @@ public interface WalletTransactionService extends IService<WalletTransactionEnti
|
||||
|
||||
Page<WalletTransactionVo> scopePageList(Page page, WalletTransactionDto dto, DataScope dataScope);
|
||||
|
||||
/**
|
||||
* 分页查询统一导出所需的钱包流水业务资料。
|
||||
*
|
||||
* <p>调用方必须先冻结身份筛选。本方法保留数据权限、部门、钱包和订单号装配,但不调用普通列表的
|
||||
* UPMS 用户回填,完整用户、岗位和身份资料由统一导出服务在同一分片内一次性查询。</p>
|
||||
*
|
||||
* @param page 分页参数;仅第一页执行总数查询
|
||||
* @param dto 已冻结身份和固定业务场景的流水查询条件
|
||||
* @param dataScope 当前操作者的数据权限范围
|
||||
* @return 不重复查询用户资料的流水导出分页
|
||||
*/
|
||||
Page<WalletTransactionVo> scopeExportPageList(Page page, WalletTransactionDto dto, DataScope dataScope);
|
||||
|
||||
/**
|
||||
* 分页查询个人充值统计导出流水,并沿用流水列表的数据权限和钱包资料装配。
|
||||
*
|
||||
* @param page 分页参数;仅第一页执行总数查询
|
||||
* @param dto 已冻结身份、充值金额和部门筛选条件
|
||||
* @param dataScope 当前操作者的数据权限范围
|
||||
* @param excludedDeptIds 统计场景固定排除的部门主键
|
||||
* @return 携带部门、钱包类型和卡号业务资料且不重复查询用户资料的分页结果
|
||||
*/
|
||||
Page<WalletTransactionVo> scopeRechargePersonExportPage(Page page, WalletTransactionDto dto,
|
||||
DataScope dataScope, List<Long> excludedDeptIds);
|
||||
|
||||
/**
|
||||
* 分页查询登录用户本人或其有效被监护人的 EAT 饭卡流水。
|
||||
*
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package com.bsd.pjcloud.wallet.service;
|
||||
|
||||
import com.bsd.pjcloud.common.asynctask.vo.AsyncTaskProgressVO;
|
||||
import com.bsd.pjcloud.wallet.api.dto.WalletManageBalanceDto;
|
||||
import com.bsd.pjcloud.wallet.api.dto.WalletManageDto;
|
||||
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDto;
|
||||
import com.bsd.pjcloud.wallet.api.dto.recharge.WalletRechargeImportDto;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 钱包用户数据统一导出服务。
|
||||
*
|
||||
* <p>负责冻结页面筛选与数据权限、按页读取钱包业务数据、批量补齐 UPMS 用户及身份资料、流式写入
|
||||
* EasyExcel 二级表头文件,并通过通用异步任务协议实时上报进度。</p>
|
||||
*/
|
||||
public interface WalletUserExportService {
|
||||
|
||||
/**
|
||||
* 提交钱包流水异步分片导出任务。
|
||||
*
|
||||
* @param dto 页面当前流水筛选条件
|
||||
* @param scene 固定流水导出场景
|
||||
* @return 供前端轮询的初始任务进度
|
||||
*/
|
||||
AsyncTaskProgressVO submitTransactionExport(WalletTransactionDto dto, WalletTransactionExportScene scene);
|
||||
|
||||
/**
|
||||
* 使用自定义二级表头执行兼容旧调用方的钱包流水同步导出。
|
||||
*
|
||||
* @param response 当前 HTTP 下载响应
|
||||
* @param dto 页面当前流水筛选条件
|
||||
* @param scene 固定流水导出场景
|
||||
*/
|
||||
void exportTransaction(HttpServletResponse response, WalletTransactionDto dto,
|
||||
WalletTransactionExportScene scene);
|
||||
|
||||
/**
|
||||
* 提交账户当前余额异步分片导出任务。
|
||||
*
|
||||
* @param dto 账户余额筛选条件
|
||||
* @return 供前端轮询的初始任务进度
|
||||
*/
|
||||
AsyncTaskProgressVO submitWalletBalanceExport(WalletManageDto dto);
|
||||
|
||||
/**
|
||||
* 提交指定月份钱包余额异步分片导出任务。
|
||||
*
|
||||
* @param dto 月度钱包余额筛选条件
|
||||
* @return 供前端轮询的初始任务进度
|
||||
*/
|
||||
AsyncTaskProgressVO submitWalletRecordExport(WalletManageBalanceDto dto);
|
||||
|
||||
/**
|
||||
* 提交批量充值扣款水单异步分片导出任务。
|
||||
*
|
||||
* @param dto 充值水单筛选条件
|
||||
* @return 供前端轮询的初始任务进度
|
||||
*/
|
||||
AsyncTaskProgressVO submitRechargeImportExport(WalletRechargeImportDto dto);
|
||||
|
||||
/**
|
||||
* 使用自定义二级表头执行兼容旧调用方的充值水单同步导出。
|
||||
*
|
||||
* @param response 当前 HTTP 下载响应
|
||||
* @param dto 充值水单筛选条件
|
||||
*/
|
||||
void exportRechargeImport(HttpServletResponse response, WalletRechargeImportDto dto);
|
||||
}
|
||||
+68
-4
@@ -269,6 +269,21 @@ public class WalletManageServiceImpl extends ServiceImpl<WalletManageMapper, Wal
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询账户余额导出业务资料,并跳过普通列表的 UPMS 用户身份回填。
|
||||
*
|
||||
* @param page 分页参数;仅第一页执行总数查询
|
||||
* @param dto 已冻结身份的账户余额查询条件
|
||||
* @param dataScope 当前操作者的数据权限范围
|
||||
* @return 已装配部门和钱包类型的钱包分页
|
||||
*/
|
||||
@Override
|
||||
public Page<WalletManageVo> scopeExportPageList(Page page, WalletManageDto dto, DataScope dataScope) {
|
||||
Page<WalletManageVo> list = baseMapper.scopePage(page, dto, dataScope);
|
||||
disposeExportList(list.getRecords());
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<WalletManageVo> appPageList(PjcloudUser user, Page page, WalletManageDto dto, DataScope dataScope) {
|
||||
Page<WalletManageVo> list = baseMapper.scopePage(page, dto, dataScope);
|
||||
@@ -332,10 +347,38 @@ public class WalletManageServiceImpl extends ServiceImpl<WalletManageMapper, Wal
|
||||
|
||||
@Override
|
||||
public Page<WalletManageVo> scopeQbPage(Page page, WalletManageBalanceDto dto, DataScope dataScope) {
|
||||
return queryWalletRecordPage(page, dto, dataScope, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页计算月末钱包余额导出数据,并把完整用户资料留给统一导出服务装配。
|
||||
*
|
||||
* @param page 分页参数;仅第一页执行总数查询
|
||||
* @param dto 已冻结身份且包含统计月份的钱包记录查询条件
|
||||
* @param dataScope 当前操作者的数据权限范围
|
||||
* @return 已计算月末余额并装配部门、钱包类型的分页
|
||||
*/
|
||||
@Override
|
||||
public Page<WalletManageVo> scopeQbExportPage(Page page, WalletManageBalanceDto dto, DataScope dataScope) {
|
||||
return queryWalletRecordPage(page, dto, dataScope, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行普通列表和统一导出共用的月末钱包余额分页计算。
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param dto 钱包记录月份及用户、部门、钱包类型筛选条件
|
||||
* @param dataScope 当前操作者的数据权限范围
|
||||
* @param fillUserInfo 是否按普通列表协议回填 UPMS 用户和身份资料
|
||||
* @return 已计算指定月份末余额的钱包分页
|
||||
*/
|
||||
private Page<WalletManageVo> queryWalletRecordPage(Page page, WalletManageBalanceDto dto,
|
||||
DataScope dataScope, boolean fillUserInfo) {
|
||||
WalletManageDto walletDto = BeanUtils.toBean(dto, WalletManageDto.class);
|
||||
prepareIdentityQuery(walletDto);
|
||||
Page<WalletManageVo> walletPage = baseMapper.scopePage(page, walletDto, dataScope);
|
||||
if (walletPage.getTotal() == 0) return walletPage;
|
||||
// 导出第二批起会关闭总数查询,此时 total 固定为零,只能按当前批记录判断是否为空。
|
||||
if (ObjectUtil.isEmpty(walletPage.getRecords())) return walletPage;
|
||||
walletPage.getRecords().forEach(v -> {
|
||||
v.setBalance(0);
|
||||
v.setTotalRecharge(0);
|
||||
@@ -363,7 +406,11 @@ public class WalletManageServiceImpl extends ServiceImpl<WalletManageMapper, Wal
|
||||
});
|
||||
}
|
||||
}
|
||||
disposeList(walletPage.getRecords());
|
||||
if (fillUserInfo) {
|
||||
disposeList(walletPage.getRecords());
|
||||
} else {
|
||||
disposeExportList(walletPage.getRecords());
|
||||
}
|
||||
return walletPage;
|
||||
}
|
||||
|
||||
@@ -581,10 +628,27 @@ public class WalletManageServiceImpl extends ServiceImpl<WalletManageMapper, Wal
|
||||
return mchTakeOutSettingMapper.selectById(dto.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 装配普通钱包列表的用户、身份及业务展示资料。
|
||||
*
|
||||
* @param list 当前钱包分页记录
|
||||
*/
|
||||
private void disposeList(List<WalletManageVo> list) {
|
||||
if (ObjectUtil.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
// 普通列表保持既有用户资料契约,导出分页由专用导出服务获取岗位等完整资料。
|
||||
userIdentitySupport.fillUserInfo(list, true);
|
||||
disposeExportList(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 装配账户余额与月度钱包导出共用的部门和钱包类型资料。
|
||||
*
|
||||
* @param list 当前钱包分页记录;为空时不执行远程或数据库查询
|
||||
*/
|
||||
private void disposeExportList(List<WalletManageVo> list) {
|
||||
if (ObjectUtil.isNotEmpty(list)) {
|
||||
// 用户及身份:必须通过带数据权限的 UPMS 基础用户分页接口批量回填。
|
||||
userIdentitySupport.fillUserInfo(list, true);
|
||||
// 部门
|
||||
List<Long> deptIds = list.stream().map(WalletManageVo::getDeptId).distinct().toList();
|
||||
R<Map<Long, SysDept>> deptList = deptService.getDeptByDeptIds(deptIds);
|
||||
|
||||
+55
-2
@@ -885,6 +885,40 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询统一导出所需的流水业务资料,并跳过普通列表的 UPMS 用户回填。
|
||||
*
|
||||
* @param page 分页参数;仅第一页执行总数查询
|
||||
* @param dto 已冻结身份和业务场景的流水查询条件
|
||||
* @param dataScope 当前操作者的数据权限范围
|
||||
* @return 已装配部门、钱包、钱包类型及订单号的流水分页
|
||||
*/
|
||||
@Override
|
||||
public Page<WalletTransactionVo> scopeExportPageList(Page page, WalletTransactionDto dto,
|
||||
DataScope dataScope) {
|
||||
Page<WalletTransactionVo> list = baseMapper.scopePageList(page, dto, dataScope);
|
||||
disposeExportList(list.getRecords(), dto);
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询个人充值统计导出流水,并复用导出分页的部门、钱包和钱包类型装配逻辑。
|
||||
*
|
||||
* @param page 分页参数;仅第一页执行总数查询
|
||||
* @param dto 已冻结身份和充值统计筛选条件
|
||||
* @param dataScope 当前操作者的数据权限范围
|
||||
* @param excludedDeptIds 统计场景固定排除的部门主键
|
||||
* @return 不重复查询 UPMS 用户资料的个人充值统计导出分页
|
||||
*/
|
||||
@Override
|
||||
public Page<WalletTransactionVo> scopeRechargePersonExportPage(Page page, WalletTransactionDto dto,
|
||||
DataScope dataScope, List<Long> excludedDeptIds) {
|
||||
Page<WalletTransactionVo> list = baseMapper.scopeRechargePersonExportPage(page, dto, dataScope,
|
||||
excludedDeptIds);
|
||||
disposeExportList(list.getRecords(), dto);
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询本人或有效被监护人的 EAT 饭卡流水。目标用户与钱包均由服务端重新解析,
|
||||
* 避免客户端通过篡改用户 ID、用户集合或钱包 ID 越权读取其他账户流水。
|
||||
@@ -1196,10 +1230,29 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 装配普通钱包流水列表的用户、身份及业务展示资料。
|
||||
*
|
||||
* @param list 当前列表分页记录
|
||||
* @param dto 当前流水查询条件,用于判断是否解析订单号
|
||||
*/
|
||||
private void disposeList(List<WalletTransactionVo> list, WalletTransactionDto dto) {
|
||||
if (ObjectUtil.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
// 普通列表仍由统一身份支持器回填;导出分片改由专用导出服务一次性获取更完整的用户资料。
|
||||
userIdentitySupport.fillUserInfo(list, true);
|
||||
disposeExportList(list, dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 装配钱包流水导出与普通列表共用的部门、钱包、钱包类型及订单号资料。
|
||||
*
|
||||
* @param list 当前分页记录;为空时不执行远程或数据库查询
|
||||
* @param dto 当前流水查询条件,用于判断是否解析订单号
|
||||
*/
|
||||
private void disposeExportList(List<WalletTransactionVo> list, WalletTransactionDto dto) {
|
||||
if (ObjectUtil.isNotEmpty(list)) {
|
||||
// 用户及身份:必须通过带数据权限的 UPMS 基础用户分页接口批量回填。
|
||||
userIdentitySupport.fillUserInfo(list, true);
|
||||
// 部门
|
||||
List<Long> deptIds = list.stream().map(WalletTransactionVo::getDeptId).distinct().toList();
|
||||
R<Map<Long, SysDept>> deptList = deptService.getDeptByDeptIds(deptIds);
|
||||
|
||||
+1249
File diff suppressed because it is too large.
Load diff
+36
-2
@@ -88,6 +88,22 @@ public class WalletRechargeImportServiceImpl extends ServiceImpl<WalletRechargeI
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询水单导出业务资料,并跳过普通列表的 UPMS 用户身份回填。
|
||||
*
|
||||
* @param page 分页参数;仅第一页执行总数查询
|
||||
* @param dto 已冻结身份的水单筛选条件
|
||||
* @param dataScope 当前操作者的数据权限范围
|
||||
* @return 已装配审批关联字段和钱包类型的水单分页
|
||||
*/
|
||||
@Override
|
||||
public Page<WalletRechargeImportVo> scopeExportPageList(Page page, WalletRechargeImportDto dto,
|
||||
DataScope dataScope) {
|
||||
Page<WalletRechargeImportVo> list = baseMapper.scopePageList(page, dto, dataScope);
|
||||
disposeExportList(list.getRecords());
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<WalletRechargeImportVo> scopeList(WalletRechargeImportDto dto, DataScope dataScope) {
|
||||
prepareIdentityQuery(dto);
|
||||
@@ -118,10 +134,28 @@ public class WalletRechargeImportServiceImpl extends ServiceImpl<WalletRechargeI
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 装配普通充值扣款水单列表的用户、身份和钱包类型资料。
|
||||
*
|
||||
* @param list 当前水单分页记录
|
||||
* @param dto 当前水单查询条件,保留既有私有方法调用契约
|
||||
*/
|
||||
private void disposeList(List<WalletRechargeImportVo> list, WalletRechargeImportDto dto) {
|
||||
if (ObjectUtil.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
// 普通列表保持既有用户资料契约,导出分页由专用导出服务获取岗位等完整资料。
|
||||
userIdentitySupport.fillUserInfo(list, true);
|
||||
disposeExportList(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 装配水单列表和统一导出共用的钱包类型资料。
|
||||
*
|
||||
* @param list 当前水单分页记录;为空时不查询钱包类型
|
||||
*/
|
||||
private void disposeExportList(List<WalletRechargeImportVo> list) {
|
||||
if (ObjectUtil.isNotEmpty(list)) {
|
||||
// 用户及身份:必须通过带数据权限的 UPMS 基础用户分页接口批量回填。
|
||||
userIdentitySupport.fillUserInfo(list, true);
|
||||
// 饭卡类型
|
||||
List<Long> walletTypeId = list.stream().map(WalletRechargeImportVo::getWalletTypeId).distinct().toList();
|
||||
if (ObjectUtil.isNotEmpty(walletTypeId)) {
|
||||
|
||||
+1
-1
@@ -110,7 +110,7 @@
|
||||
and p.process_time between #{dto.processTimeList[0]} and #{dto.processTimeList[1]}
|
||||
</if>
|
||||
</where>
|
||||
order by a.create_time desc
|
||||
order by a.create_time desc, a.id desc
|
||||
</sql>
|
||||
|
||||
<select id="scopePageList" resultType="com.bsd.pjcloud.wallet.api.vo.recharge.WalletRechargeImportVo">
|
||||
|
||||
Reference in new issue
Block a user