Merge branch 'master' of https://gitea.zianxin.com/zax/biz-api-cloud-zax
This commit is contained in:
commit
074340e5c6
22 files changed
+1071
-72
No files matched your search
@@ -0,0 +1,81 @@
|
||||
-- 钱包流水页设备类型筛选(/wallet/wallet-transaction):wallet_transaction 固化设备类型快照列。
|
||||
-- 幂等迁移脚本:可反复执行。列与索引均先查 information_schema,已存在则跳过(执行 SELECT 1 占位);
|
||||
-- 回填带 hard_type IS NULL 条件,重跑只处理增量。
|
||||
-- 背景:设备类型筛选原走「wallet Feign 中转订单 id 集合」方案,大类型(咖啡机等)订单量大,
|
||||
-- 触发上限拦截导致筛选不可用;改为流水表固化 hard_type 快照列(与 hard_order.sys_type 同值域),
|
||||
-- 筛选变为普通索引列过滤,新流水由订单支付/退款调用链路实时携带(见 HardOrderServiceImpl 两个
|
||||
-- WalletBalanceTransactionDto 构造点 + WalletTransactionServiceImpl.save 的 fillHardTypeFromContext)。
|
||||
-- 注意:
|
||||
-- 1) biz_type='5' 退款流水 biz_id 多源:订单售后退款主路径=售后单主键(hard_after_sale.id);
|
||||
-- 补偿退款=订单号;充值/套餐退款=售后单主键或 refund_ 前缀退款单号(与设备无关,hard_type
|
||||
-- 保持为空是正确语义)。因此退款回填分两段(4a 补偿路径按支付流水、4b 售后路径经
|
||||
-- hard_after_sale.order_id 关联原订单支付流水),充值售后 order_id 无效天然跳过。
|
||||
-- 2) 回填 JOIN 跨 schema(pjcloudx_wallet × pjcloudx_hardware,同实例),需用同实例账号执行。
|
||||
-- 3) 严格 SQL 模式下 CAST 非数字 biz_id 会以 1292 报错中断,REGEXP 前置过滤保证只对纯数字主键 CAST。
|
||||
|
||||
-- 【可选诊断】确认当前列与索引状态。
|
||||
SELECT COLUMN_NAME FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = 'pjcloudx_wallet' AND TABLE_NAME = 'wallet_transaction' AND COLUMN_NAME = 'hard_type';
|
||||
|
||||
-- 1) 固化列:缺失才创建,与 hard_order.sys_type 同值域(HardTypeEnum 编码,'-1' 为无设备)。
|
||||
SET @ddl = (SELECT IF(COUNT(*) = 0,
|
||||
'ALTER TABLE `pjcloudx_wallet`.`wallet_transaction` ADD COLUMN `hard_type` varchar(10) DEFAULT NULL COMMENT ''订单设备类型快照编码(HardTypeEnum.type),仅设备订单支付/退款流水有值'' AFTER `biz_id`',
|
||||
'SELECT 1')
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = 'pjcloudx_wallet' AND TABLE_NAME = 'wallet_transaction' AND COLUMN_NAME = 'hard_type');
|
||||
PREPARE stmt FROM @ddl;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 2) 筛选索引:缺失才创建(租户+删除标记前缀与既有 idx_wallet_tx_* 风格一致)。
|
||||
-- 设备类型筛选 = tenant+del_flag+hard_type IN(...),其余条件(用户/时间)回表过滤,命中分区远小于全表。
|
||||
SET @ddl = (SELECT IF(COUNT(*) = 0,
|
||||
'ALTER TABLE `pjcloudx_wallet`.`wallet_transaction` ADD KEY `idx_wallet_tx_tenant_del_hard_type` (`tenant_id`, `del_flag`, `hard_type`)',
|
||||
'SELECT 1')
|
||||
FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = 'pjcloudx_wallet' AND TABLE_NAME = 'wallet_transaction' AND INDEX_NAME = 'idx_wallet_tx_tenant_del_hard_type');
|
||||
PREPARE stmt FROM @ddl;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 3) 支付流水回填:biz_id 为纯数字订单号才 JOIN 订单表(REGEXP 防严格模式下 CAST 报错,
|
||||
-- 也排除个别非订单号形态的杂音)。CAST 后按订单主键点查。
|
||||
-- 大事务限制提示:若实例限制单事务行数,可追加 AND w.create_time >= '<分段日期>' 分批执行,脚本幂等可重跑。
|
||||
UPDATE pjcloudx_wallet.wallet_transaction w
|
||||
JOIN pjcloudx_hardware.hard_order o ON o.id = CAST(w.biz_id AS UNSIGNED)
|
||||
SET w.hard_type = o.sys_type
|
||||
WHERE w.biz_type = '4' AND w.hard_type IS NULL
|
||||
AND w.biz_id REGEXP '^[0-9]+$';
|
||||
|
||||
-- 4a) 退款流水回填·补偿路径:biz_id 与支付流水一致(订单号形态,compensatePayment 补偿退款)。
|
||||
UPDATE pjcloudx_wallet.wallet_transaction w
|
||||
JOIN pjcloudx_wallet.wallet_transaction pay
|
||||
ON pay.wallet_id = w.wallet_id
|
||||
AND pay.biz_id = w.biz_id
|
||||
AND pay.biz_type = '4'
|
||||
AND pay.del_flag = '0'
|
||||
AND pay.hard_type IS NOT NULL
|
||||
SET w.hard_type = pay.hard_type
|
||||
WHERE w.biz_type = '5' AND w.hard_type IS NULL AND w.del_flag = '0';
|
||||
|
||||
-- 4b) 退款流水回填·售后主路径:订单售后退款 biz_id 存售后单主键(hard_after_sale.id,
|
||||
-- 经 WalletRechargeServiceImpl.refund 落库),需经售后单 order_id 关联原订单支付流水;
|
||||
-- 充值售后 order_id 无效不命中,hard_type 保持为空是正确语义。
|
||||
UPDATE pjcloudx_wallet.wallet_transaction w
|
||||
JOIN pjcloudx_hardware.hard_after_sale s ON s.id = CAST(w.biz_id AS UNSIGNED)
|
||||
JOIN pjcloudx_wallet.wallet_transaction pay
|
||||
ON pay.wallet_id = w.wallet_id
|
||||
AND pay.biz_id = CAST(s.order_id AS CHAR)
|
||||
AND pay.biz_type = '4'
|
||||
AND pay.del_flag = '0'
|
||||
AND pay.hard_type IS NOT NULL
|
||||
SET w.hard_type = pay.hard_type
|
||||
WHERE w.biz_type = '5' AND w.hard_type IS NULL AND w.del_flag = '0'
|
||||
AND w.biz_id REGEXP '^[0-9]+$';
|
||||
|
||||
-- 【可选诊断】回填结果分布:设备类型 × 收支业务类型行数。
|
||||
SELECT w.biz_type, w.hard_type, COUNT(*) AS cnt
|
||||
FROM pjcloudx_wallet.wallet_transaction w
|
||||
WHERE w.hard_type IS NOT NULL
|
||||
GROUP BY w.biz_type, w.hard_type
|
||||
ORDER BY w.biz_type, w.hard_type;
|
||||
+12
@@ -24,6 +24,18 @@ public interface RemoteHardOrderService {
|
||||
@GetMapping("/hard-order/get-by-ids")
|
||||
R<List<HardOrderEntity>> getByIds(@RequestParam List<Long> ids);
|
||||
|
||||
/**
|
||||
* 按售后单主键批量查询售后单,供钱包退款流水(biz_id 为售后单主键)装配消费场景:
|
||||
* wallet 侧经售后单 orderId 再反查原订单。
|
||||
*
|
||||
* @param ids 售后单主键集合
|
||||
* @return 售后单集合;不存在的主键不返回
|
||||
*/
|
||||
@NoToken
|
||||
@GetMapping("/hard-after-sale/inner/get-by-ids")
|
||||
R<List<com.bsd.pjcloud.hard.api.entity.order.HardAfterSaleEntity>> listAfterSalesByIds(
|
||||
@RequestParam List<Long> ids);
|
||||
|
||||
@PostMapping("/hard-order/page")
|
||||
R<Page<List<HardOrderEntity>>> getPage(@RequestParam Page page, @RequestBody HardOrderPreferencesDto hardOrderPreferencesDto);
|
||||
|
||||
|
||||
+23
@@ -29,6 +29,8 @@ import com.bsd.pjcloud.hard.service.HardBatchRefundService;
|
||||
import com.bsd.pjcloud.wallet.api.dto.MchManageDto;
|
||||
import com.bsd.pjcloud.wallet.api.feign.RemoteMchService;
|
||||
import com.bsd.pjcloud.wallet.api.vo.MchManageVo;
|
||||
import com.bsd.pjcloud.hard.api.entity.order.HardAfterSaleEntity;
|
||||
import com.bsd.pjcloud.hard.mapper.order.HardAfterSaleMapper;
|
||||
import com.github.yulichang.query.MPJLambdaQueryWrapper;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import com.bsd.pjcloud.common.excel.annotation.ResponseExcel;
|
||||
@@ -55,6 +57,9 @@ public class HardAfterSaleController {
|
||||
|
||||
private final HardAfterSaleService hardAfterSaleService;
|
||||
|
||||
/** 售后单 Mapper:钱包退款流水 biz_id 存售后单主键,供跨服务按主键批量反查售后单。 */
|
||||
private final HardAfterSaleMapper hardAfterSaleMapper;
|
||||
|
||||
/** 批量退款服务,负责权限预览和最多 1000 笔订单的异步逐笔退款。 */
|
||||
private final HardBatchRefundService hardBatchRefundService;
|
||||
|
||||
@@ -73,6 +78,24 @@ public class HardAfterSaleController {
|
||||
return R.ok(hardAfterSaleService.refreshOrderRefundState(orderId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 钱包退款流水的 biz_id 存售后单主键(订单售后退款经 WalletRechargeServiceImpl.refund 落库),
|
||||
* 消费场景装配按售后单主键批量反查售后单再经 orderId 取原订单;
|
||||
* Inner 调用无用户上下文,按主键批量查询本身就是精确范围,走普通条件查询。
|
||||
*
|
||||
* @param ids 售后单主键集合
|
||||
* @return 售后单集合;不存在的主键不返回
|
||||
*/
|
||||
@Inner
|
||||
@GetMapping("/inner/get-by-ids")
|
||||
@Operation(summary = "按主键批量查询售后单", description = "供钱包退款流水(biz_id 为售后单主键)装配消费场景")
|
||||
public R<List<HardAfterSaleEntity>> getByIds(@RequestParam List<Long> ids) {
|
||||
if (ObjectUtil.isEmpty(ids)) {
|
||||
return R.ok(List.of());
|
||||
}
|
||||
return R.ok(hardAfterSaleMapper.selectBatchIds(ids));
|
||||
}
|
||||
|
||||
/* ************************************* start app start ************************************* */
|
||||
@ApiCryptoAes
|
||||
@PostMapping(value = "/app/create")
|
||||
|
||||
+7
-3
@@ -75,6 +75,7 @@ import org.springframework.format.annotation.DateTimeFormat;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 订单表
|
||||
@@ -108,9 +109,12 @@ public class HardOrderController {
|
||||
@Operation(summary = "查询订单列表", description = "查询订单列表")
|
||||
@GetMapping("/get-by-ids")
|
||||
public R<List<HardOrderEntity>> getByIds(@RequestParam List<Long> ids) {
|
||||
HardOrderDto dto = new HardOrderDto();
|
||||
dto.setOrderIds(ids);
|
||||
return R.ok(hardOrderMapper.list(dto));
|
||||
// Inner 调用没有后台用户上下文,走 selectListByScope 会被数据权限拦截器按匿名角色解析炸掉;
|
||||
// 按主键集合批量查询本身就是精确范围,直接走普通条件查询(租户与逻辑删除拦截器照常生效)
|
||||
if (ObjectUtil.isEmpty(ids)) {
|
||||
return R.ok(List.of());
|
||||
}
|
||||
return R.ok(hardOrderMapper.selectBatchIds(ids));
|
||||
}
|
||||
|
||||
@Inner
|
||||
|
||||
+3
-1
@@ -682,7 +682,9 @@ public class HardAfterSaleServiceImpl extends ServiceImpl<HardAfterSaleMapper, H
|
||||
.orderId(order.getId()).afterSaleId(afterSale.getId()).refundCode(refundCode).reason("订单退款")
|
||||
.price(refundPrice).autoRefundConfigId(autoRefundConfigId).autoRefundTaskType(autoRefundTaskType)
|
||||
// 售后退款的系统备注("订单退款退回X元"),落库到钱包流水 sys_remark
|
||||
.sysRemark(HardOrderSysRemarkHelper.renderAfterSaleRefund(refundPrice)).build();
|
||||
.sysRemark(HardOrderSysRemarkHelper.renderAfterSaleRefund(refundPrice))
|
||||
// 设备类型快照:随退款流水固化为 hard_type,供钱包流水按设备类型筛选
|
||||
.sysType(order.getSysType()).build();
|
||||
R<WalletRechargeVo> walletRefundResult = walletRechargeService.refund(hardRefundDto);
|
||||
if (walletRefundResult == null || !walletRefundResult.isOk() || ObjectUtil.isEmpty(walletRefundResult.getData())) {
|
||||
throw new I18nBizException(ErrorCodes.AFTER_SALE_WALLET_REFUND_FAIL);
|
||||
|
||||
+14
@@ -48,6 +48,7 @@ import com.bsd.pjcloud.common.core.util.BeanUtils;
|
||||
import com.bsd.pjcloud.common.core.util.CollectionUtils;
|
||||
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.security.service.PjcloudUser;
|
||||
import com.bsd.pjcloud.common.security.util.SecurityUtils;
|
||||
import com.bsd.pjcloud.hard.api.constant.HardProductNatureEnum;
|
||||
@@ -2750,6 +2751,8 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
transaction.setBizId(String.valueOf(order.getId()));
|
||||
transaction.setBizType(WalletBizEnum.PAYMENT);
|
||||
transaction.setForce(false);
|
||||
// 设备类型快照:随支付/补偿流水固化为 hard_type,供钱包流水页按设备类型筛选
|
||||
transaction.setSysType(order.getSysType());
|
||||
// 钱包底层流水不保存商户,订单调用必须显式传实际商户快照供同事务Outbox使用。
|
||||
transaction.setSourceMchId(order.getMerchantId());
|
||||
transaction.setSourceScene("ORDER");
|
||||
@@ -4051,6 +4054,8 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
reduceBuild.setBizId(String.valueOf(order.getId()));
|
||||
reduceBuild.setBizType(WalletBizEnum.PAYMENT);
|
||||
reduceBuild.setForce(true);
|
||||
// 设备类型快照:随支付流水固化为 hard_type,供钱包流水页按设备类型筛选
|
||||
reduceBuild.setSysType(order.getSysType());
|
||||
// 快速支付同样使用订单实际商户,禁止退化为钱包所有人的默认食堂。
|
||||
reduceBuild.setSourceMchId(order.getMerchantId());
|
||||
reduceBuild.setSourceScene("ORDER");
|
||||
@@ -4953,6 +4958,15 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
car -> Math.multiplyExact(car.getProductCount(), factor),
|
||||
Math::addExact,
|
||||
LinkedHashMap::new));
|
||||
// 部分退款时后台会把未选中的商品以数量 0 回传,售后快照原样保存;
|
||||
// 远程幂等库存协议拒绝 incrCount=0 的行(报“库存业务参数错误”),发送前统一剔除零增量。
|
||||
stockChanges.values().removeIf(delta -> delta == 0);
|
||||
if (stockChanges.isEmpty()) {
|
||||
// 纯称重等份数为 0 的退款没有份数库存可变更,跳过远程调用,避免被远程参数校验误拒。
|
||||
log.info("|{}|库存变更增量全为0,跳过远程库存调用,orderId={} isReduce={} afterSaleId={}",
|
||||
TAG, order.getId(), isReduce, afterSaleId);
|
||||
return;
|
||||
}
|
||||
|
||||
HardParentCarEntity parentCar = hardCarService.getParentCarById(order.getParentCarId());
|
||||
// 带菜谱标记的购物车(App 预约每日菜谱)整单走分池;设备购物车(createCard 不写 cartScene)
|
||||
|
||||
+1
-2
@@ -94,8 +94,7 @@ public class HardXfjServiceImpl implements HardXfjService {
|
||||
* 保持未配置商户的原有查询行为。
|
||||
*/
|
||||
private static String resolveQueryCardNumber(String cardNo, Long merchantId) {
|
||||
if (ObjectUtil.isEmpty(merchantId) || !DECIMAL_UID_MERCHANT_IDS.contains(merchantId)
|
||||
|| ObjectUtil.isEmpty(cardNo) || !cardNo.matches("\\d+")) {
|
||||
if (ObjectUtil.isEmpty(merchantId) || !DECIMAL_UID_MERCHANT_IDS.contains(merchantId) || ObjectUtil.isEmpty(cardNo) || !cardNo.matches("\\d+")) {
|
||||
return cardNo;
|
||||
}
|
||||
long value = Long.parseLong(cardNo);
|
||||
|
||||
+7
-2
@@ -482,11 +482,16 @@ public class SysUserController {
|
||||
return R.ok(userService.getExportUsersPage(page, userDTO, DataScope.of()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询用户下拉选项
|
||||
*
|
||||
* <p>供管理端、移动端用户选择器远程搜索使用,只返回用户基础字段,
|
||||
* 不回填角色、岗位、身份、人脸和审批信息,也避免下拉场景触发远程流程服务调用。</p>
|
||||
*/
|
||||
@ApiCryptoAes
|
||||
@PostMapping("/select")
|
||||
public R userPage(@ParameterObject Page page, @RequestBody UserDTO userDTO) {
|
||||
userDTO.setIsFindCheckUser(true);
|
||||
return R.ok(userService.getUsersWithRolePage(page, userDTO, null));
|
||||
return R.ok(userService.getSelectUsersPage(page, userDTO));
|
||||
}
|
||||
|
||||
|
||||
|
||||
+12
@@ -68,6 +68,18 @@ public interface SysUserService extends IService<SysUser> {
|
||||
*/
|
||||
IPage<UserVO> getBasicUsersPage(Page page, UserDTO userDTO, DataScope dataScope);
|
||||
|
||||
/**
|
||||
* 分页查询用户下拉选项,仅返回用户、账号、手机号等基础字段。
|
||||
*
|
||||
* <p>下拉组件不展示角色、身份、人脸和审批信息,也不使用分页总条数,
|
||||
* 因此该方法只执行一次索引分页查询,不回填任何扩展资料。</p>
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param userDTO 姓名、手机号、账号关键词及指定用户筛选条件
|
||||
* @return 仅含基础字段的用户分页结果,total 固定为 0
|
||||
*/
|
||||
IPage<UserVO> getSelectUsersPage(Page page, UserDTO userDTO);
|
||||
|
||||
/**
|
||||
* 分页查询导出所需的用户主资料、角色岗位和身份资料,不加载人脸及审批流程。
|
||||
*
|
||||
|
||||
+18
@@ -547,6 +547,24 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询用户下拉选项。
|
||||
*
|
||||
* <p>下拉组件只消费用户 ID、姓名、账号和手机号,因此复用轻量用户分页 SQL,
|
||||
* 不回填角色、岗位、身份、人脸和审批标记;原先的八次串行数据库往返
|
||||
* (含分页 COUNT 全量扫描当前租户用户索引)和一次远程流程服务调用压缩为一次索引分页查询。</p>
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param userDTO 姓名、手机号、账号关键词及指定用户筛选条件
|
||||
* @return 仅含基础字段的用户分页结果,total 固定为 0
|
||||
*/
|
||||
@Override
|
||||
public IPage<UserVO> getSelectUsersPage(Page page, UserDTO userDTO) {
|
||||
// 下拉组件按关键词远程搜索,不展示总条数,关闭 COUNT 查询。
|
||||
page.setSearchCount(false);
|
||||
return baseMapper.getBasicUserPage(page, userDTO, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询导出所需的完整用户主资料并批量补齐身份资料。
|
||||
*
|
||||
|
||||
@@ -357,6 +357,10 @@
|
||||
LEFT JOIN sys_dept d ON d.dept_id = u.dept_id
|
||||
<where>
|
||||
u.del_flag = '0'
|
||||
<!-- 用户选择器回显已选用户时按单个用户 ID 精确查询。 -->
|
||||
<if test="query.userId != null">
|
||||
AND u.user_id = #{query.userId}
|
||||
</if>
|
||||
<if test="query.nameOrPhone != null and query.nameOrPhone != ''">
|
||||
<bind name="basicNameOrPhone" value="'%'+query.nameOrPhone+'%'"/>
|
||||
AND (u.phone LIKE #{basicNameOrPhone}
|
||||
|
||||
+6
@@ -51,6 +51,12 @@ public class WalletBalanceTransactionDto {
|
||||
*/
|
||||
private String sysRemark;
|
||||
|
||||
/**
|
||||
* 订单设备类型快照编码(HardTypeEnum.type);订单支付/退款调用传订单 sys_type,
|
||||
* 随流水固化为 hard_type 供设备类型筛选,非订单调用不传。
|
||||
*/
|
||||
private String sysType;
|
||||
|
||||
/** 订单实际所属商户ID;账户级操作允许为空并由钱包服务解析当前食堂。 */
|
||||
private Long sourceMchId;
|
||||
|
||||
|
||||
+6
@@ -43,6 +43,12 @@ public class WalletTransactionDto extends WalletTransactionEntity implements Bas
|
||||
|
||||
private List<String> bizTypes;
|
||||
|
||||
/**
|
||||
* 订单设备类型快照编码多选(HardTypeEnum.type),直接按流水固化列 hard_type 过滤。
|
||||
* 仅支付/退款流水有设备类型快照,其余流水该列为空、天然不参与匹配。
|
||||
*/
|
||||
private List<String> sysTypes;
|
||||
|
||||
/**
|
||||
* 财务统计标签筛选值;传入多个字典值时匹配包含任一标签的流水。
|
||||
*/
|
||||
|
||||
+6
@@ -78,4 +78,10 @@ public class WalletRechargeRefundDto {
|
||||
*/
|
||||
@Length(max = 256, message = "系统备注长度不能超过 256")
|
||||
private String sysRemark;
|
||||
|
||||
/**
|
||||
* 原订单设备类型快照编码(HardTypeEnum.type);订单售后退款传订单 sys_type,
|
||||
* 随退款流水固化为 hard_type 供设备类型筛选,充值退款不传。
|
||||
*/
|
||||
private String sysType;
|
||||
}
|
||||
+7
@@ -62,6 +62,13 @@ public class WalletTransactionEntity extends Model<WalletTransactionEntity> {
|
||||
@Schema(description = "关联业务编号")
|
||||
private String bizId;
|
||||
|
||||
/**
|
||||
* 订单设备类型快照(HardTypeEnum.type);
|
||||
* 仅支付/退款流水(biz_id 存订单主键)由订单调用方携带,其余流水为空。
|
||||
*/
|
||||
@Schema(description = "订单设备类型快照编码")
|
||||
private String hardType;
|
||||
|
||||
/**
|
||||
* 流水标题
|
||||
*/
|
||||
|
||||
+10
-6
@@ -41,32 +41,36 @@ public class WalletTransactionExportExcelVo extends WalletUserExportExcelVo {
|
||||
@ExcelProperty(value = {"流水", "标题"}, index = 13)
|
||||
private String title;
|
||||
|
||||
/** 订单设备类型中文名称;充值、转账等非订单流水为空。 */
|
||||
@ExcelProperty(value = {"流水", "设备类型"}, index = 14)
|
||||
private String sysTypeName;
|
||||
|
||||
/** 本次交易金额,单位为元并保留两位小数,支出保留负数。 */
|
||||
@ExcelProperty(value = {"流水", "交易金额(元)"}, index = 14)
|
||||
@ExcelProperty(value = {"流水", "交易金额(元)"}, index = 15)
|
||||
@NumberFormat("#,##0.00")
|
||||
private BigDecimal price;
|
||||
|
||||
/** 交易完成后的钱包余额,单位为元并保留两位小数。 */
|
||||
@ExcelProperty(value = {"流水", "交易后余额(元)"}, index = 15)
|
||||
@ExcelProperty(value = {"流水", "交易后余额(元)"}, index = 16)
|
||||
@NumberFormat("#,##0.00")
|
||||
private BigDecimal balance;
|
||||
|
||||
/** 财务统计标签原始字典值,多个值使用英文逗号分隔。 */
|
||||
@ExcelProperty(value = {"流水", "财务标签"}, index = 16)
|
||||
@ExcelProperty(value = {"流水", "财务标签"}, index = 17)
|
||||
private String tags;
|
||||
|
||||
/** 后台补充的流水业务备注;历史记录允许为空。 */
|
||||
@ExcelProperty(value = {"流水", "备注"}, index = 17)
|
||||
@ExcelProperty(value = {"流水", "备注"}, index = 18)
|
||||
@ColumnWidth(24)
|
||||
private String remark;
|
||||
|
||||
/** 服务端按场景模板自动生成的流水简介(如"在食堂线下消费10元");历史未回填记录允许为空。 */
|
||||
@ExcelProperty(value = {"流水", "系统备注"}, index = 18)
|
||||
@ExcelProperty(value = {"流水", "系统备注"}, index = 19)
|
||||
@ColumnWidth(24)
|
||||
private String sysRemark;
|
||||
|
||||
/** 流水创建时间,格式为 yyyy-MM-dd HH:mm:ss。 */
|
||||
@ExcelProperty(value = {"流水", "交易时间"}, index = 19)
|
||||
@ExcelProperty(value = {"流水", "交易时间"}, index = 20)
|
||||
@ColumnWidth(22)
|
||||
private String createTime;
|
||||
}
|
||||
+38
@@ -10,6 +10,7 @@ import com.bsd.pjcloud.wallet.api.vo.base.BaseWalletInterface;
|
||||
import com.bsd.pjcloud.wallet.api.vo.base.BaseWalletTypeInterface;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@@ -148,6 +149,43 @@ public class WalletTransactionVo extends WalletTransactionEntity implements Base
|
||||
|
||||
private LocalDateTime orderPayTime;
|
||||
|
||||
/**
|
||||
* 订单设备类型快照编码(HardTypeEnum.type);按 biz_id 反查订单回填,
|
||||
* 充值、转账等非订单流水为空。
|
||||
*/
|
||||
private String sysType;
|
||||
|
||||
/** 订单设备类型中文名称;编码不在枚举内时回退展示原编码。 */
|
||||
private String sysTypeName;
|
||||
|
||||
/**
|
||||
* 消费场景摘要(查询时按页批量回填,历史流水全量有效)。
|
||||
* 订单支付/退款按 biz_id 反查订单:商户名、商品摘要(productDesc 原文);
|
||||
* 预约单(hourType 非空)才有预约日期与餐次,非预约单 apptUseDate 为下单当天不可展示。
|
||||
*/
|
||||
private String merchantName;
|
||||
|
||||
/** 商品摘要原文("商品名x数量;商品名x数量;");由客户端截断展示、展开区显示完整。 */
|
||||
private String productName;
|
||||
|
||||
/** 预约用餐日期;仅 hourType 非空的预约单有意义。 */
|
||||
private LocalDate apptUseDate;
|
||||
|
||||
/** 预约餐次编码(HardHourTypeEnum:1早餐/2午餐/3晚餐/4夜间餐);非预约单为空。 */
|
||||
private String hourType;
|
||||
|
||||
/** 充值套餐名;仅充值流水(biz_type=3)回填。 */
|
||||
private String rechargePackageName;
|
||||
|
||||
/** 充值实付金额(分);仅充值流水回填。 */
|
||||
private Integer rechargePayPrice;
|
||||
|
||||
/** 充值赠送金额(分);仅充值流水回填。 */
|
||||
private Integer rechargeBonusPrice;
|
||||
|
||||
/** 转账方向描述(如"饭卡 → 消费钱包");仅转账/转账退款流水回填。 */
|
||||
private String transferDesc;
|
||||
|
||||
/**
|
||||
* 导出相关
|
||||
*/
|
||||
|
||||
+2
@@ -45,6 +45,8 @@ public interface WalletTransactionMapper extends PjcloudBaseMapper<WalletTransac
|
||||
.eq(ObjectUtil.isNotEmpty(dto.getDeptId()), WalletTransactionEntity::getDeptId, dto.getDeptId())
|
||||
.in(ObjectUtil.isNotEmpty(dto.getUserIds()), WalletTransactionEntity::getUserId, dto.getUserIds())
|
||||
.in(ObjectUtil.isNotEmpty(dto.getBizTypes()), WalletTransactionEntity::getBizType, dto.getBizTypes())
|
||||
// 设备类型多选:流水固化列,非订单流水该列为空不参与匹配;空集合表示不筛选
|
||||
.in(ObjectUtil.isNotEmpty(dto.getSysTypes()), WalletTransactionEntity::getHardType, dto.getSysTypes())
|
||||
.between(
|
||||
ObjectUtil.isNotEmpty(dto.getCreateTimeList()),
|
||||
WalletTransactionEntity::getCreateTime,
|
||||
|
||||
+455
-55
@@ -3,6 +3,7 @@ package com.bsd.pjcloud.wallet.service.impl;
|
||||
import com.bsd.pjcloud.common.core.exception.ErrorCodes;
|
||||
import com.bsd.pjcloud.common.core.exception.I18nBizException;
|
||||
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
@@ -32,6 +33,10 @@ import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.data.datascope.DataScope;
|
||||
import com.bsd.pjcloud.common.data.datascope.PjcloudDefaultDataScopeHandle;
|
||||
import com.bsd.pjcloud.common.data.tenant.TenantContextHolder;
|
||||
import com.bsd.pjcloud.hard.api.constant.HardTypeEnum;
|
||||
import com.bsd.pjcloud.hard.api.entity.order.HardOrderEntity;
|
||||
import com.bsd.pjcloud.hard.api.entity.order.HardAfterSaleEntity;
|
||||
import com.bsd.pjcloud.hard.api.feign.RemoteHardOrderService;
|
||||
import com.bsd.pjcloud.thirdparty.constant.appmessage.AppMessageEventEnum;
|
||||
import com.bsd.pjcloud.wallet.api.constant.WalletBizEnum;
|
||||
import com.bsd.pjcloud.wallet.api.constant.WalletSysRemarkTemplate;
|
||||
@@ -51,7 +56,11 @@ import com.bsd.pjcloud.wallet.api.entity.WalletMessageOutboxEntity;
|
||||
import com.bsd.pjcloud.wallet.api.entity.WalletTransactionEntity;
|
||||
import com.bsd.pjcloud.wallet.api.entity.WalletTypeEntity;
|
||||
import com.bsd.pjcloud.wallet.api.entity.mch.MchManageEntity;
|
||||
import com.bsd.pjcloud.wallet.api.entity.recharge.WalletRechargeEntity;
|
||||
import com.bsd.pjcloud.wallet.api.entity.recharge.WalletRechargePackageEntity;
|
||||
import com.bsd.pjcloud.wallet.api.entity.rule.WalletRuleTransactionEntity;
|
||||
import com.bsd.pjcloud.wallet.api.utils.WalletDataUtil;
|
||||
import com.bsd.pjcloud.wallet.api.vo.MchManageVo;
|
||||
import com.bsd.pjcloud.wallet.api.vo.*;
|
||||
import com.bsd.pjcloud.wallet.api.vo.mch.WalletMchDeptViewVo;
|
||||
import com.bsd.pjcloud.wallet.api.vo.transaction.WalletTransactionVo;
|
||||
@@ -60,6 +69,9 @@ import com.bsd.pjcloud.wallet.mapper.WalletMessageOutboxMapper;
|
||||
import com.bsd.pjcloud.wallet.mapper.WalletTransactionMapper;
|
||||
import com.bsd.pjcloud.wallet.mapper.WalletTypeMapper;
|
||||
import com.bsd.pjcloud.wallet.mapper.mch.view.MchDeptViewMapper;
|
||||
import com.bsd.pjcloud.wallet.mapper.recharge.WalletRechargeMapper;
|
||||
import com.bsd.pjcloud.wallet.mapper.recharge.WalletRechargePackageMapper;
|
||||
import com.bsd.pjcloud.wallet.mapper.rule.WalletRuleTransactionMapper;
|
||||
import com.bsd.pjcloud.wallet.service.MchManageService;
|
||||
import com.bsd.pjcloud.wallet.service.WalletManageService;
|
||||
import com.bsd.pjcloud.wallet.service.WalletThirdZyService;
|
||||
@@ -80,7 +92,9 @@ import org.springframework.validation.annotation.Validated;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -138,7 +152,9 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
private final WalletThirdZyService walletThirdZyService;
|
||||
|
||||
private final WalletManageMapper walletManageMapper;
|
||||
/** 钱包业务消息Outbox Mapper,与钱包流水使用同一数据源和本地事务。 */
|
||||
/**
|
||||
* 钱包业务消息Outbox Mapper,与钱包流水使用同一数据源和本地事务。
|
||||
*/
|
||||
private final WalletMessageOutboxMapper walletMessageOutboxMapper;
|
||||
private final WalletTypeMapper walletTypeMapper;
|
||||
private final MchDeptViewMapper mchDeptViewMapper;
|
||||
@@ -148,7 +164,9 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
private final RemoteUserService userService;
|
||||
private final RemoteDeptService deptService;
|
||||
|
||||
/** 钱包流水用户身份筛选和展示资料装配器,统一复用 UPMS 权限用户查询。 */
|
||||
/**
|
||||
* 钱包流水用户身份筛选和展示资料装配器,统一复用 UPMS 权限用户查询。
|
||||
*/
|
||||
private final WalletUserIdentitySupport userIdentitySupport;
|
||||
|
||||
/**
|
||||
@@ -156,6 +174,20 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
*/
|
||||
private final RemoteUserRelationService remoteUserRelationService;
|
||||
|
||||
/**
|
||||
* 硬件订单远程服务:按 biz_id 批量反查订单回填消费场景摘要(商品/预约/商户)。
|
||||
*/
|
||||
private final RemoteHardOrderService remoteHardOrderService;
|
||||
|
||||
/** 充值记录 Mapper:充值流水按 biz_id 反查套餐与实付/赠送金额。 */
|
||||
private final WalletRechargeMapper walletRechargeMapper;
|
||||
|
||||
/** 充值套餐 Mapper:按套餐主键批量取套餐名。 */
|
||||
private final WalletRechargePackageMapper walletRechargePackageMapper;
|
||||
|
||||
/** 转账记录 Mapper:转账/转账退款流水按 biz_id 反查源与目标钱包。 */
|
||||
private final WalletRuleTransactionMapper walletRuleTransactionMapper;
|
||||
|
||||
/**
|
||||
* 项目统一 Redisson 客户端,用于协调并发扣款、补偿和记录迟到扣款禁止标记。
|
||||
*/
|
||||
@@ -199,6 +231,7 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
if (StrUtil.isBlank(entity.getSysRemark())) {
|
||||
entity.setSysRemark(WalletSysRemarkTemplate.defaultRemark(entity.getBizType(), entity.getPrice()));
|
||||
}
|
||||
fillHardTypeFromContext(entity);
|
||||
boolean saved = baseMapper.insert(entity) > 0;
|
||||
if (saved) {
|
||||
createMessageOutbox(entity);
|
||||
@@ -206,6 +239,26 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
return saved;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从当前调用的订单上下文固化设备类型快照到流水 hard_type 列。
|
||||
*
|
||||
* <p>订单支付/退款调用在 Controller 层经 {@link WalletMessageContextHolder#supplyWith}
|
||||
* 绑定明细(含 sysType),这里按钱包+业务号+业务类型取回落库;非订单调用
|
||||
* (充值、转账等)没有上下文,hard_type 保持为空。</p>
|
||||
*
|
||||
* @param entity 待持久化钱包流水
|
||||
*/
|
||||
private void fillHardTypeFromContext(WalletTransactionEntity entity) {
|
||||
if (ObjectUtil.isNotEmpty(entity.getHardType())) {
|
||||
return;
|
||||
}
|
||||
WalletBalanceTransactionDto context = WalletMessageContextHolder.find(
|
||||
entity.getWalletId(), entity.getBizId(), entity.getBizType());
|
||||
if (context != null) {
|
||||
entity.setHardType(context.getSysType());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据新钱包流水和当前调用上下文创建不可变消息快照;来源商户无法解析时保留空值,由消息中心仅入本人收件箱。
|
||||
*
|
||||
@@ -263,7 +316,7 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
/**
|
||||
* 解析钱包事件来源商户:订单优先使用调用方实际商户,账户级事件按钱包所有人部门取当前食堂。
|
||||
*
|
||||
* @param context 当前远程调用的可选订单上下文
|
||||
* @param context 当前远程调用的可选订单上下文
|
||||
* @param walletOwnerDeptId 钱包所有人部门ID
|
||||
* @return 来源商户,无法可靠解析时为空
|
||||
*/
|
||||
@@ -602,7 +655,7 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
* 按稳定业务键顺序获取整批订单交易锁,并把释放动作绑定到当前数据库事务完成之后。
|
||||
* 这样补偿返回时可以确定同一订单的扣款请求不在并发执行,数据库行锁也不会在 Redis 锁之前释放。
|
||||
*
|
||||
* @param payments 已完成基础校验的支付或补偿明细
|
||||
* @param payments 已完成基础校验的支付或补偿明细
|
||||
* @param rollbackMarkerKeys 当前补偿事务写入、需要在事务回滚时删除的取消标记键;扣款场景传空列表
|
||||
*/
|
||||
private void acquirePaymentOperationLocks(List<WalletBalanceTransactionDto> payments, List<String> rollbackMarkerKeys) {
|
||||
@@ -629,7 +682,7 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
/**
|
||||
* 将分布式锁释放绑定到事务完成阶段,确保本地 PAYMENT/REFUND 流水提交前同一业务号不会被另一请求处理。
|
||||
*
|
||||
* @param locks 当前线程已经获取的订单交易锁
|
||||
* @param locks 当前线程已经获取的订单交易锁
|
||||
* @param rollbackMarkerKeys 当前补偿事务写入的取消标记键,事务回滚时必须在释放锁前删除
|
||||
*/
|
||||
private void registerPaymentOperationLockRelease(List<RLock> locks, List<String> rollbackMarkerKeys) {
|
||||
@@ -701,7 +754,7 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
/**
|
||||
* 记录有期限的订单支付取消终态。补偿接口只在存在 PAYMENT 时写 REFUND,标记本身不参与财务流水统计。
|
||||
*
|
||||
* @param payment 单笔已进入补偿流程的订单支付明细
|
||||
* @param payment 单笔已进入补偿流程的订单支付明细
|
||||
* @param rollbackMarkerKeys 当前事务回滚时需要删除的标记键集合
|
||||
*/
|
||||
private void markPaymentCancelled(WalletBalanceTransactionDto payment, List<String> rollbackMarkerKeys) {
|
||||
@@ -756,9 +809,9 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
* 锁定钱包后按钱包、业务号和业务类型查询已有流水,解决远程调用成功但调用方未落状态时的重复入账。
|
||||
*
|
||||
* @param walletId 目标钱包 ID
|
||||
* @param price 增加金额,单位分
|
||||
* @param bizId 稳定业务号
|
||||
* @param bizType 钱包业务类型
|
||||
* @param price 增加金额,单位分
|
||||
* @param bizId 稳定业务号
|
||||
* @param bizType 钱包业务类型
|
||||
* @return 首次创建或已经存在且金额一致的流水
|
||||
*/
|
||||
@Override
|
||||
@@ -772,11 +825,11 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
* 新建流水时同步保存财务标签和流水备注。
|
||||
*
|
||||
* @param walletId 目标钱包 ID
|
||||
* @param price 增加金额,单位分
|
||||
* @param bizId 稳定业务号
|
||||
* @param bizType 钱包业务类型
|
||||
* @param tags 财务标签字典值,多个值使用英文逗号分隔,允许为空
|
||||
* @param remark 流水业务备注,允许为空,最多 256 个字符
|
||||
* @param price 增加金额,单位分
|
||||
* @param bizId 稳定业务号
|
||||
* @param bizType 钱包业务类型
|
||||
* @param tags 财务标签字典值,多个值使用英文逗号分隔,允许为空
|
||||
* @param remark 流水业务备注,允许为空,最多 256 个字符
|
||||
* @return 首次创建或已经存在且金额一致的流水
|
||||
*/
|
||||
@Override
|
||||
@@ -789,12 +842,12 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
* 锁定钱包后按钱包、业务号和业务类型查询已有流水,解决远程调用成功但调用方未落状态时的重复入账;
|
||||
* 新建流水时同步保存财务标签、流水备注和系统备注。
|
||||
*
|
||||
* @param walletId 目标钱包 ID
|
||||
* @param price 增加金额,单位分
|
||||
* @param bizId 稳定业务号
|
||||
* @param bizType 钱包业务类型
|
||||
* @param tags 财务标签字典值,多个值使用英文逗号分隔,允许为空
|
||||
* @param remark 流水业务备注,允许为空,最多 256 个字符
|
||||
* @param walletId 目标钱包 ID
|
||||
* @param price 增加金额,单位分
|
||||
* @param bizId 稳定业务号
|
||||
* @param bizType 钱包业务类型
|
||||
* @param tags 财务标签字典值,多个值使用英文逗号分隔,允许为空
|
||||
* @param remark 流水业务备注,允许为空,最多 256 个字符
|
||||
* @param sysRemark 调用方按场景渲染好的系统备注(模板见 WalletSysRemarkTemplate),允许为空
|
||||
* @return 首次创建或已经存在且金额一致的流水
|
||||
*/
|
||||
@@ -814,7 +867,8 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
}
|
||||
switch (bizType) {
|
||||
case UPDATE, RECHARGE, BATCH, TRANSFER_ACCOUNTS -> walletManageMapper.updateWhenRecharge(walletId, price);
|
||||
case REFUND, TRANSFER_ACCOUNTS_REFUND, RETURN_CANTEEN -> walletManageMapper.updateWhenConsumptionRefund(walletId, price);
|
||||
case REFUND, TRANSFER_ACCOUNTS_REFUND, RETURN_CANTEEN ->
|
||||
walletManageMapper.updateWhenConsumptionRefund(walletId, price);
|
||||
default -> throw new UnsupportedOperationException("待实现");
|
||||
}
|
||||
WalletTransactionEntity transaction = buildWalletTransaction(wallet, walletId, price,
|
||||
@@ -831,10 +885,10 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
* 增加钱包余额并将来源业务的财务标签同步到最终流水,供后续列表筛选和金额统计。
|
||||
*
|
||||
* @param walletId 钱包 id
|
||||
* @param price 增加金额,单位分
|
||||
* @param bizId 业务关联 id
|
||||
* @param bizType 业务关联分类
|
||||
* @param tags 财务标签字典值,多个值使用英文逗号分隔,允许为空
|
||||
* @param price 增加金额,单位分
|
||||
* @param bizId 业务关联 id
|
||||
* @param bizType 业务关联分类
|
||||
* @param tags 财务标签字典值,多个值使用英文逗号分隔,允许为空
|
||||
* @return 已保存的钱包流水
|
||||
*/
|
||||
@Override
|
||||
@@ -847,11 +901,11 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
* 增加钱包余额,并在同一笔最终流水中保存财务标签和人工备注。
|
||||
*
|
||||
* @param walletId 钱包 id
|
||||
* @param price 增加金额,单位分
|
||||
* @param bizId 业务关联 id
|
||||
* @param bizType 业务关联分类
|
||||
* @param tags 财务标签字典值,多个值使用英文逗号分隔,允许为空
|
||||
* @param remark 流水业务备注,允许为空,最多 256 个字符
|
||||
* @param price 增加金额,单位分
|
||||
* @param bizId 业务关联 id
|
||||
* @param bizType 业务关联分类
|
||||
* @param tags 财务标签字典值,多个值使用英文逗号分隔,允许为空
|
||||
* @param remark 流水业务备注,允许为空,最多 256 个字符
|
||||
* @return 已保存的钱包流水
|
||||
*/
|
||||
@Override
|
||||
@@ -863,12 +917,12 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
/**
|
||||
* 增加钱包余额,并在同一笔最终流水中保存财务标签、人工备注和系统备注。
|
||||
*
|
||||
* @param walletId 钱包 id
|
||||
* @param price 增加金额,单位分
|
||||
* @param bizId 业务关联 id
|
||||
* @param bizType 业务关联分类
|
||||
* @param tags 财务标签字典值,多个值使用英文逗号分隔,允许为空
|
||||
* @param remark 流水业务备注,允许为空,最多 256 个字符
|
||||
* @param walletId 钱包 id
|
||||
* @param price 增加金额,单位分
|
||||
* @param bizId 业务关联 id
|
||||
* @param bizType 业务关联分类
|
||||
* @param tags 财务标签字典值,多个值使用英文逗号分隔,允许为空
|
||||
* @param remark 流水业务备注,允许为空,最多 256 个字符
|
||||
* @param sysRemark 调用方按场景渲染好的系统备注(模板见 WalletSysRemarkTemplate),允许为空
|
||||
* @return 已保存的钱包流水
|
||||
*/
|
||||
@@ -1001,8 +1055,8 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
* 重置钱包余额并将来源导入记录的财务标签同步到重置流水。
|
||||
*
|
||||
* @param walletId 钱包 id
|
||||
* @param bizId 业务关联 id
|
||||
* @param tags 财务标签字典值,多个值使用英文逗号分隔,允许为空
|
||||
* @param bizId 业务关联 id
|
||||
* @param tags 财务标签字典值,多个值使用英文逗号分隔,允许为空
|
||||
* @return 重置成功返回 {@code true}
|
||||
*/
|
||||
@Override
|
||||
@@ -1039,8 +1093,8 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
* 锁定钱包行后按业务号查重,再重置余额;预约执行等可重试场景重复调用不会产生第二笔重置流水。
|
||||
*
|
||||
* @param walletId 钱包 id
|
||||
* @param bizId 业务关联 id,与重置业务类型共同确定幂等范围
|
||||
* @param tags 财务标签字典值,多个值使用英文逗号分隔,允许为空
|
||||
* @param bizId 业务关联 id,与重置业务类型共同确定幂等范围
|
||||
* @param tags 财务标签字典值,多个值使用英文逗号分隔,允许为空
|
||||
* @return 重置成功或此前已经重置过返回 {@code true}
|
||||
*/
|
||||
@Override
|
||||
@@ -1092,8 +1146,8 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
/**
|
||||
* 分页查询统一导出所需的流水业务资料,并跳过普通列表的 UPMS 用户回填。
|
||||
*
|
||||
* @param page 分页参数;仅第一页执行总数查询
|
||||
* @param dto 已冻结身份和业务场景的流水查询条件
|
||||
* @param page 分页参数;仅第一页执行总数查询
|
||||
* @param dto 已冻结身份和业务场景的流水查询条件
|
||||
* @param dataScope 当前操作者的数据权限范围
|
||||
* @return 已装配部门、钱包、钱包类型及订单号的流水分页
|
||||
*/
|
||||
@@ -1108,15 +1162,15 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
/**
|
||||
* 分页查询个人充值统计导出流水,并复用导出分页的部门、钱包和钱包类型装配逻辑。
|
||||
*
|
||||
* @param page 分页参数;仅第一页执行总数查询
|
||||
* @param dto 已冻结身份和充值统计筛选条件
|
||||
* @param dataScope 当前操作者的数据权限范围
|
||||
* @param page 分页参数;仅第一页执行总数查询
|
||||
* @param dto 已冻结身份和充值统计筛选条件
|
||||
* @param dataScope 当前操作者的数据权限范围
|
||||
* @param excludedDeptIds 统计场景固定排除的部门主键
|
||||
* @return 不重复查询 UPMS 用户资料的个人充值统计导出分页
|
||||
*/
|
||||
@Override
|
||||
public Page<WalletTransactionVo> scopeRechargePersonExportPage(Page page, WalletTransactionDto dto,
|
||||
DataScope dataScope, List<Long> excludedDeptIds) {
|
||||
DataScope dataScope, List<Long> excludedDeptIds) {
|
||||
Page<WalletTransactionVo> list = baseMapper.scopeRechargePersonExportPage(page, dto, dataScope,
|
||||
excludedDeptIds);
|
||||
disposeExportList(list.getRecords(), dto);
|
||||
@@ -1127,16 +1181,16 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
* 分页查询本人或有效被监护人的 EAT 饭卡流水。目标用户与钱包均由服务端重新解析,
|
||||
* 避免客户端通过篡改用户 ID、用户集合或钱包 ID 越权读取其他账户流水。
|
||||
*
|
||||
* @param loginUserId 当前登录用户 ID
|
||||
* @param loginUserId 当前登录用户 ID
|
||||
* @param targetUserId 目标用户 ID,仅允许本人或当前有效被监护人
|
||||
* @param page 分页参数
|
||||
* @param dto 收支类型等可选筛选条件
|
||||
* @param page 分页参数
|
||||
* @param dto 收支类型等可选筛选条件
|
||||
* @return 目标饭卡流水分页;未开通 EAT 饭卡时返回空分页
|
||||
*/
|
||||
@Override
|
||||
public Page<WalletTransactionVo> pageSelfOrGuardianWardTransactions(Long loginUserId, Long targetUserId,
|
||||
Page<WalletTransactionVo> page,
|
||||
WalletTransactionDto dto) {
|
||||
Page<WalletTransactionVo> page,
|
||||
WalletTransactionDto dto) {
|
||||
validateSelfOrGuardianWardUser(loginUserId, targetUserId);
|
||||
WalletManageEntity wallet = walletManageService.getWallet(targetUserId, WalletTypeEnum.EAT);
|
||||
if (wallet == null) {
|
||||
@@ -1154,7 +1208,7 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
* 校验目标用户是否为登录用户本人或其当前有效被监护人。关系服务异常时必须拒绝访问,
|
||||
* 不能在授权状态未知时降级放行敏感资金流水。
|
||||
*
|
||||
* @param loginUserId 当前登录用户 ID
|
||||
* @param loginUserId 当前登录用户 ID
|
||||
* @param targetUserId 目标用户 ID
|
||||
*/
|
||||
private void validateSelfOrGuardianWardUser(Long loginUserId, Long targetUserId) {
|
||||
@@ -1438,7 +1492,7 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
* 装配普通钱包流水列表的用户、身份及业务展示资料。
|
||||
*
|
||||
* @param list 当前列表分页记录
|
||||
* @param dto 当前流水查询条件,用于判断是否解析订单号
|
||||
* @param dto 当前流水查询条件,用于判断是否解析订单号
|
||||
*/
|
||||
private void disposeList(List<WalletTransactionVo> list, WalletTransactionDto dto) {
|
||||
if (ObjectUtil.isEmpty(list)) {
|
||||
@@ -1453,7 +1507,7 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
* 装配钱包流水导出与普通列表共用的部门、钱包、钱包类型及订单号资料。
|
||||
*
|
||||
* @param list 当前分页记录;为空时不执行远程或数据库查询
|
||||
* @param dto 当前流水查询条件,用于判断是否解析订单号
|
||||
* @param dto 当前流水查询条件,用于判断是否解析订单号
|
||||
*/
|
||||
private void disposeExportList(List<WalletTransactionVo> list, WalletTransactionDto dto) {
|
||||
if (ObjectUtil.isNotEmpty(list)) {
|
||||
@@ -1495,13 +1549,17 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
}
|
||||
});
|
||||
}
|
||||
// 设备类型展示列:纯内存名称映射,非订单流水该列为空由前端按占位符展示
|
||||
fillSysTypeColumns(list);
|
||||
// 消费场景摘要:商品/预约/商户/充值套餐/转账方向,让用户知道这笔钱花在哪
|
||||
fillSceneSummary(list);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将身份条件预筛选为用户主键,避免钱包流水 SQL 因空身份集合而放宽结果。
|
||||
*
|
||||
* @param dto 钱包流水查询条件
|
||||
* @param dto 钱包流水筛选条件
|
||||
*/
|
||||
@Override
|
||||
public void prepareIdentityQuery(WalletTransactionDto dto) {
|
||||
@@ -1516,4 +1574,346 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
|
||||
dto.setHasFace(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回填设备类型展示列:hard_type 非空行做纯内存中文名称映射,无远程查询;
|
||||
* 充值、转账等非订单流水该列为空,由前端按占位符展示。
|
||||
*
|
||||
* @param list 当前分页记录
|
||||
*/
|
||||
private void fillSysTypeColumns(List<WalletTransactionVo> list) {
|
||||
if (ObjectUtil.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
list.stream()
|
||||
.filter(row -> ObjectUtil.isNotEmpty(row.getHardType()))
|
||||
.forEach(row -> {
|
||||
row.setSysType(row.getHardType());
|
||||
row.setSysTypeName(resolveHardSysTypeName(row.getHardType()));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备类型编码转中文名称;编码不在枚举内(历史脏数据)时回退展示原编码。
|
||||
*/
|
||||
private static String resolveHardSysTypeName(String sysType) {
|
||||
return Arrays.stream(HardTypeEnum.values())
|
||||
.filter(type -> ObjectUtil.equals(type.getType(), sysType))
|
||||
.findFirst()
|
||||
.map(HardTypeEnum::getDescription)
|
||||
.orElse(sysType);
|
||||
}
|
||||
|
||||
/** 消费场景装配的单批远程/数据库查询上限,与系统备注回填的 500 一批口径一致。 */
|
||||
private static final int SCENE_BACKFILL_BATCH_SIZE = 500;
|
||||
|
||||
/**
|
||||
* 按页回填消费场景摘要,让用户明确知道每笔钱的去向。
|
||||
*
|
||||
* <p>按业务类型分流批量装配:订单支付/退款(hard_type 非空)反查硬件订单取商品摘要、
|
||||
* 预约日期餐次和商户名;充值反查充值记录取套餐与实付/赠送;转账反查转账记录拼
|
||||
* 「源钱包 → 目标钱包」。充值退款(hard_type 为空)沿用系统备注文案不额外装配。
|
||||
* 各查询失败均降级留空,不阻断列表返回。</p>
|
||||
*
|
||||
* @param list 当前分页记录
|
||||
*/
|
||||
private void fillSceneSummary(List<WalletTransactionVo> list) {
|
||||
if (ObjectUtil.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
fillOrderSceneSummary(list.stream().filter(this::isOrderSceneRow).toList());
|
||||
fillRechargeSceneSummary(list.stream()
|
||||
.filter(row -> WalletBizEnum.RECHARGE.getCode().equals(row.getBizType())).toList());
|
||||
fillTransferSceneSummary(list.stream()
|
||||
.filter(row -> WalletBizEnum.TRANSFER_ACCOUNTS.getCode().equals(row.getBizType())
|
||||
|| WalletBizEnum.TRANSFER_ACCOUNTS_REFUND.getCode().equals(row.getBizType()))
|
||||
.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单场景行:支付/退款流水且固化过设备类型(区分于充值退款,后者的 biz_id 是售后单号)。
|
||||
*/
|
||||
private boolean isOrderSceneRow(WalletTransactionVo row) {
|
||||
return ObjectUtil.isNotEmpty(row.getHardType())
|
||||
&& (WalletBizEnum.PAYMENT.getCode().equals(row.getBizType())
|
||||
|| WalletBizEnum.REFUND.getCode().equals(row.getBizType()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单场景:支付流水 biz_id 即订单主键直接反查;退款流水 biz_id 是售后单主键
|
||||
* (订单售后退款经 WalletRechargeServiceImpl.refund 落库),需先反查售后单再经
|
||||
* orderId 取原订单。查到订单后回填商品摘要、预约信息、商户名与 orderId(供 App 跳转订单详情)。
|
||||
*/
|
||||
private void fillOrderSceneSummary(List<WalletTransactionVo> rows) {
|
||||
if (rows.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 支付行:biz_id(订单主键) -> 订单
|
||||
Map<Long, HardOrderEntity> paymentOrderMap = fetchOrdersByBizIds(rows.stream()
|
||||
.filter(row -> WalletBizEnum.PAYMENT.getCode().equals(row.getBizType())).toList());
|
||||
// 退款行:biz_id(售后单主键) -> 售后单 orderId -> 订单
|
||||
Map<Long, HardOrderEntity> refundOrderMap = fetchOrdersByAfterSaleIds(rows.stream()
|
||||
.filter(row -> WalletBizEnum.REFUND.getCode().equals(row.getBizType())).toList());
|
||||
if (paymentOrderMap.isEmpty() && refundOrderMap.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<Long, HardOrderEntity> allOrderMap = new HashMap<>(paymentOrderMap);
|
||||
allOrderMap.putAll(refundOrderMap);
|
||||
List<Long> merchantIds = allOrderMap.values().stream()
|
||||
.map(HardOrderEntity::getMerchantId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.toList();
|
||||
Map<Long, String> merchantNameMap = merchantIds.isEmpty() ? Map.of() : fetchMerchantNames(merchantIds);
|
||||
rows.forEach(row -> {
|
||||
HardOrderEntity order = resolveRowOrder(row, paymentOrderMap, refundOrderMap);
|
||||
if (order == null) {
|
||||
return;
|
||||
}
|
||||
row.setOrderId(order.getId());
|
||||
row.setProductName(order.getProductDesc());
|
||||
row.setApptUseDate(order.getApptUseDate());
|
||||
row.setHourType(order.getHourType());
|
||||
row.setMerchantName(order.getMerchantId() == null ? null : merchantNameMap.get(order.getMerchantId()));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析流水行关联的原订单:支付行按 biz_id(订单主键) 命中支付订单集合;
|
||||
* 退款行按 biz_id(售后单主键) 命中经售后反查的订单集合。
|
||||
*/
|
||||
private HardOrderEntity resolveRowOrder(WalletTransactionVo row, Map<Long, HardOrderEntity> paymentOrderMap,
|
||||
Map<Long, HardOrderEntity> refundOrderMap) {
|
||||
Long bizId = parseNumericBizId(row.getBizId());
|
||||
if (bizId == null) {
|
||||
return null;
|
||||
}
|
||||
return WalletBizEnum.PAYMENT.getCode().equals(row.getBizType())
|
||||
? paymentOrderMap.get(bizId) : refundOrderMap.get(bizId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付流水:biz_id 为纯数字订单主键时批量反查订单;杂音业务号不参与。
|
||||
*/
|
||||
private Map<Long, HardOrderEntity> fetchOrdersByBizIds(List<WalletTransactionVo> rows) {
|
||||
List<Long> orderIds = rows.stream()
|
||||
.map(WalletTransactionVo::getBizId)
|
||||
.map(WalletTransactionServiceImpl::parseNumericBizId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.toList();
|
||||
if (orderIds.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
return fetchHardOrdersByIds(orderIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 退款流水:biz_id 为纯数字售后单主键时先反查售后单(保留 售后主键→订单 的行级映射),
|
||||
* 再按 orderId 批量取原订单;充值售后 orderId 无效查不到订单,天然只命中订单退款。
|
||||
* 返回集合的键为售后单主键(即退款流水 biz_id),供行级直接命中。
|
||||
*/
|
||||
private Map<Long, HardOrderEntity> fetchOrdersByAfterSaleIds(List<WalletTransactionVo> rows) {
|
||||
List<Long> afterSaleIds = rows.stream()
|
||||
.map(WalletTransactionVo::getBizId)
|
||||
.map(WalletTransactionServiceImpl::parseNumericBizId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.toList();
|
||||
if (afterSaleIds.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
Map<Long, Long> saleOrderMap = new HashMap<>(afterSaleIds.size());
|
||||
for (List<Long> idBatch : ListUtil.partition(afterSaleIds, SCENE_BACKFILL_BATCH_SIZE)) {
|
||||
try {
|
||||
R<List<HardAfterSaleEntity>> result = remoteHardOrderService.listAfterSalesByIds(idBatch);
|
||||
if (result == null || !result.isOk() || ObjectUtil.isEmpty(result.getData())) {
|
||||
continue;
|
||||
}
|
||||
result.getData().stream()
|
||||
.filter(sale -> sale.getId() != null && sale.getOrderId() != null)
|
||||
.forEach(sale -> saleOrderMap.put(sale.getId(), sale.getOrderId()));
|
||||
} catch (Exception exception) {
|
||||
log.warn("|{}|退款消费场景反查售后单失败,批次大小:|{}|", TAG, idBatch.size(), exception);
|
||||
}
|
||||
}
|
||||
if (saleOrderMap.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
Map<Long, HardOrderEntity> orderMap = fetchHardOrdersByIds(List.copyOf(saleOrderMap.values()));
|
||||
// 键换回售后单主键,与退款流水 biz_id 对齐
|
||||
Map<Long, HardOrderEntity> saleOrderRef = new HashMap<>(saleOrderMap.size());
|
||||
saleOrderMap.forEach((saleId, orderId) -> {
|
||||
HardOrderEntity order = orderMap.get(orderId);
|
||||
if (order != null) {
|
||||
saleOrderRef.put(saleId, order);
|
||||
}
|
||||
});
|
||||
return saleOrderRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量查询硬件订单;远程失败仅告警并返回已命中部分,不阻断流水列表。
|
||||
*/
|
||||
private Map<Long, HardOrderEntity> fetchHardOrdersByIds(List<Long> orderIds) {
|
||||
Map<Long, HardOrderEntity> orderMap = new HashMap<>(orderIds.size());
|
||||
for (List<Long> idBatch : ListUtil.partition(orderIds, SCENE_BACKFILL_BATCH_SIZE)) {
|
||||
try {
|
||||
R<List<HardOrderEntity>> result = remoteHardOrderService.getByIds(idBatch);
|
||||
if (result != null && result.isOk() && ObjectUtil.isNotEmpty(result.getData())) {
|
||||
result.getData().forEach(order -> orderMap.put(order.getId(), order));
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
log.warn("|{}|消费场景回填查询订单失败,批次大小:|{}|", TAG, idBatch.size(), exception);
|
||||
}
|
||||
}
|
||||
return orderMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量查询商户名(wallet 本地);失败降级返回空映射。
|
||||
*/
|
||||
private Map<Long, String> fetchMerchantNames(List<Long> merchantIds) {
|
||||
try {
|
||||
List<MchManageVo> merchants = mchManageService.list(MchManageDto.builder().ids(merchantIds).build());
|
||||
if (ObjectUtil.isEmpty(merchants)) {
|
||||
return Map.of();
|
||||
}
|
||||
return merchants.stream()
|
||||
.filter(merchant -> merchant.getId() != null)
|
||||
.collect(Collectors.toMap(MchManageVo::getId, MchManageVo::getName, (left, right) -> left));
|
||||
} catch (Exception exception) {
|
||||
log.warn("|{}|消费场景回填查询商户失败,商户数:|{}|", TAG, merchantIds.size(), exception);
|
||||
return Map.of();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 充值场景:biz_id 反查充值记录,回填套餐名与实付/赠送金额(到账金额即流水 price)。
|
||||
*/
|
||||
private void fillRechargeSceneSummary(List<WalletTransactionVo> rows) {
|
||||
if (rows.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<Long> rechargeIds = rows.stream()
|
||||
.map(WalletTransactionVo::getBizId)
|
||||
.map(WalletTransactionServiceImpl::parseNumericBizId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.toList();
|
||||
if (rechargeIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<Long, WalletRechargeEntity> rechargeMap = walletRechargeMapper.selectBatchIds(rechargeIds).stream()
|
||||
.collect(Collectors.toMap(WalletRechargeEntity::getId, recharge -> recharge, (left, right) -> left));
|
||||
if (rechargeMap.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<Long> packageIds = rechargeMap.values().stream()
|
||||
.map(WalletRechargeEntity::getPackageId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.toList();
|
||||
Map<Long, String> packageNameMap = packageIds.isEmpty() ? Map.of()
|
||||
: walletRechargePackageMapper.selectBatchIds(packageIds).stream()
|
||||
.filter(pkg -> ObjectUtil.isNotEmpty(pkg.getName()))
|
||||
.collect(Collectors.toMap(WalletRechargePackageEntity::getId,
|
||||
WalletRechargePackageEntity::getName, (left, right) -> left));
|
||||
rows.forEach(row -> {
|
||||
Long rechargeId = parseNumericBizId(row.getBizId());
|
||||
WalletRechargeEntity recharge = rechargeId == null ? null : rechargeMap.get(rechargeId);
|
||||
if (recharge == null) {
|
||||
return;
|
||||
}
|
||||
row.setRechargePackageName(recharge.getPackageId() == null ? null
|
||||
: packageNameMap.get(recharge.getPackageId()));
|
||||
row.setRechargePayPrice(recharge.getPayPrice());
|
||||
row.setRechargeBonusPrice(recharge.getBonusPrice());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 转账场景:biz_id 反查转账记录,按源/目标钱包的钱包类型拼「饭卡 → 消费钱包」方向描述。
|
||||
*/
|
||||
private void fillTransferSceneSummary(List<WalletTransactionVo> rows) {
|
||||
if (rows.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<Long> ruleIds = rows.stream()
|
||||
.map(WalletTransactionVo::getBizId)
|
||||
.map(WalletTransactionServiceImpl::parseNumericBizId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.toList();
|
||||
if (ruleIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<Long, WalletRuleTransactionEntity> ruleMap = walletRuleTransactionMapper.selectBatchIds(ruleIds).stream()
|
||||
.collect(Collectors.toMap(WalletRuleTransactionEntity::getId, rule -> rule, (left, right) -> left));
|
||||
if (ruleMap.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Set<Long> walletIds = new HashSet<>();
|
||||
ruleMap.values().forEach(rule -> {
|
||||
if (rule.getSourceWalletId() != null) {
|
||||
walletIds.add(rule.getSourceWalletId());
|
||||
}
|
||||
if (rule.getTargetWalletId() != null) {
|
||||
walletIds.add(rule.getTargetWalletId());
|
||||
}
|
||||
});
|
||||
Map<Long, String> walletTypeNameMap = resolveWalletTypeNames(new ArrayList<>(walletIds));
|
||||
rows.forEach(row -> {
|
||||
Long ruleId = parseNumericBizId(row.getBizId());
|
||||
WalletRuleTransactionEntity rule = ruleId == null ? null : ruleMap.get(ruleId);
|
||||
if (rule == null) {
|
||||
return;
|
||||
}
|
||||
String fromName = walletTypeNameMap.getOrDefault(rule.getSourceWalletId(), "钱包");
|
||||
String toName = walletTypeNameMap.getOrDefault(rule.getTargetWalletId(), "钱包");
|
||||
row.setTransferDesc(String.format("%s → %s", fromName, toName));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 按钱包主键批量解析钱包类型名称;类型缺失时回退「钱包」。
|
||||
*/
|
||||
private Map<Long, String> resolveWalletTypeNames(List<Long> walletIds) {
|
||||
if (walletIds.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
List<WalletManageEntity> wallets = walletManageMapper.selectBatchIds(walletIds);
|
||||
if (ObjectUtil.isEmpty(wallets)) {
|
||||
return Map.of();
|
||||
}
|
||||
List<Long> typeIds = wallets.stream()
|
||||
.map(WalletManageEntity::getWalletTypeId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.toList();
|
||||
Map<Long, String> typeNameMap = typeIds.isEmpty() ? Map.of()
|
||||
: walletTypeMapper.selectBatchIds(typeIds).stream()
|
||||
.filter(type -> ObjectUtil.isNotEmpty(type.getName()))
|
||||
.collect(Collectors.toMap(WalletTypeEntity::getId, WalletTypeEntity::getName,
|
||||
(left, right) -> left));
|
||||
return wallets.stream()
|
||||
.filter(wallet -> wallet.getId() != null)
|
||||
.collect(Collectors.toMap(WalletManageEntity::getId,
|
||||
wallet -> typeNameMap.getOrDefault(wallet.getWalletTypeId(), "钱包"),
|
||||
(left, right) -> left));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析流水 biz_id 中的数字主键;非数字业务号(退款单号、回滚标记等)返回 null。
|
||||
*/
|
||||
private static Long parseNumericBizId(String bizId) {
|
||||
if (ObjectUtil.isEmpty(bizId)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Long.valueOf(bizId);
|
||||
} catch (NumberFormatException exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+2
@@ -721,6 +721,7 @@ public class WalletUserExportServiceImpl implements WalletUserExportService {
|
||||
row.setWalletTypeName(source.getWalletTypeName());
|
||||
row.setCardNumber(source.getWalletCardNumber());
|
||||
row.setTitle(source.getTitle());
|
||||
row.setSysTypeName(source.getSysTypeName());
|
||||
row.setPrice(formatPrice(source.getPrice()));
|
||||
row.setBalance(formatPrice(source.getBalance()));
|
||||
row.setTags(source.getTags());
|
||||
@@ -1173,6 +1174,7 @@ public class WalletUserExportServiceImpl implements WalletUserExportService {
|
||||
BeanUtils.copyProperties(source, target);
|
||||
target.setUserIds(copyList(source.getUserIds()));
|
||||
target.setBizTypes(copyList(source.getBizTypes()));
|
||||
target.setSysTypes(copyList(source.getSysTypes()));
|
||||
target.setTagList(copyList(source.getTagList()));
|
||||
target.setTotalDpetIds(copyList(source.getTotalDpetIds()));
|
||||
target.setDeptIds(copyList(source.getDeptIds()));
|
||||
|
||||
+23
-3
@@ -65,6 +65,9 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import com.bsd.pjcloud.wallet.api.dto.WalletBalanceTransactionDto;
|
||||
import com.bsd.pjcloud.wallet.support.WalletMessageContextHolder;
|
||||
|
||||
|
||||
/**
|
||||
* 会员钱包充值
|
||||
@@ -331,10 +334,28 @@ public class WalletRechargeServiceImpl extends ServiceImpl<WalletRechargeMapper,
|
||||
return resumeOrValidateExistingRefund(dto, existing);
|
||||
}
|
||||
// 增加金额;售后退款调用方渲染的系统备注为空时由钱包服务按退款类型兜底
|
||||
walletTransactionService.addWalletBalance(wallet.getId(), dto.getPrice(), String.valueOf(dto.getAfterSaleId()), WalletBizEnum.REFUND, null, null, dto.getSysRemark());
|
||||
refundWithSceneContext(wallet.getId(), dto);
|
||||
return BeanUtils.toBean(recharge, WalletRechargeVo.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 售后退款入账并固化设备类型快照:订单售后退款经调用上下文携带订单 sysType,
|
||||
* 由流水保存逻辑固化为 hard_type;充值退款不传 sysType,hard_type 保持为空。
|
||||
*
|
||||
* @param walletId 退款到账的钱包 ID
|
||||
* @param dto 售后退款请求
|
||||
*/
|
||||
private void refundWithSceneContext(Long walletId, WalletRechargeRefundDto dto) {
|
||||
WalletBalanceTransactionDto refundContext = new WalletBalanceTransactionDto();
|
||||
refundContext.setWalletId(walletId);
|
||||
refundContext.setBizId(String.valueOf(dto.getAfterSaleId()));
|
||||
refundContext.setBizType(WalletBizEnum.REFUND);
|
||||
refundContext.setSysType(dto.getSysType());
|
||||
WalletMessageContextHolder.supplyWith(List.of(refundContext),
|
||||
() -> walletTransactionService.addWalletBalance(walletId, dto.getPrice(),
|
||||
String.valueOf(dto.getAfterSaleId()), WalletBizEnum.REFUND, null, null, dto.getSysRemark()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 对已存在退款记录区分“幂等重试”和“人工冲正后的再次同意”。退款状态为空兼容历史成功记录,
|
||||
* 状态为未售后时只在当前钱包行锁内重新入账一次,并恢复成功状态。
|
||||
@@ -362,8 +383,7 @@ public class WalletRechargeServiceImpl extends ServiceImpl<WalletRechargeMapper,
|
||||
throw new RuntimeException("钱包退款冲正流水金额不一致");
|
||||
}
|
||||
|
||||
walletTransactionService.addWalletBalance(existing.getWalletId(), dto.getPrice(),
|
||||
String.valueOf(dto.getAfterSaleId()), WalletBizEnum.REFUND, null, null, dto.getSysRemark());
|
||||
refundWithSceneContext(existing.getWalletId(), dto);
|
||||
LocalDateTime refundTime = LocalDateTime.now();
|
||||
WalletRechargeEntity update = WalletRechargeEntity.builder().id(existing.getId())
|
||||
.refundStatus(HardRefundStatusEnum.SUCCESS.getStatus()).refundTime(refundTime).build();
|
||||
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
package com.bsd.pjcloud.wallet.service.impl;
|
||||
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.hard.api.constant.HardTypeEnum;
|
||||
import com.bsd.pjcloud.hard.api.entity.order.HardAfterSaleEntity;
|
||||
import com.bsd.pjcloud.hard.api.entity.order.HardOrderEntity;
|
||||
import com.bsd.pjcloud.hard.api.feign.RemoteHardOrderService;
|
||||
import com.bsd.pjcloud.wallet.api.constant.WalletBizEnum;
|
||||
import com.bsd.pjcloud.wallet.api.dto.MchManageDto;
|
||||
import com.bsd.pjcloud.wallet.api.dto.WalletBalanceTransactionDto;
|
||||
import com.bsd.pjcloud.wallet.api.entity.WalletManageEntity;
|
||||
import com.bsd.pjcloud.wallet.api.entity.WalletTransactionEntity;
|
||||
import com.bsd.pjcloud.wallet.api.entity.WalletTypeEntity;
|
||||
import com.bsd.pjcloud.wallet.api.entity.recharge.WalletRechargeEntity;
|
||||
import com.bsd.pjcloud.wallet.api.entity.recharge.WalletRechargePackageEntity;
|
||||
import com.bsd.pjcloud.wallet.api.entity.rule.WalletRuleTransactionEntity;
|
||||
import com.bsd.pjcloud.wallet.api.vo.MchManageVo;
|
||||
import com.bsd.pjcloud.wallet.api.vo.transaction.WalletTransactionVo;
|
||||
import com.bsd.pjcloud.wallet.mapper.WalletManageMapper;
|
||||
import com.bsd.pjcloud.wallet.mapper.WalletTypeMapper;
|
||||
import com.bsd.pjcloud.wallet.mapper.recharge.WalletRechargeMapper;
|
||||
import com.bsd.pjcloud.wallet.mapper.recharge.WalletRechargePackageMapper;
|
||||
import com.bsd.pjcloud.wallet.mapper.rule.WalletRuleTransactionMapper;
|
||||
import com.bsd.pjcloud.wallet.service.MchManageService;
|
||||
import com.bsd.pjcloud.wallet.support.WalletMessageContextHolder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 设备类型快照固化与消费场景摘要回填测试:订单/充值/转账分流装配、充值退款跳过、远程失败降级。
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class WalletTransactionSysTypeFilterTest {
|
||||
|
||||
/** 被测钱包流水服务,设备类型场景仅需纯内存逻辑,其余依赖由 Mockito 注入空值。 */
|
||||
@InjectMocks
|
||||
private WalletTransactionServiceImpl service;
|
||||
|
||||
/** 硬件订单远程服务 Mock,用于订单场景摘要回填。 */
|
||||
@Mock
|
||||
private RemoteHardOrderService remoteHardOrderService;
|
||||
|
||||
/** 商户服务 Mock,用于订单场景商户名回填。 */
|
||||
@Mock
|
||||
private MchManageService mchManageService;
|
||||
|
||||
/** 充值记录 Mapper Mock。 */
|
||||
@Mock
|
||||
private WalletRechargeMapper walletRechargeMapper;
|
||||
|
||||
/** 充值套餐 Mapper Mock。 */
|
||||
@Mock
|
||||
private WalletRechargePackageMapper walletRechargePackageMapper;
|
||||
|
||||
/** 转账记录 Mapper Mock。 */
|
||||
@Mock
|
||||
private WalletRuleTransactionMapper walletRuleTransactionMapper;
|
||||
|
||||
/** 钱包 Mapper Mock,用于转账方向解析。 */
|
||||
@Mock
|
||||
private WalletManageMapper walletManageMapper;
|
||||
|
||||
/** 钱包类型 Mapper Mock,用于转账方向解析。 */
|
||||
@Mock
|
||||
private WalletTypeMapper walletTypeMapper;
|
||||
|
||||
/**
|
||||
* 订单调用经上下文绑定明细后,新流水应固化订单携带的设备类型快照。
|
||||
*/
|
||||
@Test
|
||||
void orderContextShouldFillHardTypeOnSave() {
|
||||
WalletBalanceTransactionDto context = new WalletBalanceTransactionDto();
|
||||
context.setWalletId(20L);
|
||||
context.setBizId("101");
|
||||
context.setBizType(WalletBizEnum.PAYMENT);
|
||||
context.setSysType(HardTypeEnum.COFFEE.getType());
|
||||
WalletTransactionEntity entity = transaction(20L, "101", WalletBizEnum.PAYMENT.getCode());
|
||||
|
||||
WalletMessageContextHolder.supplyWith(List.of(context), () -> {
|
||||
invokeFillHardType(entity);
|
||||
return null;
|
||||
});
|
||||
|
||||
assertEquals(HardTypeEnum.COFFEE.getType(), entity.getHardType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 非订单调用(充值、转账等)没有上下文,hard_type 保持为空。
|
||||
*/
|
||||
@Test
|
||||
void nonOrderCallShouldLeaveHardTypeEmpty() {
|
||||
WalletTransactionEntity entity = transaction(20L, "201", WalletBizEnum.RECHARGE.getCode());
|
||||
|
||||
invokeFillHardType(entity);
|
||||
|
||||
assertNull(entity.getHardType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用方已显式设置 hard_type 时不被上下文覆盖。
|
||||
*/
|
||||
@Test
|
||||
void presetHardTypeShouldNotBeOverridden() {
|
||||
WalletBalanceTransactionDto context = new WalletBalanceTransactionDto();
|
||||
context.setWalletId(20L);
|
||||
context.setBizId("101");
|
||||
context.setBizType(WalletBizEnum.PAYMENT);
|
||||
context.setSysType(HardTypeEnum.COFFEE.getType());
|
||||
WalletTransactionEntity entity = transaction(20L, "101", WalletBizEnum.PAYMENT.getCode());
|
||||
entity.setHardType(HardTypeEnum.EY_PRINT.getType());
|
||||
|
||||
WalletMessageContextHolder.supplyWith(List.of(context), () -> {
|
||||
invokeFillHardType(entity);
|
||||
return null;
|
||||
});
|
||||
|
||||
assertEquals(HardTypeEnum.EY_PRINT.getType(), entity.getHardType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 展示列回填:hard_type 非空行做本地名称映射,空行(非订单流水)保持为空。
|
||||
*/
|
||||
@Test
|
||||
void fillSysTypeColumnsShouldMapNamesLocally() {
|
||||
WalletTransactionVo payment = vo(WalletBizEnum.PAYMENT.getCode(), HardTypeEnum.COFFEE.getType());
|
||||
WalletTransactionVo recharge = vo(WalletBizEnum.RECHARGE.getCode(), null);
|
||||
WalletTransactionVo legacy = vo(WalletBizEnum.PAYMENT.getCode(), "99");
|
||||
|
||||
ReflectionTestUtils.invokeMethod(service, "fillSysTypeColumns", List.of(payment, recharge, legacy));
|
||||
|
||||
assertEquals(HardTypeEnum.COFFEE.getType(), payment.getSysType());
|
||||
assertEquals("咖啡机", payment.getSysTypeName());
|
||||
assertNull(recharge.getSysTypeName());
|
||||
// 编码不在枚举内的历史脏数据回退展示原编码
|
||||
assertEquals("99", legacy.getSysTypeName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单场景摘要:支付行反查订单回填商品/预约/商户;充值退款行(hard_type 空)不触发订单查询。
|
||||
*/
|
||||
@Test
|
||||
void orderSceneShouldFillProductApptAndMerchant() {
|
||||
WalletTransactionVo payment = vo(WalletBizEnum.PAYMENT.getCode(), HardTypeEnum.CANTEEN.getType());
|
||||
payment.setBizId("901");
|
||||
WalletTransactionVo rechargeRefund = vo(WalletBizEnum.REFUND.getCode(), null);
|
||||
rechargeRefund.setBizId("refund_1776925896139LIjX0rUNes");
|
||||
HardOrderEntity order = new HardOrderEntity();
|
||||
order.setId(901L);
|
||||
order.setProductDesc("红烧肉套餐x1;米饭x1;");
|
||||
order.setApptUseDate(LocalDate.of(2026, 10, 13));
|
||||
order.setHourType("2");
|
||||
order.setMerchantId(30L);
|
||||
when(remoteHardOrderService.getByIds(anyList())).thenReturn(R.ok(List.of(order)));
|
||||
MchManageVo merchant = new MchManageVo();
|
||||
merchant.setId(30L);
|
||||
merchant.setName("三食堂");
|
||||
when(mchManageService.list(any(MchManageDto.class))).thenReturn(List.of(merchant));
|
||||
|
||||
ReflectionTestUtils.invokeMethod(service, "fillSceneSummary", List.of(payment, rechargeRefund));
|
||||
|
||||
assertEquals("红烧肉套餐x1;米饭x1;", payment.getProductName());
|
||||
assertEquals(LocalDate.of(2026, 10, 13), payment.getApptUseDate());
|
||||
assertEquals("2", payment.getHourType());
|
||||
assertEquals("三食堂", payment.getMerchantName());
|
||||
assertEquals(901L, payment.getOrderId());
|
||||
assertNull(rechargeRefund.getProductName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单退款场景:biz_id 是售后单主键,经售后单 orderId 反查原订单回填摘要与 orderId。
|
||||
*/
|
||||
@Test
|
||||
void refundSceneShouldResolveOrderThroughAfterSale() {
|
||||
WalletTransactionVo refund = vo(WalletBizEnum.REFUND.getCode(), HardTypeEnum.COFFEE.getType());
|
||||
refund.setBizId("801");
|
||||
HardAfterSaleEntity sale = new HardAfterSaleEntity();
|
||||
sale.setId(801L);
|
||||
sale.setOrderId(902L);
|
||||
when(remoteHardOrderService.listAfterSalesByIds(anyList())).thenReturn(R.ok(List.of(sale)));
|
||||
HardOrderEntity order = new HardOrderEntity();
|
||||
order.setId(902L);
|
||||
order.setProductDesc("美式咖啡x1;");
|
||||
order.setMerchantId(31L);
|
||||
when(remoteHardOrderService.getByIds(anyList())).thenReturn(R.ok(List.of(order)));
|
||||
MchManageVo merchant = new MchManageVo();
|
||||
merchant.setId(31L);
|
||||
merchant.setName("楼下咖啡");
|
||||
when(mchManageService.list(any(MchManageDto.class))).thenReturn(List.of(merchant));
|
||||
|
||||
ReflectionTestUtils.invokeMethod(service, "fillSceneSummary", List.of(refund));
|
||||
|
||||
assertEquals("美式咖啡x1;", refund.getProductName());
|
||||
assertEquals("楼下咖啡", refund.getMerchantName());
|
||||
assertEquals(902L, refund.getOrderId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 充值售后退款:售后单 orderId 关联不到订单支付流水时不回填摘要(hard_type 也保持为空)。
|
||||
*/
|
||||
@Test
|
||||
void refundSceneWithoutOrderShouldLeaveSummaryEmpty() {
|
||||
WalletTransactionVo rechargeRefund = vo(WalletBizEnum.REFUND.getCode(), HardTypeEnum.COFFEE.getType());
|
||||
rechargeRefund.setBizId("802");
|
||||
HardAfterSaleEntity sale = new HardAfterSaleEntity();
|
||||
sale.setId(802L);
|
||||
sale.setOrderId(null);
|
||||
when(remoteHardOrderService.listAfterSalesByIds(anyList())).thenReturn(R.ok(List.of(sale)));
|
||||
|
||||
ReflectionTestUtils.invokeMethod(service, "fillSceneSummary", List.of(rechargeRefund));
|
||||
|
||||
assertNull(rechargeRefund.getProductName());
|
||||
assertNull(rechargeRefund.getOrderId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单远程查询失败时摘要整体降级留空,不阻断列表。
|
||||
*/
|
||||
@Test
|
||||
void orderSceneShouldDegradeOnRemoteFailure() {
|
||||
WalletTransactionVo payment = vo(WalletBizEnum.PAYMENT.getCode(), HardTypeEnum.COFFEE.getType());
|
||||
payment.setBizId("902");
|
||||
when(remoteHardOrderService.getByIds(anyList())).thenThrow(new RuntimeException("feign timeout"));
|
||||
|
||||
ReflectionTestUtils.invokeMethod(service, "fillSceneSummary", List.of(payment));
|
||||
|
||||
assertNull(payment.getProductName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 充值场景摘要:回填套餐名与实付/赠送金额。
|
||||
*/
|
||||
@Test
|
||||
void rechargeSceneShouldFillPackageAndPrices() {
|
||||
WalletTransactionVo recharge = vo(WalletBizEnum.RECHARGE.getCode(), null);
|
||||
recharge.setBizId("701");
|
||||
WalletRechargeEntity record = new WalletRechargeEntity();
|
||||
record.setId(701L);
|
||||
record.setPackageId(8L);
|
||||
record.setPayPrice(3000);
|
||||
record.setBonusPrice(300);
|
||||
when(walletRechargeMapper.selectBatchIds(anyList())).thenReturn(List.of(record));
|
||||
WalletRechargePackageEntity pkg = new WalletRechargePackageEntity();
|
||||
pkg.setId(8L);
|
||||
pkg.setName("30元套餐");
|
||||
when(walletRechargePackageMapper.selectBatchIds(anyList())).thenReturn(List.of(pkg));
|
||||
|
||||
ReflectionTestUtils.invokeMethod(service, "fillSceneSummary", List.of(recharge));
|
||||
|
||||
assertEquals("30元套餐", recharge.getRechargePackageName());
|
||||
assertEquals(3000, recharge.getRechargePayPrice());
|
||||
assertEquals(300, recharge.getRechargeBonusPrice());
|
||||
}
|
||||
|
||||
/**
|
||||
* 转账场景摘要:按源/目标钱包类型拼方向描述。
|
||||
*/
|
||||
@Test
|
||||
void transferSceneShouldFillDirectionDesc() {
|
||||
WalletTransactionVo transfer = vo(WalletBizEnum.TRANSFER_ACCOUNTS.getCode(), null);
|
||||
transfer.setBizId("601");
|
||||
WalletRuleTransactionEntity rule = new WalletRuleTransactionEntity();
|
||||
rule.setId(601L);
|
||||
rule.setSourceWalletId(10L);
|
||||
rule.setTargetWalletId(20L);
|
||||
when(walletRuleTransactionMapper.selectBatchIds(anyList())).thenReturn(List.of(rule));
|
||||
WalletManageEntity eatWallet = new WalletManageEntity();
|
||||
eatWallet.setId(10L);
|
||||
eatWallet.setWalletTypeId(1L);
|
||||
WalletManageEntity payWallet = new WalletManageEntity();
|
||||
payWallet.setId(20L);
|
||||
payWallet.setWalletTypeId(2L);
|
||||
when(walletManageMapper.selectBatchIds(anyList())).thenReturn(List.of(eatWallet, payWallet));
|
||||
WalletTypeEntity eatType = new WalletTypeEntity();
|
||||
eatType.setId(1L);
|
||||
eatType.setName("饭卡");
|
||||
WalletTypeEntity payType = new WalletTypeEntity();
|
||||
payType.setId(2L);
|
||||
payType.setName("消费钱包");
|
||||
when(walletTypeMapper.selectBatchIds(anyList())).thenReturn(List.of(eatType, payType));
|
||||
|
||||
ReflectionTestUtils.invokeMethod(service, "fillSceneSummary", List.of(transfer));
|
||||
|
||||
assertEquals("饭卡 → 消费钱包", transfer.getTransferDesc());
|
||||
}
|
||||
|
||||
/**
|
||||
* 非消费场景流水(退还食堂等)不触发任何摘要查询。
|
||||
*/
|
||||
@Test
|
||||
void unrelatedBizTypeShouldSkipAllSceneQueries() {
|
||||
WalletTransactionVo canteenReturn = vo(WalletBizEnum.RETURN_CANTEEN.getCode(), null);
|
||||
canteenReturn.setBizId("501");
|
||||
|
||||
ReflectionTestUtils.invokeMethod(service, "fillSceneSummary", List.of(canteenReturn));
|
||||
|
||||
verify(remoteHardOrderService, never()).getByIds(anyList());
|
||||
verify(walletRechargeMapper, never()).selectBatchIds(anyList());
|
||||
verify(walletRuleTransactionMapper, never()).selectBatchIds(anyList());
|
||||
}
|
||||
|
||||
private void invokeFillHardType(WalletTransactionEntity entity) {
|
||||
ReflectionTestUtils.invokeMethod(service, "fillHardTypeFromContext", entity);
|
||||
}
|
||||
|
||||
private WalletTransactionEntity transaction(Long walletId, String bizId, String bizType) {
|
||||
WalletTransactionEntity entity = new WalletTransactionEntity();
|
||||
entity.setWalletId(walletId);
|
||||
entity.setBizId(bizId);
|
||||
entity.setBizType(bizType);
|
||||
return entity;
|
||||
}
|
||||
|
||||
private WalletTransactionVo vo(String bizType, String hardType) {
|
||||
WalletTransactionVo vo = new WalletTransactionVo();
|
||||
vo.setBizType(bizType);
|
||||
vo.setHardType(hardType);
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user