| | |
| | | @Resource |
| | | private TExchangeCodeUseRecordMapper useRecordMapper; |
| | | |
| | | /** |
| | | * 分页查询兑换码卡包列表。 |
| | | * |
| | | * @param query 查询条件 |
| | | * @return 卡包分页结果 |
| | | */ |
| | | @Override |
| | | public PageInfo<TExchangeCodePackage> pageList(ExchangeCodePackageQuery query) { |
| | | PageInfo<TExchangeCodePackage> pageInfo = new PageInfo<>(query.getPageCurr(), query.getPageSize()); |
| | |
| | | return pageInfo; |
| | | } |
| | | |
| | | /** |
| | | * 新增兑换码卡包。 |
| | | * |
| | | * @param dto 卡包保存参数 |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void savePackage(ExchangeCodePackageSaveDTO dto) { |
| | |
| | | refreshPackageStats(entity.getId()); |
| | | } |
| | | |
| | | /** |
| | | * 编辑兑换码卡包。 |
| | | * |
| | | * @param dto 卡包保存参数 |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void updatePackage(ExchangeCodePackageSaveDTO dto) { |
| | |
| | | refreshPackageStats(dto.getId()); |
| | | } |
| | | |
| | | /** |
| | | * 更新兑换码卡包启用状态。 |
| | | * |
| | | * @param id 卡包ID |
| | | * @param status 启用状态 |
| | | */ |
| | | @Override |
| | | public void updatePackageStatus(Long id, Integer status) { |
| | | if (id == null) { |
| | |
| | | this.updateById(entity); |
| | | } |
| | | |
| | | /** |
| | | * 刷新所有已过领取截止日期的兑换码卡包未绑定兑换码状态。 |
| | | */ |
| | | @Override |
| | | public void refreshExpiredPackageCodes() { |
| | | List<TExchangeCodePackage> expiredPackages = this.list(new QueryWrapper<TExchangeCodePackage>() |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 删除兑换码卡包。 |
| | | * |
| | | * @param ids 卡包ID,多个ID用英文逗号分隔 |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void deletePackage(String ids) { |
| | |
| | | codeDetailMapper.delete(new QueryWrapper<TExchangeCodeDetail>().in("package_id", idList)); |
| | | } |
| | | |
| | | /** |
| | | * 查询兑换码卡包详情。 |
| | | * |
| | | * @param id 卡包ID |
| | | * @return 卡包详情 |
| | | */ |
| | | @Override |
| | | public ExchangeCodePackageDetailVO getDetail(Long id) { |
| | | TExchangeCodePackage entity = this.getById(id); |
| | |
| | | return detailVO; |
| | | } |
| | | |
| | | /** |
| | | * 分页查询兑换码明细。 |
| | | * |
| | | * @param query 查询条件 |
| | | * @return 兑换码明细分页结果 |
| | | */ |
| | | @Override |
| | | public PageInfo<ExchangeCodeDetailVO> codePage(ExchangeCodeDetailQuery query) { |
| | | refreshExpiredPackageCodes(query.getPackageId()); |
| | |
| | | return pageInfo; |
| | | } |
| | | |
| | | /** |
| | | * 导出兑换码明细。 |
| | | * |
| | | * @param query 查询条件 |
| | | * @return 兑换码明细导出数据 |
| | | */ |
| | | @Override |
| | | public List<ExchangeCodeDetailVO> exportCodes(ExchangeCodeDetailQuery query) { |
| | | refreshExpiredPackageCodes(query.getPackageId()); |
| | | return codeDetailMapper.exportList(query); |
| | | } |
| | | |
| | | /** |
| | | * 批量失效兑换码。 |
| | | * |
| | | * @param dto 失效参数 |
| | | * @return 失效结果 |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public ExchangeCodeBatchInvalidResultVO batchInvalidate(ExchangeCodeBatchInvalidDTO dto) { |
| | |
| | | return new ExchangeCodeBatchInvalidResultVO(success, skip); |
| | | } |
| | | |
| | | /** |
| | | * 查询可下载二维码的兑换码列表。 |
| | | * |
| | | * @param packageId 卡包ID |
| | | * @param onlyUnbound 是否只查询未绑定兑换码 |
| | | * @return 可下载二维码的兑换码列表 |
| | | */ |
| | | @Override |
| | | public List<ExchangeCodeDetailVO> listDownloadableCodes(Long packageId, boolean onlyUnbound) { |
| | | ExchangeCodeDetailQuery query = new ExchangeCodeDetailQuery(); |
| | |
| | | return exportCodes(query); |
| | | } |
| | | |
| | | /** |
| | | * 查询兑换码明细。 |
| | | * |
| | | * @param id 兑换码明细ID |
| | | * @return 兑换码明细 |
| | | */ |
| | | @Override |
| | | public ExchangeCodeDetailVO getCodeDetail(Long id) { |
| | | TExchangeCodeDetail detail = codeDetailMapper.selectById(id); |
| | |
| | | return vo; |
| | | } |
| | | |
| | | /** |
| | | * 绑定兑换码。 |
| | | * |
| | | * @param dto 绑定参数 |
| | | * @param userId 用户ID |
| | | * @param phone 手机号 |
| | | * @return 绑定结果 |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public ExchangeCodeBindResultVO bindExchangeCode(ExchangeCodeBindDTO dto, Long userId, String phone) { |
| | |
| | | return resultVO; |
| | | } |
| | | |
| | | /** |
| | | * 分页查询兑换码余额流水。 |
| | | * |
| | | * @param query 查询条件 |
| | | * @return 流水分页结果 |
| | | */ |
| | | @Override |
| | | public Page<TExchangeCodeUseRecord> useRecordPage(ExchangeCodeUseRecordQuery query) { |
| | | Page<TExchangeCodeUseRecord> page = useRecordMapper.selectPage(Page.of(query.getPageCurr(), query.getPageSize()), |
| | |
| | | return page; |
| | | } |
| | | |
| | | /** |
| | | * 查询兑换码余额流水详情。 |
| | | * |
| | | * @param id 流水ID |
| | | * @param userId 用户ID |
| | | * @return 流水详情 |
| | | */ |
| | | @Override |
| | | public TExchangeCodeUseRecord useRecordDetail(Long id, Long userId) { |
| | | TExchangeCodeUseRecord record = useRecordMapper.selectOne(new QueryWrapper<TExchangeCodeUseRecord>() |
| | |
| | | return record; |
| | | } |
| | | |
| | | /** |
| | | * 查询用户兑换码余额映射。 |
| | | * |
| | | * @param userIds 用户ID集合 |
| | | * @return 用户余额映射 |
| | | */ |
| | | @Override |
| | | public Map<Long, BigDecimal> getUserExchangeCodeBalanceMap(List<Long> userIds) { |
| | | Map<Long, BigDecimal> result = new HashMap<>(); |
| | |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 扣减用户兑换码余额。 |
| | | * |
| | | * @param dto 扣减参数 |
| | | * @return 扣减结果 |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public ExchangeCodeConsumeResultVO consumeBalance(ExchangeCodeConsumeDTO dto) { |
| | |
| | | return resultVO; |
| | | } |
| | | |
| | | /** |
| | | * 退款返还用户兑换码余额。 |
| | | * |
| | | * @param dto 退款参数 |
| | | * @return 退款结果 |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public ExchangeCodeConsumeResultVO refundBalance(ExchangeCodeConsumeDTO dto) { |
| | |
| | | return resultVO; |
| | | } |
| | | |
| | | /** |
| | | * 根据兑换码明细记录的赠送比例计算本次使用金额中的赠送金额。 |
| | | * |
| | | * @param useAmount 本次使用金额 |
| | | * @param detail 兑换码明细 |
| | | * @return 本次使用金额对应的赠送金额 |
| | | */ |
| | | private BigDecimal calculateGiftUsedAmount(BigDecimal useAmount, TExchangeCodeDetail detail) { |
| | | if (useAmount == null || detail == null || detail.getGiftRatio() == null) { |
| | | return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP); |
| | |
| | | return useAmount.multiply(detail.getGiftRatio()).setScale(2, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | /** |
| | | * 查询兑换码明细当前剩余可用金额。 |
| | | * |
| | | * @param detail 兑换码明细 |
| | | * @return 剩余可用金额 |
| | | */ |
| | | private BigDecimal getDetailRemainAmount(TExchangeCodeDetail detail) { |
| | | if (detail == null || detail.getPackageSpecId() == null) { |
| | | return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP); |
| | |
| | | : remainAmount.setScale(2, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | /** |
| | | * 将保存参数转换为兑换码卡包实体。 |
| | | * |
| | | * @param dto 卡包保存参数 |
| | | * @param entity 待填充的卡包实体 |
| | | * @return 填充后的卡包实体 |
| | | */ |
| | | private TExchangeCodePackage buildPackageEntity(ExchangeCodePackageSaveDTO dto, TExchangeCodePackage entity) { |
| | | entity.setPackageName(dto.getPackageName()); |
| | | entity.setCoverPicture(dto.getCoverPicture()); |
| | |
| | | return entity; |
| | | } |
| | | |
| | | /** |
| | | * 校验兑换码卡包新增或编辑参数。 |
| | | * |
| | | * @param dto 卡包保存参数 |
| | | * @param editing 是否为编辑场景 |
| | | */ |
| | | private void validateSaveDTO(ExchangeCodePackageSaveDTO dto, boolean editing) { |
| | | if (!StringUtils.hasText(dto.getPackageName())) { |
| | | throw new ServiceException("请输入卡包名称"); |
| | |
| | | allocateDiscount(dto.getSpecList(), gift, total); |
| | | } |
| | | |
| | | /** |
| | | * 按规格小计占比分摊卡包赠送金额。 |
| | | * |
| | | * @param specList 规格列表 |
| | | * @param giftAmount 赠送金额 |
| | | * @param totalAmount 卡包总金额 |
| | | */ |
| | | private void allocateDiscount(List<ExchangeCodePackageSpecDTO> specList, BigDecimal giftAmount, BigDecimal totalAmount) { |
| | | if (giftAmount.compareTo(BigDecimal.ZERO) == 0 || totalAmount.compareTo(BigDecimal.ZERO) == 0) { |
| | | for (ExchangeCodePackageSpecDTO spec : specList) { |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 保存兑换码规格并按新增库存生成兑换码明细。 |
| | | * |
| | | * @param packageId 卡包ID |
| | | * @param specList 规格列表 |
| | | * @param inventory 当前库存 |
| | | * @param oldInventory 编辑前库存,新增场景为空 |
| | | */ |
| | | private void saveSpecsAndCodes(Long packageId, List<ExchangeCodePackageSpecDTO> specList, Integer inventory, Integer oldInventory) { |
| | | List<TExchangeCodePackageSpec> specEntities = new ArrayList<>(); |
| | | for (int i = 0; i < specList.size(); i++) { |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 计算卡包赠送金额占总金额的比例。 |
| | | * |
| | | * @param exchangePackage 兑换码卡包 |
| | | * @return 赠送金额比例 |
| | | */ |
| | | private BigDecimal calculateGiftRatio(TExchangeCodePackage exchangePackage) { |
| | | if (exchangePackage == null || exchangePackage.getTotalAmount() == null |
| | | || exchangePackage.getTotalAmount().compareTo(BigDecimal.ZERO) == 0) { |
| | |
| | | .divide(exchangePackage.getTotalAmount(), 6, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | /** |
| | | * 计算单个兑换码对应的赠送金额。 |
| | | * |
| | | * @param spec 兑换码规格 |
| | | * @param giftRatio 赠送金额比例 |
| | | * @return 单个兑换码赠送金额 |
| | | */ |
| | | private BigDecimal calculateDetailGiftAmount(TExchangeCodePackageSpec spec, BigDecimal giftRatio) { |
| | | if (spec == null || spec.getAmount() == null || giftRatio == null) { |
| | | return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP); |
| | |
| | | return spec.getAmount().multiply(giftRatio).setScale(2, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | /** |
| | | * 刷新兑换码卡包的已绑定、已失效和未绑定数量。 |
| | | * |
| | | * @param packageId 卡包ID |
| | | */ |
| | | private void refreshPackageStats(Long packageId) { |
| | | TExchangeCodePackage entity = this.getById(packageId); |
| | | if (entity == null) { |
| | |
| | | this.baseMapper.updateById(entity); |
| | | } |
| | | |
| | | /** |
| | | * 将已过领取截止日期卡包下未绑定的兑换码刷新为已失效。 |
| | | * |
| | | * @param packageId 卡包ID |
| | | */ |
| | | private void refreshExpiredPackageCodes(Long packageId) { |
| | | if (packageId == null) { |
| | | return; |
| | |
| | | refreshPackageStats(packageId); |
| | | } |
| | | |
| | | /** |
| | | * 生成兑换码卡号。 |
| | | * |
| | | * @param packageId 卡包ID |
| | | * @param seq 当前批次序号 |
| | | * @return 兑换码卡号 |
| | | */ |
| | | private String generateCode(Long packageId, int seq) { |
| | | return "CD" + packageId + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyMMddHHmmss")) + String.format("%03d", seq); |
| | | } |
| | | |
| | | /** |
| | | * 生成兑换码密码。 |
| | | * |
| | | * @return 兑换码密码 |
| | | */ |
| | | private String generatePassword() { |
| | | return UUID.randomUUID().toString().replace("-", "").substring(0, 8).toUpperCase(); |
| | | } |
| | | |
| | | /** |
| | | * 合并同一订单或同一兑换码维度的余额流水展示记录。 |
| | | * |
| | | * @param records 原始流水列表 |
| | | * @return 合并后的流水列表 |
| | | */ |
| | | private List<TExchangeCodeUseRecord> mergeUseRecords(List<TExchangeCodeUseRecord> records) { |
| | | if (records == null || records.isEmpty()) { |
| | | return records; |
| | |
| | | return new ArrayList<>(recordMap.values()); |
| | | } |
| | | |
| | | /** |
| | | * 构建余额流水合并键。 |
| | | * |
| | | * @param record 余额流水记录 |
| | | * @return 流水合并键 |
| | | */ |
| | | private String buildUseRecordMergeKey(TExchangeCodeUseRecord record) { |
| | | Long orderId = record.getOrderId() == null ? 0L : record.getOrderId(); |
| | | Long detailId = record.getDetailId() == null ? 0L : record.getDetailId(); |
| | |
| | | return detailId + "_" + changeType + "_" + refundNo; |
| | | } |
| | | |
| | | /** |
| | | * 填充单条余额流水发生后的用户当前余额。 |
| | | * |
| | | * @param record 余额流水记录 |
| | | * @param userId 用户ID |
| | | */ |
| | | private void fillUseRecordBalance(TExchangeCodeUseRecord record, Long userId) { |
| | | if (record == null || userId == null || record.getId() == null) { |
| | | return; |
| | |
| | | record.setCurrentBalance(calculateCurrentBalance(records)); |
| | | } |
| | | |
| | | /** |
| | | * 批量填充余额流水发生后的用户当前余额。 |
| | | * |
| | | * @param records 余额流水列表 |
| | | * @param userId 用户ID |
| | | */ |
| | | private void fillUseRecordBalances(List<TExchangeCodeUseRecord> records, Long userId) { |
| | | if (records == null || records.isEmpty() || userId == null) { |
| | | return; |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据流水列表计算用户当前余额。 |
| | | * |
| | | * @param records 余额流水列表 |
| | | * @return 当前余额 |
| | | */ |
| | | private BigDecimal calculateCurrentBalance(List<TExchangeCodeUseRecord> records) { |
| | | BigDecimal balance = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP); |
| | | if (records == null) { |
| | |
| | | return balance; |
| | | } |
| | | |
| | | /** |
| | | * 将单条余额流水变动应用到当前余额。 |
| | | * |
| | | * @param balance 当前余额 |
| | | * @param record 余额流水记录 |
| | | * @return 应用流水后的余额 |
| | | */ |
| | | private BigDecimal applyUseRecordChange(BigDecimal balance, TExchangeCodeUseRecord record) { |
| | | BigDecimal amount = defaultMoney(record == null ? null : record.getUsedAmount()); |
| | | Integer changeType = record == null ? null : record.getChangeType(); |
| | |
| | | return balance.add(amount).setScale(2, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | /** |
| | | * 填充余额流水前端展示字段。 |
| | | * |
| | | * @param record 余额流水记录 |
| | | */ |
| | | private void fillUseRecordView(TExchangeCodeUseRecord record) { |
| | | if (record == null) { |
| | | return; |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据使用金额和赠送比例填充流水的总使用金额与赠送使用金额。 |
| | | * |
| | | * @param record 余额流水记录 |
| | | * @param amount 使用金额 |
| | | * @param giftRatio 赠送金额比例 |
| | | */ |
| | | private void fillUseRecordAmount(TExchangeCodeUseRecord record, BigDecimal amount, BigDecimal giftRatio) { |
| | | BigDecimal usedAmount = defaultMoney(amount); |
| | | BigDecimal ratio = giftRatio == null ? BigDecimal.ZERO : giftRatio; |
| | |
| | | record.setGiftUsedAmount(giftUsedAmount); |
| | | } |
| | | |
| | | /** |
| | | * 将金额空值归零并统一保留两位小数。 |
| | | * |
| | | * @param amount 原始金额 |
| | | * @return 标准金额 |
| | | */ |
| | | private BigDecimal defaultMoney(BigDecimal amount) { |
| | | return amount == null ? BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP) |
| | | : amount.setScale(2, RoundingMode.HALF_UP); |