|
|
@@ -22,7 +22,8 @@ import java.util.HashMap;
|
|
|
import java.util.Map;
|
|
|
|
|
|
/**
|
|
|
- * CF 值发放途径接入服务(F-3b1:成长行为类 F07 / F08 / F09 / F10 / F11 / F12 / F13 + F19 管理员手动调整)。
|
|
|
+ * CF 值发放途径接入服务(F-3b1:成长行为类 F07 / F08 / F09 / F10 / F11 / F12 / F13 + F19 管理员手动调整;
|
|
|
+ * F-3c:问题域 / 关卡 / 任务 / 成员转让 / 分享注册 F20-F24)。
|
|
|
*
|
|
|
* <p>统一收口:所有途径一律改投 F-2 的统一入口
|
|
|
* {@link CfBalanceService#earnByRule(Long, String, Long, Map)}(读规则 → 会员等级日限/冷却/累计校验 →
|
|
|
@@ -38,6 +39,8 @@ import java.util.Map;
|
|
|
* <li>F09 测评建档 → assessment_report;F10 家庭挑战 → family_challenge;</li>
|
|
|
* <li>F11 邀请里程碑 → invite_milestone;F12 成长任务 → growth_task;</li>
|
|
|
* <li>F13 分享阅读 → share_read;F19 管理员调整 → admin_adjust。</li>
|
|
|
+ * <li>F20 问题域全部任务完成 → problem_domain;F21 关卡通关 → unlock_gate;</li>
|
|
|
+ * <li>F22 任务完成 CF → task_complete;F23 成员间转让 → cf_transfer;F24 分享奖励(推荐注册)→ referral_register。</li>
|
|
|
* </ul>
|
|
|
*
|
|
|
* <p>口径见 docs/superpowers/plans/2026-10-24-cf-value-unification-plan.md §3.2 / §三 / §七 F-3 行。</p>
|
|
|
@@ -64,11 +67,31 @@ public class CfPathwayEarnService {
|
|
|
public static final String RULE_F13 = "F13";
|
|
|
/** F19 管理员手动调整(正负任意,需家长密码校验,必须留痕 Q9) */
|
|
|
public static final String RULE_F19 = "F19";
|
|
|
+ /** F20 问题域全部任务完成奖励(数量 = problem_domain.reward_cf_value,管理端配置) */
|
|
|
+ public static final String RULE_F20 = "F20";
|
|
|
+ /** F21 关卡通关奖励(数量 = unlock_gate.reward_points,管理端配置) */
|
|
|
+ public static final String RULE_F21 = "F21";
|
|
|
+ /** F22 任务完成 CF(数量 = 任务配置 CF 值;当前来源 tasks.points,日限按会员等级 N11) */
|
|
|
+ public static final String RULE_F22 = "F22";
|
|
|
+ /** F23 成员间转让(收款方;同一事务内含转出方 spend,见 CfTransferService) */
|
|
|
+ public static final String RULE_F23 = "F23";
|
|
|
+ /** F24 分享奖励(推荐他人注册即获 CF,数量 = 规则 amount 建议 20,日限 10 次 N8) */
|
|
|
+ public static final String RULE_F24 = "F24";
|
|
|
|
|
|
/** F09 规则缺失时的兜底 refType(与迁移408 种子一致) */
|
|
|
private static final String DEFAULT_REF_TYPE_F09 = "assessment_report";
|
|
|
/** F19 规则缺失时的兜底 refType(与迁移408 种子一致) */
|
|
|
private static final String DEFAULT_REF_TYPE_F19 = "admin_adjust";
|
|
|
+ /** F20 规则缺失时的兜底 refType(与迁移408 种子一致) */
|
|
|
+ private static final String DEFAULT_REF_TYPE_F20 = "problem_domain";
|
|
|
+ /** F21 规则缺失时的兜底 refType(与迁移408 种子一致) */
|
|
|
+ private static final String DEFAULT_REF_TYPE_F21 = "unlock_gate";
|
|
|
+ /** F22 规则缺失时的兜底 refType(与迁移408 种子一致) */
|
|
|
+ private static final String DEFAULT_REF_TYPE_F22 = "task_complete";
|
|
|
+ /** F23 规则缺失时的兜底 refType(与迁移408 种子一致) */
|
|
|
+ private static final String DEFAULT_REF_TYPE_F23 = "cf_transfer";
|
|
|
+ /** F24 规则缺失时的兜底 refType(与迁移408 种子一致) */
|
|
|
+ private static final String DEFAULT_REF_TYPE_F24 = "referral_register";
|
|
|
|
|
|
@Resource
|
|
|
private CfBalanceService cfBalanceService;
|
|
|
@@ -353,6 +376,130 @@ public class CfPathwayEarnService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // ==================== F20 问题域全部任务完成 ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * F20 问题域全部任务完成奖励:数量 = {@code problem_domain.reward_cf_value}。
|
|
|
+ *
|
|
|
+ * <p>统一落到完成任务所属家庭成员的个人 CF 账本;ref_id = 问题域ID,
|
|
|
+ * 同一成员同一问题域只发一次(由 F-2 幂等键拦截)。</p>
|
|
|
+ *
|
|
|
+ * @param memberId 家庭成员ID(family_members.id)
|
|
|
+ * @param domainId 问题域ID(problem_domains.id)
|
|
|
+ * @param rewardCfValue 问题域配置的 CF 奖励数量
|
|
|
+ * @param domainName 问题域名称(流水备注)
|
|
|
+ */
|
|
|
+ public CfEarnResult earnProblemDomainCf(Long memberId, Long domainId, int rewardCfValue, String domainName) {
|
|
|
+ if (memberId == null || memberId <= 0 || domainId == null || domainId <= 0 || rewardCfValue <= 0) {
|
|
|
+ return skipped(RULE_F20, refTypeOfRule(RULE_F20, DEFAULT_REF_TYPE_F20), domainId,
|
|
|
+ "成员、问题域或奖励数量无效,未发放问题域完成奖励");
|
|
|
+ }
|
|
|
+ Map<String, Object> ctx = new HashMap<>();
|
|
|
+ ctx.put("amount", rewardCfValue);
|
|
|
+ ctx.put("domain_id", domainId);
|
|
|
+ ctx.put("remark", "完成问题域「" + (domainName == null ? "" : domainName) + "」全部任务奖励");
|
|
|
+ return cfBalanceService.earnByRule(memberId, RULE_F20, domainId, ctx);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== F21 关卡通关 ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * F21 关卡通关奖励:数量 = {@code unlock_gate.reward_points}。
|
|
|
+ *
|
|
|
+ * @param memberId 家庭成员ID(family_members.id)
|
|
|
+ * @param gateId 关卡ID(unlock_gate.id)
|
|
|
+ * @param rewardPoints 关卡配置的 CF 奖励数量
|
|
|
+ * @param gateName 关卡名称(流水备注)
|
|
|
+ */
|
|
|
+ public CfEarnResult earnUnlockGateCf(Long memberId, Long gateId, int rewardPoints, String gateName) {
|
|
|
+ if (memberId == null || memberId <= 0 || gateId == null || gateId <= 0 || rewardPoints <= 0) {
|
|
|
+ return skipped(RULE_F21, refTypeOfRule(RULE_F21, DEFAULT_REF_TYPE_F21), gateId,
|
|
|
+ "成员、关卡或奖励数量无效,未发放关卡通关奖励");
|
|
|
+ }
|
|
|
+ Map<String, Object> ctx = new HashMap<>();
|
|
|
+ ctx.put("amount", rewardPoints);
|
|
|
+ ctx.put("gate_id", gateId);
|
|
|
+ ctx.put("remark", "通关奖励: " + (gateName == null ? "" : gateName));
|
|
|
+ return cfBalanceService.earnByRule(memberId, RULE_F21, gateId, ctx);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== F22 任务完成 CF ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * F22 任务完成 CF:数量取任务配置的 CF 值。
|
|
|
+ *
|
|
|
+ * <p>当前 tasks 表尚未拆分独立 {@code cf_amount} 列,且本批次禁止新增迁移,
|
|
|
+ * 因此沿用现有任务 CF 发放来源 {@code tasks.points};规则 F22 仍负责日限与幂等。</p>
|
|
|
+ *
|
|
|
+ * @param memberId 家庭成员ID(family_members.id)
|
|
|
+ * @param taskId 任务ID(tasks.id)
|
|
|
+ * @param cfAmount 任务 CF 奖励数量
|
|
|
+ * @param taskTitle 任务标题(流水备注)
|
|
|
+ */
|
|
|
+ public CfEarnResult earnTaskCompletionCf(Long memberId, Long taskId, int cfAmount, String taskTitle) {
|
|
|
+ if (memberId == null || memberId <= 0 || taskId == null || taskId <= 0 || cfAmount <= 0) {
|
|
|
+ return skipped(RULE_F22, refTypeOfRule(RULE_F22, DEFAULT_REF_TYPE_F22), taskId,
|
|
|
+ "成员、任务或 CF 数量无效,未发放任务完成奖励");
|
|
|
+ }
|
|
|
+ Map<String, Object> ctx = new HashMap<>();
|
|
|
+ ctx.put("amount", cfAmount);
|
|
|
+ ctx.put("task_id", taskId);
|
|
|
+ ctx.put("remark", "完成任务: " + (taskTitle == null ? "" : taskTitle));
|
|
|
+ return cfBalanceService.earnByRule(memberId, RULE_F22, taskId, ctx);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== F23 成员间转让(收款方) ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * F23 成员间转让收款方发放:数量 = 实际转让金额。
|
|
|
+ *
|
|
|
+ * <p>转出方扣减由 {@link CfBalanceService#spend(Long, int, String, Long, String)} 完成;
|
|
|
+ * 两侧共用同一 transfer refId,并由 {@code CfTransferService} 的同一事务保证原子性。</p>
|
|
|
+ *
|
|
|
+ * @param toMemberId 收款方家庭成员ID
|
|
|
+ * @param transferRefId 转让流水ID(幂等标识)
|
|
|
+ * @param transferAmount 转让金额
|
|
|
+ */
|
|
|
+ public CfEarnResult earnMemberTransferCf(Long toMemberId, Long transferRefId, int transferAmount) {
|
|
|
+ if (toMemberId == null || toMemberId <= 0 || transferRefId == null || transferRefId <= 0 || transferAmount <= 0) {
|
|
|
+ return skipped(RULE_F23, refTypeOfRule(RULE_F23, DEFAULT_REF_TYPE_F23), transferRefId,
|
|
|
+ "收款成员、转让标识或金额无效,未发放转让 CF 值");
|
|
|
+ }
|
|
|
+ Map<String, Object> ctx = new HashMap<>();
|
|
|
+ ctx.put("amount", transferAmount);
|
|
|
+ ctx.put("transfer_ref_id", transferRefId);
|
|
|
+ ctx.put("remark", "收到成员转让 CF 值");
|
|
|
+ return cfBalanceService.earnByRule(toMemberId, RULE_F23, transferRefId, ctx);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * F23 转出方 spend 流水使用的 refType;与规则 F23 的 dedup_ref_type 保持一致。
|
|
|
+ */
|
|
|
+ public String transferRefType() {
|
|
|
+ return refTypeOfRule(RULE_F23, DEFAULT_REF_TYPE_F23);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== F24 分享奖励(推荐注册) ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * F24 分享奖励:推荐人成功绑定被推荐人后发放,数量由规则固定值决定(建议 20 CF/人,日限 10 次)。
|
|
|
+ *
|
|
|
+ * <p>ref_id = 被推荐用户ID,同一推荐人针对同一被推荐用户只发一次。</p>
|
|
|
+ *
|
|
|
+ * @param referrerMemberId 推荐人家庭成员ID
|
|
|
+ * @param referredUserId 被推荐用户ID(users.id)
|
|
|
+ */
|
|
|
+ public CfEarnResult earnReferralRegistrationCf(Long referrerMemberId, Long referredUserId) {
|
|
|
+ if (referrerMemberId == null || referrerMemberId <= 0 || referredUserId == null || referredUserId <= 0) {
|
|
|
+ return skipped(RULE_F24, refTypeOfRule(RULE_F24, DEFAULT_REF_TYPE_F24), referredUserId,
|
|
|
+ "推荐人或被推荐用户无效,未发放分享奖励");
|
|
|
+ }
|
|
|
+ Map<String, Object> ctx = new HashMap<>();
|
|
|
+ ctx.put("referred_user_id", referredUserId);
|
|
|
+ ctx.put("remark", "分享奖励:推荐新人注册");
|
|
|
+ return cfBalanceService.earnByRule(referrerMemberId, RULE_F24, referredUserId, ctx);
|
|
|
+ }
|
|
|
+
|
|
|
// ==================== 工具方法 ====================
|
|
|
|
|
|
/** 规则 refType:取 dedup_ref_type,规则缺失时用兜底值 */
|