// Package apperr academic(教务)域业务错误码(**31xx 段**,跨任务唯一定义点)。 // // 依据:unified/03-migration/academic-domain-design.md §10.1「错误码段位(academic = 31xx)」。 // // 设计约束: // - 31xx 段业务码全部在 **3100~3199** 内,禁止越界(单测断言,CI 门禁); // - 通用语义复用 infra/errors 的 sentinel:1001 未登录 / 1003 无权限 / 1004 作用域越界 / // 2001 参数缺失 / 2002 格式错误 / 3001 不存在 / 5002 DB 异常——**本包不重复定义**; // - 3103(可退金额不足)为 **warn 级**:不阻断流程,响应体带 badDebt 字段; // - 3114(幂等重复)**仅内部日志用**,对外 code 仍返回 200 + data.repeated=true。 // // 使用约定(Service 层): // // return nil, apperr.ErrGiftAmountShort // 纯业务错误 // return nil, infraerrors.WithCause(apperr.ErrBalanceInsufficient, err) // 需保留底层原因排障 package apperr import infraerrors "yuansiwei/unified-backend/internal/infra/errors" // academic 业务错误码常量(31xx 段,取值与设计 §10.1 逐条对应)。 const ( // CodeBalanceInsufficient 余额不足(消费/转结时可用余额 < 需要)。 CodeBalanceInsufficient = 3101 // CodeNoBalanceToRefund 账户余额为 0,不可退费(custom_refund / cleared_balance 前置校验)。 CodeNoBalanceToRefund = 3102 // CodeRefundableShort 可退金额不足,差额计坏账(**warn 级,不阻断**)。 CodeRefundableShort = 3103 // CodeCampusNotFound 校区不存在或未归属机构(跨域读 t_campus 失败 / school_id<=0)。 CodeCampusNotFound = 3104 // CodeActivityAlreadySigned 已报名该活动(signed_add 重复)。 CodeActivityAlreadySigned = 3105 // CodeRecommendLimited 老带新活动每人限参加一次(signed_add,活动 type=4)。 CodeRecommendLimited = 3106 // CodeActivitySignedAudited 活动已审核通过,不可取消(signed_cancel,type∈{2,3} 且已审核)。 CodeActivitySignedAudited = 3107 // CodeRecommendUsed 老带新已产生消费,不可取消(signed_cancel,type=4 且有 used_log)。 CodeRecommendUsed = 3108 // CodeGiftAmountShort 可用赠送金额不足(user_discount_use)。 CodeGiftAmountShort = 3109 // CodeActivityUnavailable 活动已过期或不可用(活动 status/时间窗校验)。 CodeActivityUnavailable = 3110 // CodeDivideQuotaExceeded 分账层级超限或额度超上限,**同一个码四种判据**(前端按 message 区分): // ① 建节点时层级超**业务**上限 3 层(divide_into_add,F20 硬规则,2026-11-10); // ② 建节点时层级超**结构**上限 100 层(divide_into_add,防 BFS 不终止/爆栈,脏数据才触发); // ③ 记流水时上行链路超 100 层或成环(log_add 的 BFS 兜底); // ④ 改额度时新额度 > 父节点额度(update_quota 超上线)。 // ①② 与 ③ 都叫"层级",但不是同一个概念(业务规则 vs 数据结构自保),文案不得混用。 CodeDivideQuotaExceeded = 3111 // CodePayrollPlanMissing 课酬方案未配置(get_teacher_detail 无可用方案)。 CodePayrollPlanMissing = 3112 // CodeArrearsCounteractFailed 欠费核销失败(counteract_arrears 冲突)。 CodeArrearsCounteractFailed = 3113 // CodeRepeated 幂等重复请求(**仅内部日志**;对外仍 code=200 + data.repeated=true)。 CodeRepeated = 3114 // CodeClearBalanceRejected 余额清零前置条件不满足(余额≤0 / 校区余额>总余额)。 CodeClearBalanceRejected = 3120 // CodeRecordStateInvalid 打卡/上课状态非法(record start/finish 状态机)。 CodeRecordStateInvalid = 3121 // CodeScheduleConflict 排班冲突(record generate 同 courses+start_time 重复)。 CodeScheduleConflict = 3122 // CodeBusinessParam 多参数联合校验失败(campusId<=0、金额<=0 等;单参数优先 2001/2002)。 CodeBusinessParam = 3140 // CodeActivityFull 报名人数已满(signed_add:number_sign >= number_max)。 CodeActivityFull = 3141 // CodeEnrollmentDuplicate 重复报名(同一学生在本课程已有在读报课行)。 // // 为什么单独立码而不复用 3140:§10.1 v0.1.19 明令 3140 只收"**参数之间**互相矛盾" // (句式「A 与 B 矛盾」)。"该学生已报过名"是**业务状态冲突**,不是参数矛盾; // 塞进 3140 会把 3140 变成垃圾桶(该禁令见 §10.1)。 CodeEnrollmentDuplicate = 3143 ) // 金额/申请/活动/分账/课酬/课程 业务错误的预置 sentinel(业务域直接复用,禁止散落 magic message)。 // // 文案口径:面向终端用户的中文短句,**不含 SQL/堆栈/内部编码**(脱敏纪律,架构规格 §3.3)。 var ( // ErrBalanceInsufficient 余额不足(3101)。 ErrBalanceInsufficient = infraerrors.New(CodeBalanceInsufficient, "余额不足") // ErrNoBalanceToRefund 余额为 0 不可退费(3102)。 ErrNoBalanceToRefund = infraerrors.New(CodeNoBalanceToRefund, "账户余额为 0,不可退费") // ErrRefundableShort 可退金额不足(3103,warn 级:差额转坏账,不阻断主流程)。 ErrRefundableShort = infraerrors.New(CodeRefundableShort, "可退金额不足,差额将计入坏账") // ErrCampusNotFound 校区不存在或未归属该机构(3104;跨域 port.CampusReader 失败的统一出口)。 ErrCampusNotFound = infraerrors.New(CodeCampusNotFound, "校区不存在或未归属该机构") // ErrActivityAlreadySigned 已报名该活动(3105)。 ErrActivityAlreadySigned = infraerrors.New(CodeActivityAlreadySigned, "已报名该活动") // ErrRecommendLimited 老带新每人限参加一次(3106)。 ErrRecommendLimited = infraerrors.New(CodeRecommendLimited, "老带新活动每人限参加一次") // ErrActivitySignedAudited 活动已审核通过不可取消(3107)。 ErrActivitySignedAudited = infraerrors.New(CodeActivitySignedAudited, "活动已审核通过,不可取消") // ErrRecommendUsed 老带新已产生消费不可取消(3108)。 ErrRecommendUsed = infraerrors.New(CodeRecommendUsed, "老带新已产生消费,不可取消") // ErrGiftAmountShort 可用赠送金额不足(3109)。 ErrGiftAmountShort = infraerrors.New(CodeGiftAmountShort, "可用赠送金额不足") // ErrActivityUnavailable 活动已过期或不可用(3110)。 ErrActivityUnavailable = infraerrors.New(CodeActivityUnavailable, "活动已过期或不可用") // ErrDivideQuotaExceeded 分账层级超限或额度超上限(3111;四种判据见 CodeDivideQuotaExceeded)。 ErrDivideQuotaExceeded = infraerrors.New(CodeDivideQuotaExceeded, "分账层级超限或额度超上限") // ErrPayrollPlanMissing 课酬方案未配置(3112)。 ErrPayrollPlanMissing = infraerrors.New(CodePayrollPlanMissing, "课酬方案未配置") // ErrArrearsCounteractFailed 欠费核销失败(3113)。 ErrArrearsCounteractFailed = infraerrors.New(CodeArrearsCounteractFailed, "欠费核销失败") // ErrRepeated 幂等重复请求(3114;对外 code 仍 200,仅内部日志/指标使用)。 ErrRepeated = infraerrors.New(CodeRepeated, "重复请求(幂等命中)") // ErrClearBalanceRejected 余额清零前置条件不满足(3120)。 ErrClearBalanceRejected = infraerrors.New(CodeClearBalanceRejected, "余额清零前置条件不满足") // ErrRecordStateInvalid 打卡/上课状态非法(3121)。 ErrRecordStateInvalid = infraerrors.New(CodeRecordStateInvalid, "上课状态非法") // ErrScheduleConflict 排班冲突(3122)。 ErrScheduleConflict = infraerrors.New(CodeScheduleConflict, "排班冲突(同一课程同一上课时间已存在)") // ErrBusinessParam 多参数联合校验失败(3140)。 ErrBusinessParam = infraerrors.New(CodeBusinessParam, "业务参数错误") // ErrActivityFull 报名人数已满(3141)。 ErrActivityFull = infraerrors.New(CodeActivityFull, "报名人数已满") // ErrEnrollmentDuplicate 重复报名(3143)。 ErrEnrollmentDuplicate = infraerrors.New(CodeEnrollmentDuplicate, "该学生已在本课程在读,请勿重复报名") ) // CodeLowerBound / CodeUpperBound academic 业务码段位上下界(含端点,单测与门禁断言用)。 const ( // CodeLowerBound 段位下界。 CodeLowerBound = 3100 // CodeUpperBound 段位上界。 CodeUpperBound = 3199 ) // all 本包定义的全部业务 sentinel(顺序与设计 §10.1 表格一致;返回新切片,调用方可安全 append)。 var all = []*infraerrors.AppError{ ErrBalanceInsufficient, ErrNoBalanceToRefund, ErrRefundableShort, ErrCampusNotFound, ErrActivityAlreadySigned, ErrRecommendLimited, ErrActivitySignedAudited, ErrRecommendUsed, ErrGiftAmountShort, ErrActivityUnavailable, ErrDivideQuotaExceeded, ErrPayrollPlanMissing, ErrArrearsCounteractFailed, ErrRepeated, ErrClearBalanceRejected, ErrRecordStateInvalid, ErrScheduleConflict, ErrBusinessParam, ErrActivityFull, ErrEnrollmentDuplicate, } // All 返回本包定义的 academic 业务错误全集(供单测断言段位/唯一性、供 Handler 生成错误码文档)。 // // 出参:[]*infraerrors.AppError——新切片(每次调用重新分配,调用方 append 不影响内部)。 func All() []*infraerrors.AppError { out := make([]*infraerrors.AppError, len(all)) copy(out, all) return out } // IsAcademicCode 判断错误码是否落在 academic 31xx 段内(3100~3199,含端点)。 // // 入参:code int——任意错误码。出参:true 表示属 academic 业务码段。 func IsAcademicCode(code int) bool { return code >= CodeLowerBound && code <= CodeUpperBound }