|
|
@@ -131,7 +131,11 @@ public class FamilyPlatformPointsService {
|
|
|
refType, refId, remark);
|
|
|
}
|
|
|
|
|
|
- /** 消费家庭可用 CF 值,余额不足抛异常 */
|
|
|
+ /**
|
|
|
+ * @deprecated F-5 起消费侧(券兑换 / 提现冻结 / 商品抵扣)已全部改投统一账本 {@link CfSpendDispatchService},
|
|
|
+ * 本方法不再作为消费入口使用;家庭池仅保留发放侧(F-8 清理时标注下线)。
|
|
|
+ */
|
|
|
+ @Deprecated
|
|
|
@Transactional
|
|
|
public void spend(Long familyId, int amount, String refType, Long refId, String remark) {
|
|
|
if (amount <= 0) {
|
|
|
@@ -222,7 +226,10 @@ public class FamilyPlatformPointsService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 用家庭CF值兑换优惠券:校验 → 扣家庭池 → 发券 → 落记录
|
|
|
+ * 用个人 CF 值兑换优惠券(N3:券挂家庭,个人扣 cf_balance):校验 → 扣个人 CF → 发券到家庭 → 落记录。
|
|
|
+ *
|
|
|
+ * <p>F-5:扣减改投统一账本 {@code cf_balance}(兑换人 member 维度,幂等 refId = 券实例 ID),
|
|
|
+ * 不再扣家庭池;日限数值读 {@code service_config}(L2)。</p>
|
|
|
*/
|
|
|
@Transactional
|
|
|
public Long exchangeCouponByCf(Long familyId, Long couponId, Long refUserId) {
|
|
|
@@ -245,34 +252,30 @@ public class FamilyPlatformPointsService {
|
|
|
if (coupon.getValidUntil() != null && now.after(coupon.getValidUntil())) {
|
|
|
throw new RuntimeException("优惠券已过期");
|
|
|
}
|
|
|
+ // N3:券模板绑定家庭时仅该家庭可兑换(NULL = 全平台模板)
|
|
|
+ if (coupon.getFamilyId() != null && !coupon.getFamilyId().equals(familyId)) {
|
|
|
+ throw new RuntimeException("该优惠券仅限指定家庭使用");
|
|
|
+ }
|
|
|
|
|
|
- // 2. 家庭级日限次(cf_exchange_daily_limit,默认5)
|
|
|
- int dailyLimit = 5;
|
|
|
- try {
|
|
|
- String limitStr = sysConfigService.getValue("cf_exchange_daily_limit");
|
|
|
- if (limitStr != null && !limitStr.isEmpty()) dailyLimit = Integer.parseInt(limitStr);
|
|
|
- } catch (Exception ignored) {}
|
|
|
+ // 2. 个人级日限次(cf.spend.coupon.exchange_daily_limit,默认5;按兑换人统计)
|
|
|
+ int dailyLimit = configService.getIntConfig(CfConfigKeys.SPEND_COUPON_EXCHANGE_DAILY_LIMIT, 5);
|
|
|
Calendar cal = Calendar.getInstance();
|
|
|
cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0);
|
|
|
cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0);
|
|
|
long todayCount = exchangeRecordMapper.selectCount(
|
|
|
new LambdaQueryWrapper<FamilyPlatformExchangeRecord>()
|
|
|
- .eq(FamilyPlatformExchangeRecord::getFamilyId, familyId)
|
|
|
+ .eq(FamilyPlatformExchangeRecord::getUserId, refUserId)
|
|
|
.eq(FamilyPlatformExchangeRecord::getStatus, "completed")
|
|
|
.ge(FamilyPlatformExchangeRecord::getCreatedAt, cal.getTime()));
|
|
|
if (todayCount >= dailyLimit) {
|
|
|
- throw new RuntimeException("家庭今日CF值兑换次数已达上限(" + dailyLimit + "次)");
|
|
|
+ throw new RuntimeException("今日CF值兑换次数已达上限(" + dailyLimit + "次)");
|
|
|
}
|
|
|
|
|
|
- // 3. 单券模板日限次(cf_coupon_exchange_daily_limit,默认1)
|
|
|
- int couponDailyLimit = 1;
|
|
|
- try {
|
|
|
- String cdlStr = sysConfigService.getValue("cf_coupon_exchange_daily_limit");
|
|
|
- if (cdlStr != null && !cdlStr.isEmpty()) couponDailyLimit = Integer.parseInt(cdlStr);
|
|
|
- } catch (Exception ignored) {}
|
|
|
+ // 3. 单券模板日限次(cf.spend.coupon.coupon_daily_limit,默认1;按兑换人统计)
|
|
|
+ int couponDailyLimit = configService.getIntConfig(CfConfigKeys.SPEND_COUPON_COUPON_DAILY_LIMIT, 1);
|
|
|
long couponTodayCount = exchangeRecordMapper.selectCount(
|
|
|
new LambdaQueryWrapper<FamilyPlatformExchangeRecord>()
|
|
|
- .eq(FamilyPlatformExchangeRecord::getFamilyId, familyId)
|
|
|
+ .eq(FamilyPlatformExchangeRecord::getUserId, refUserId)
|
|
|
.eq(FamilyPlatformExchangeRecord::getCouponId, couponId)
|
|
|
.eq(FamilyPlatformExchangeRecord::getStatus, "completed")
|
|
|
.ge(FamilyPlatformExchangeRecord::getCreatedAt, cal.getTime()));
|
|
|
@@ -280,17 +283,15 @@ public class FamilyPlatformPointsService {
|
|
|
throw new RuntimeException("该优惠券今日兑换次数已达上限");
|
|
|
}
|
|
|
|
|
|
- // 4. 扣减家庭池CF值
|
|
|
- spend(familyId, pointsPrice, "coupon_exchange", couponId,
|
|
|
- "兑换优惠券: " + (coupon.getName() != null ? coupon.getName() : "优惠券"));
|
|
|
-
|
|
|
- // 5. 发券给家庭(记录券实例ID,退款时据此退还CF值)
|
|
|
+ // 4. 先发券(券实例ID作为扣减幂等 refId),再扣兑换人个人 cf_balance
|
|
|
Long familyCouponId = couponService.grantFamilyOne(familyId, couponId, "CF_EXCHANGE", "family:" + familyId);
|
|
|
if (familyCouponId == null) {
|
|
|
throw new RuntimeException("发券失败,兑换未完成");
|
|
|
}
|
|
|
+ cfSpendDispatchService.spendForUser(refUserId, pointsPrice, "coupon_exchange", familyCouponId,
|
|
|
+ "兑换优惠券: " + (coupon.getName() != null ? coupon.getName() : "优惠券"));
|
|
|
|
|
|
- // 6. 落兑换记录
|
|
|
+ // 5. 落兑换记录
|
|
|
FamilyPlatformExchangeRecord record = new FamilyPlatformExchangeRecord();
|
|
|
record.setUserId(refUserId);
|
|
|
record.setFamilyId(familyId);
|
|
|
@@ -309,28 +310,20 @@ public class FamilyPlatformPointsService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 优惠券退回时退还 CF 值:
|
|
|
+ * 优惠券退回时退还 CF 值(F-5:原路返还到兑换人个人 cf_balance,不再退家庭池):
|
|
|
* 券实例在 {@link #exchangeCouponByCf} 中有 completed 兑换记录 → 属「用户 CF 兑换而来」,
|
|
|
- * 按原消耗 cfCost 退还到家庭池;无兑换记录(平台赠送:JOIN/POPULATION/MANUAL 等)→ 不退。
|
|
|
+ * 按原消耗 cfCost 退还到兑换人本人;无兑换记录(平台赠送:JOIN/POPULATION/MANUAL 等)→ 不退。
|
|
|
*
|
|
|
- * 幂等:以 (refType=coupon_refund, refId=券实例ID) 去重,同一张券重复退只生效一次。
|
|
|
+ * 幂等:以 (member_id, type=earn, ref_type=coupon_refund, ref_id=券实例ID) 去重,同一张券重复退只生效一次。
|
|
|
*
|
|
|
* @param familyCouponId 券实例ID(family_coupon.id)
|
|
|
- * @return 实际退还的 CF 值;0 = 平台赠送券 / 记录缺失 / 已退还过
|
|
|
+ * @return 实际退还的 CF 值;0 = 平台赠送券 / 记录缺失 / 兑换人已退出家庭 / 已退还过
|
|
|
*/
|
|
|
@Transactional
|
|
|
public int refundCouponCf(Long familyCouponId) {
|
|
|
if (familyCouponId == null) {
|
|
|
return 0;
|
|
|
}
|
|
|
- // 幂等检查:同一券实例只退一次
|
|
|
- Long dup = logMapper.selectCount(
|
|
|
- new LambdaQueryWrapper<FamilyPlatformBalanceLog>()
|
|
|
- .eq(FamilyPlatformBalanceLog::getRefType, "coupon_refund")
|
|
|
- .eq(FamilyPlatformBalanceLog::getRefId, familyCouponId));
|
|
|
- if (dup != null && dup > 0) {
|
|
|
- return 0;
|
|
|
- }
|
|
|
FamilyPlatformExchangeRecord rec = exchangeRecordMapper.selectOne(
|
|
|
new LambdaQueryWrapper<FamilyPlatformExchangeRecord>()
|
|
|
.eq(FamilyPlatformExchangeRecord::getFamilyCouponId, familyCouponId)
|
|
|
@@ -349,10 +342,21 @@ public class FamilyPlatformPointsService {
|
|
|
familyCouponId, rec.getCouponId());
|
|
|
return 0;
|
|
|
}
|
|
|
- earn(rec.getFamilyId(), rec.getCfCost(), "coupon_refund", familyCouponId,
|
|
|
- "优惠券退回返还CF值: " + (rec.getCouponName() != null ? rec.getCouponName() : ""));
|
|
|
- logger.info("优惠券退回退还CF值: familyId={}, familyCouponId={}, cfCost={}",
|
|
|
- rec.getFamilyId(), familyCouponId, rec.getCfCost());
|
|
|
+ // F-5:退还到兑换人本人(原路返还),幂等由账本 (member_id, type, ref_type, ref_id) 唯一键兜底
|
|
|
+ String remark = "优惠券退回返还CF值: " + (rec.getCouponName() != null ? rec.getCouponName() : "");
|
|
|
+ try {
|
|
|
+ CfBalanceResult result = cfSpendDispatchService.refundForUser(
|
|
|
+ rec.getUserId(), rec.getCfCost(), "coupon_refund", familyCouponId, remark);
|
|
|
+ if (result != null && Boolean.TRUE.equals(result.getDuplicated())) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.warn("CF退还跳过:兑换人成员解析失败 familyCouponId={}, userId={}, err={}",
|
|
|
+ familyCouponId, rec.getUserId(), e.getMessage());
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ logger.info("优惠券退回退还CF值: userId={}, familyCouponId={}, cfCost={}",
|
|
|
+ rec.getUserId(), familyCouponId, rec.getCfCost());
|
|
|
return rec.getCfCost();
|
|
|
}
|
|
|
|