models_fund.go 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. // 资金账户与流水 6 表 model(t_user_expense / _school / _log / t_user_balance_request /
  2. // t_order_pay / t_payment_log)——与 unified/02-data-model/academic-model-ddl.sql「第一部分」逐列对齐。
  3. //
  4. // 字段注释五维口径(本项目 discipline,逐字段必填):
  5. //
  6. // 语义与单位 | 枚举全量取值 | 写入时机 | 主外键/关联语义 | 安全红线
  7. //
  8. // GORM 类型映射纪律(backend-model-field-spec.md §1.4,T2 实证漏 size → longtext 漂移):
  9. // - varchar(N) → string/*string + **必带 size:N**(N 严格等于 DDL);
  10. // - decimal(12,2) → **string + type:decimal(12,2)**(本批不直接落 money.Amount:
  11. // model 层保结构稳定,金额运算在 service 层用 money.Amount 完成,AC-02 共享内核);
  12. // - datetime(3) → time.Time/*time.Time(GORM MySQL 驱动默认 datetime(3));
  13. // - **审计时间列(create_time / created_at / updated_at,DDL 带 DEFAULT CURRENT_TIMESTAMP(3))
  14. // 的 `CreateTime`/`CreatedAt`/`UpdatedAt` 非指针字段必须带 `default:CURRENT_TIMESTAMP`**:
  15. // GORM 对 time.Time 零值会**显式**写入 0001-01-01(DB 默认值兜不住),带 default tag 后
  16. // 零值走「列不出现在 INSERT 里 → DB 默认生效」,非零值仍原样写入(历史回放/人工录入不受影响)。
  17. // 口径说明:tag 写 `CURRENT_TIMESTAMP`(不带 `(3)`)是**跨方言可执行的唯一写法**——
  18. // 纯 Go 的 glebarez/sqlite(本仓单测栈)不支持 `DEFAULT CURRENT_TIMESTAMP(3)`(语法错),
  19. // 故 MySQL 侧由 DB 默认兜底时精度为秒(DATETIME(3) 列存 .000);需要毫秒的路径用显式赋值。
  20. // DDL 文档仍写 `CURRENT_TIMESTAMP(3)`,两者差异只在「默认值精度」,不参与列对账(AC-01 口径);
  21. // - date → *time.Time + type:date(对齐 DDL 的 DATE 列);
  22. // - tinyint(1) → bool;tinyint → int8;smallint → *int16;int → int32;bigint → int64;
  23. // - LONGTEXT(JSON/HTML)→ *string + type:longtext(string 直存,规避自定义类型序列化坑)。
  24. //
  25. // 作用域说明(设计 §1.7 两条例外):
  26. // - t_user_expense / t_user_divide_into(_log) **无** institution_id/campus_id:
  27. // 全局钱包 / 全局推广体系,**不继承 BaseRepo**,越权断言在 Service 层经 MembershipChecker;
  28. // - t_user_expense_school 等带作用域列的表 → Repository 继承 BaseRepo 强制 Scoped。
  29. package model
  30. import "time"
  31. // t_user_expense_log.type 编码(设计 §10.2 权威口径,方向以 SP dump 实现为准)。
  32. //
  33. // 方向标注:+ 表示入账方向(balance 增加),− 表示出账方向;**106/206 为 ★dump 口径漂移项**
  34. // (与 P0-04 规格相反,见设计 §11-6 #2),后续 service 实现不得"按规格顺手修正"。
  35. const (
  36. ExpenseLogTypeRecharge int32 = 100 // 充值(+)
  37. ExpenseLogTypeCourseCampus int32 = 101 // 课程消费·校区(−)
  38. ExpenseLogTypeCourseSchoolA int32 = 104 // 课程消费·学校(−)
  39. ExpenseLogTypeCourseSchoolB int32 = 105 // 课程消费·学校(−)
  40. ExpenseLogTypeTransferOut int32 = 106 // 转结出(★dump 口径:+money)
  41. ExpenseLogTypeRecommend int32 = 107 // 老带新推荐(+)
  42. ExpenseLogTypeBadDebt int32 = 108 // 坏账处理(+)
  43. ExpenseLogTypeHistoryDeduct int32 = 200 // 历史扣费(−)
  44. ExpenseLogTypeCourseCampusB int32 = 201 // 课程消费·校区(−)
  45. ExpenseLogTypeCourseSchoolC int32 = 204 // 课程消费·学校(−)
  46. ExpenseLogTypeCourseSchoolD int32 = 205 // 课程消费·学校(−)
  47. ExpenseLogTypeTransferIn int32 = 206 // 转结入(★dump 口径:−money)
  48. ExpenseLogTypeRefund int32 = 207 // 余额退费(−,全额含坏账部分)
  49. ExpenseLogTypeClearBalance int32 = 208 // 余额清零(−)
  50. ExpenseLogTypeCancelRechargeAct int32 = 209 // 取消充值活动(−)
  51. )
  52. // t_user_balance_request.type / approved 编码(设计 §2「4. t_user_balance_request」)。
  53. const (
  54. BalanceRequestTypeRecharge int32 = 100 // 普通充值
  55. BalanceRequestTypeBadDebt int32 = 101 // 坏账
  56. BalanceRequestTypeRefund int32 = 202 // 退费
  57. BalanceRequestTypeCancelRecharge int32 = 203 // 取消充值活动
  58. BalanceRequestTypeTransfer int32 = 300 // 转结
  59. BalanceRequestApprovedCancelled int8 = -2 // 取消
  60. BalanceRequestApprovedRejected int8 = -1 // 不通过
  61. BalanceRequestApprovedPending int8 = 0 // 未处理
  62. BalanceRequestApprovedPassed int8 = 1 // 已通过(资金变动触发点)
  63. )
  64. // t_user_balance_request.transfer_accounts_type 收款方式编码。
  65. const (
  66. TransferAccountsNone int8 = 0 // 无
  67. TransferAccountsCash int8 = 1 // 现金
  68. TransferAccountsWechat int8 = 2 // 微信
  69. TransferAccountsAlipay int8 = 3 // 支付宝
  70. )
  71. // t_order_pay.status 状态编码。
  72. const (
  73. OrderPayStatusFailed int8 = -2 // 失败
  74. OrderPayStatusCancelled int8 = -1 // 取消
  75. OrderPayStatusUnpaid int8 = 0 // 未支付
  76. OrderPayStatusSucceeded int8 = 1 // 成功
  77. )
  78. // UserExpense 用户费用表 t_user_expense(**全局维度**账户·自然键 user_id·无作用域列)。
  79. //
  80. // 主键:user_id(canonical_user_id ULID,D1)。
  81. // 作用域:**无** institution_id/campus_id——跨机构共享的钱包视图,作用域断言由 Service 层完成
  82. // (设计 §1.7 例外一,代码评审须逐处确认"未绕过 Scoped 就代表了它不需要 scoped")。
  83. type UserExpense struct {
  84. UserID string `gorm:"column:user_id;primaryKey;size:26;not null" json:"userId"` // 用户(ULID 26 位,D1;原 userid int 经 t_legacy_mapping 重映射)|枚举:无|写入:首次资金操作 UPSERT|FK:→ t_user.canonical_user_id(无物理 FK)|红线:旧 int userid 只进映射表,禁止在此落 int
  85. Balance string `gorm:"column:balance;type:decimal(12,2);not null;default:0.00" json:"balance"` // 余额(元,±;负数=欠费)|枚举:无|写入:充值/消费/退费/清零事务内更新|FK:无|红线:只允许 `balance = balance + ?` 原子表达式,禁止读-改-写(防丢更新)
  86. Spend string `gorm:"column:spend;type:decimal(12,2);not null;default:0.00" json:"spend"` // 上课消费课费单价之和(元;只增)|枚举:无|写入:spend SP(FundService.AccumulateSpend)|FK:无|红线:本列不动 balance,勿混用为扣费
  87. TotalIncome string `gorm:"column:total_income;type:decimal(12,2);not null;default:0.00" json:"totalIncome"` // 总充值(元)|枚举:无|写入:充值+/退费-/清零不动/转结双向|FK:无|红线:报表口径列,禁止业务分支改写
  88. TotalExpenditure string `gorm:"column:total_expenditure;type:decimal(12,2);not null;default:0.00" json:"totalExpenditure"` // 总消费(元;报课学费之和+历史欠费)|枚举:无|写入:消费+/退费-/转结双向调整|FK:无|红线:与 t_user_expense_school 同名列须同事务同步
  89. TotalRecommendedAmount string `gorm:"column:total_recommended_amount;type:decimal(12,2);not null;default:0.00" json:"totalRecommendedAmount"` // 累计推荐金额(元)|枚举:无|写入:老带新双边入账累加|FK:无|红线:双边各加一次,勿重复累加(双跑重点比对项)
  90. SpecialExpenses string `gorm:"column:special_expenses;type:decimal(12,2);not null;default:0.00" json:"specialExpenses"` // 特殊费用(元;充值活动提前退费/坏账/清零累加)|枚举:无|写入:BadDebt/ClearBalance/退费差额|FK:无|红线:只增不减(对账口径)
  91. }
  92. // TableName 指定表名(GORM 默认复数化,与 DDL 的 t_ 前缀不符)。
  93. func (UserExpense) TableName() string { return "t_user_expense" }
  94. // UserExpenseSchool 用户费用表 t_user_expense_school(**校区维度**账户·多租户)。
  95. //
  96. // UK(user_id, institution_id, campus_id):旧库仅 PK id 无唯一键,目标态补 UK 以支撑
  97. // "COUNT>0 则 UPDATE 否则 INSERT"(旧 SP)→ `clause.OnConflict{DoNothing}` 的 UPSERT 语义(设计 §1.5)。
  98. // campus_id=0 表示机构级账户(跨校区汇总)。
  99. type UserExpenseSchool struct {
  100. ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // 账户记录 ID(D3 自增)|枚举:无|写入:INSERT 自动|FK:被流水/工单逻辑引用(无物理 FK)|红线:不可复用/手工指定
  101. UserID string `gorm:"uniqueIndex:uk_user_inst_campus,priority:1;column:user_id;size:26;not null" json:"userId"` // 用户(ULID;原 userid)|枚举:无|写入:账户创建时|FK:→ t_user(逻辑)|红线:不得跨界改写
  102. InstitutionID int64 `gorm:"uniqueIndex:uk_user_inst_campus,priority:2;index:idx_inst_campus,priority:1;column:institution_id;not null" json:"institutionId"` // 机构 ID(原 schoolid;作用域列)|枚举:无|写入:账户创建时锁定,永不更新|FK:→ t_institution|红线:scoping 强依赖,禁止 0 值写入
  103. CampusID int64 `gorm:"uniqueIndex:uk_user_inst_campus,priority:3;index:idx_inst_campus,priority:2;index:idx_campus_balance,priority:1;column:campus_id;not null;default:0" json:"campusId"` // 校区 ID(原 campusid;0=机构级)|枚举:无|写入:账户创建时锁定|FK:→ t_campus|红线:UK 第 3 列,改动即账户错位
  104. Balance string `gorm:"index:idx_campus_balance,priority:2;column:balance;type:decimal(12,2);not null;default:0.00" json:"balance"` // 校区余额(元,±;负数=欠费)|枚举:无|写入:资金事务内原子增减|FK:无|红线:与全局 balance 同事务更新(固定顺序:全局→校区,防死锁)
  105. Spend string `gorm:"column:spend;type:decimal(12,2);not null;default:0.00" json:"spend"` // 校区上课消费课费单价之和(元)|枚举:无|写入:spend SP|FK:无|红线:只增
  106. TotalIncome string `gorm:"column:total_income;type:decimal(12,2);not null;default:0.00" json:"totalIncome"` // 校区总充值(元)|枚举:无|写入:充值/退费/转结|FK:无|红线:与全局表同事务
  107. TotalExpenditure string `gorm:"column:total_expenditure;type:decimal(12,2);not null;default:0.00" json:"totalExpenditure"` // 校区总消费(元)|枚举:无|写入:消费/退费/转结|FK:无|红线:与全局表同事务
  108. TotalRecommendedAmount string `gorm:"column:total_recommended_amount;type:decimal(12,2);not null;default:0.00" json:"totalRecommendedAmount"` // 校区累计推荐金额(元)|枚举:无|写入:老带新结算|FK:无|红线:双边累计勿重复
  109. SpecialExpenses string `gorm:"column:special_expenses;type:decimal(12,2);not null;default:0.00" json:"specialExpenses"` // 校区特殊费用(元;坏账/清零/提前退费)|枚举:无|写入:BadDebt/ClearBalance|FK:无|红线:只增不减
  110. }
  111. // TableName 指定表名。
  112. func (UserExpenseSchool) TableName() string { return "t_user_expense_school" }
  113. // UserExpenseLog 用户费用变更流水表 t_user_expense_log(**只增不改**·幂等键 request_id+type)。
  114. //
  115. // 主键:id(D3 自增);幂等:UNIQUE(request_id, type)——Go 侧写库撞键即视为重复请求,
  116. // 返回首次结果(Repeated=true,code 仍 200,设计 §1.5)。历史行 request_id 为 NULL(MySQL 唯一键对 NULL 放行)。
  117. type UserExpenseLog struct {
  118. ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // 流水 ID(D3 自增)|枚举:无|写入:INSERT 自动|FK:无|红线:只增不改不删(审计底线)
  119. UserID string `gorm:"index:idx_user_time,priority:1;index:idx_user_type,priority:1;column:user_id;size:26;not null" json:"userId"` // 用户(ULID;原 userid)|枚举:无|写入:资金方法内|FK:→ t_user(逻辑)|红线:与账户行 user_id 必须一致
  120. Type int32 `gorm:"uniqueIndex:uk_request_type,priority:2;index:idx_user_type,priority:2;column:type;not null;default:0" json:"type"` // 类型编码(见 ExpenseLogType*:100/101/104/105/106/107/108/200/201/204/205/206/207/208/209)|枚举:全量见常量块|写入:资金方法内按动作固定传值|FK:无|红线:type 参与幂等键,禁止复用不同语义
  121. LogID *string `gorm:"index:idx_log_id;column:log_id;size:64" json:"logId,omitempty"` // 业务单号(原 logid:报课 id/缴费申请 id/结课申请 id/充值单号;无则 NULL)|枚举:无|写入:资金方法内|FK:弱关联 t_user_balance_request.biz_no 等|红线:非唯一,勿当幂等键
  122. RequestID *string `gorm:"uniqueIndex:uk_request_type,priority:1;column:request_id;size:64" json:"requestId,omitempty"` // 幂等请求号(原 SP inputRequestid;NULL=历史数据)|枚举:无|写入:调用方传入(工单 biz_no/支付单 biz_no)|FK:无|红线:与 type 组唯一键,重复提交必须命中而非重复入账
  123. Money string `gorm:"column:money;type:decimal(12,2);not null;default:0.00" json:"money"` // 变更金额(元;正负号表方向,见 ExpenseLogType* 注释)|枚举:无|写入:资金方法内|FK:无|红线:符号方向须与 type 口径一致(106/206 ★反向)
  124. BalanceAfter *string `gorm:"column:balance_after;type:decimal(12,2)" json:"balanceAfter,omitempty"` // 变更后校区余额快照(元;新增列,便于对账/双跑)|枚举:无|写入:同事务内 UPDATE 后回读|FK:无|红线:历史行为 NULL,不得回填猜测值
  125. CreateUserID *string `gorm:"column:create_user_id;size:26" json:"createUserId,omitempty"` // 创建人(ULID;原 create_userid int,0/系统 → NULL)|枚举:无|写入:Service 从 scope/user 上下文取|FK:→ t_user(逻辑)|红线:不得写入旧 int id
  126. CreateTime time.Time `gorm:"index:idx_user_time,priority:2;column:create_time;not null;default:CURRENT_TIMESTAMP" json:"createTime"` // 创建时间(datetime(3))|枚举:无|写入:DB 默认 CURRENT_TIMESTAMP(3)|FK:无|红线:只增不改(勿 UPDATE)
  127. Remark *string `gorm:"column:remark;size:1000" json:"remark,omitempty"` // 备注(原 text → varchar(1000),超长截断见迁移报告)|枚举:无|写入:资金方法内|FK:无|红线:不得写入敏感信息(手机号/证件号)
  128. }
  129. // TableName 指定表名。
  130. func (UserExpenseLog) TableName() string { return "t_user_expense_log" }
  131. // UserBalanceRequest 余额变更申请表 t_user_balance_request(充值/退费/坏账/取消活动/转结 工单·审批驱动)。
  132. //
  133. // 旧 UUID 主键处置(设计 §2.2 / §11-10 拍板):id int64 自增 + biz_no varchar(32) UNIQUE 保留旧 UUID。
  134. // 资金变动触发点:approved 由 0 → 1 的审批动作(Service 同事务内先调资金方法、后回写工单并加幂等 request_id)。
  135. type UserBalanceRequest struct {
  136. ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // 工单 ID(D3 自增)|枚举:无|写入:INSERT 自动|FK:被流水 log_id/优惠池 balance_request_id 引用|红线:对外暴露的业务单号是 biz_no,不是本列
  137. BizNo string `gorm:"uniqueIndex:uk_biz_no;column:biz_no;size:32;not null" json:"bizNo"` // 业务单号(旧 UUID 主键原样保留;UNIQUE)|枚举:无|写入:创建工单时生成/迁移搬入|FK:唯一键锚点(迁移幂等/回滚/双跑反查)|红线:不可变(割接回滚依赖)
  138. Type int32 `gorm:"index:idx_type_approved,priority:1;column:type;not null;default:100" json:"type"` // 类型(100 普通充值/101 坏账/202 退费/203 取消充值活动/300 转结)|枚举:见 BalanceRequestType*|写入:创建工单时|FK:无|红线:决定调用哪个资金方法,禁止运行期变更
  139. UserID string `gorm:"index:idx_user;column:user_id;size:26;not null;default:''" json:"userId"` // 用户(ULID;原 userid)|枚举:无|写入:创建工单时|FK:→ t_user(逻辑)|红线:与资金方法入参必须一致
  140. InstitutionID int64 `gorm:"index:idx_inst_campus_approved,priority:1;column:institution_id;not null;default:0" json:"institutionId"` // 机构 ID(原 schoolid;作用域列)|枚举:无|写入:创建工单时|FK:→ t_institution|红线:scoping 依赖,禁止 0 值落库(除平台级)
  141. CampusID int64 `gorm:"index:idx_inst_campus_approved,priority:2;column:campus_id;not null;default:0" json:"campusId"` // 校区 ID(原 campusid;作用域列)|枚举:无|写入:创建工单时|FK:→ t_campus|红线:同上
  142. ToInstitutionID *int64 `gorm:"column:to_institution_id" json:"toInstitutionId,omitempty"` // 转入机构 ID(仅 type=300 转结;新增列)|枚举:无|写入:转结申请时|FK:→ t_institution|红线:转出≠转入时须校验两侧均可见
  143. ToCampusID *int64 `gorm:"column:to_campus_id" json:"toCampusId,omitempty"` // 转入校区 ID(仅 type=300)|枚举:无|写入:转结申请时|FK:→ t_campus|红线:同上
  144. Money string `gorm:"column:money;type:decimal(12,2);not null;default:0.00" json:"money"` // 金额(元;旧 decimal(10,3) 收敛)|枚举:无|写入:创建工单时|FK:无|红线:审批通过后不可改(改须新建工单)
  145. TransferAccountsType int8 `gorm:"column:transfer_accounts_type;not null;default:0" json:"transferAccountsType"` // 收款方式(0 无/1 现金/2 微信/3 支付宝)|枚举:TransferAccounts*|写入:创建工单时|FK:无|红线:仅记账口径,不代表已对账
  146. TransferAccountsTime *time.Time `gorm:"column:transfer_accounts_time" json:"transferAccountsTime,omitempty"` // 收款时间(datetime(3))|枚举:无|写入:财务确认收款时|FK:无|红线:不得早于 request_time
  147. OrderNo *string `gorm:"column:order_no;size:50" json:"orderNo,omitempty"` // 交易单号(原 orderid;第三方流水号/报课单号)|枚举:无|写入:支付回调/财务录入|FK:弱关联 t_order_pay.pay_order|红线:非唯一
  148. RequestUserID string `gorm:"column:request_user_id;size:26;not null;default:''" json:"requestUserId"` // 申请人(ULID;原 request_userid)|枚举:无|写入:创建工单时|FK:→ t_user(逻辑)|红线:禁止旧 int id
  149. RequestTime *time.Time `gorm:"column:request_time" json:"requestTime,omitempty"` // 申请时间(datetime(3))|枚举:无|写入:提交时|FK:无|红线:只写一次
  150. ResponseUserID *string `gorm:"column:response_user_id;size:26" json:"responseUserId,omitempty"` // 审批人(ULID;原 response_userid;0→NULL)|枚举:无|写入:审批动作|FK:→ t_user(逻辑)|红线:申请人不得自审(业务规则在 Service 校验)
  151. ResponseTime *time.Time `gorm:"column:response_time" json:"responseTime,omitempty"` // 审批时间(datetime(3))|枚举:无|写入:审批动作|FK:无|红线:与 approved 同事务写
  152. ResponseMsg *string `gorm:"column:response_msg;size:1000" json:"responseMsg,omitempty"` // 审批意见|枚举:无|写入:审批动作|FK:无|红线:用户输入,禁止拼接进 SQL
  153. Approved int8 `gorm:"index:idx_inst_campus_approved,priority:3;index:idx_type_approved,priority:2;column:approved;not null;default:0" json:"approved"` // 审批状态(-2 取消/-1 不通过/0 未处理/1 已通过;原 aproved 错拼修正)|枚举:BalanceRequestApproved*|写入:审批动作|FK:无|红线:0→1 是资金变动唯一触发点,重复调用须幂等
  154. Item *string `gorm:"column:item;size:1000" json:"item,omitempty"` // 项目(报课/充值课程说明)|枚举:无|写入:创建工单时|FK:无|红线:展示用,不参与计算
  155. Imgs *string `gorm:"column:imgs;type:longtext" json:"-"` // 凭证图片(JSON 字符串直存)|枚举:无|写入:创建工单时|FK:无|红线:**API 不回显原始报文**(json:"-"),防凭证泄露
  156. Remark *string `gorm:"column:remark;size:1000" json:"remark,omitempty"` // 备注|枚举:无|写入:创建/审批|FK:无|红线:脱敏
  157. CreatedAt time.Time `gorm:"column:created_at;not null;default:CURRENT_TIMESTAMP" json:"createdAt"` // 创建时间(datetime(3))|枚举:无|写入:DB 默认|FK:无|红线:勿手改
  158. UpdatedAt time.Time `gorm:"column:updated_at;not null;default:CURRENT_TIMESTAMP" json:"updatedAt"` // 更新时间(datetime(3),ON UPDATE)|枚举:无|写入:DB 自动|FK:无|红线:勿手改
  159. }
  160. // TableName 指定表名。
  161. func (UserBalanceRequest) TableName() string { return "t_user_balance_request" }
  162. // OrderPay 支付订单表 t_order_pay(旧 UUID 主键 → int64 + biz_no)。
  163. //
  164. // 本期仅承接"创建/取消/回调"三动作;支付通道(JSAPI/W02 银联)适配层 P1(设计 §11-11)。
  165. type OrderPay struct {
  166. ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // 订单 ID(D3 自增)|枚举:无|写入:INSERT 自动|FK:被 t_payment_log.order_id 引用|红线:对外单号是 biz_no
  167. BizNo string `gorm:"uniqueIndex:uk_biz_no;column:biz_no;size:32;not null" json:"bizNo"` // 业务单号(旧 UUID 原样保留;UNIQUE;对外暴露给前端/支付网关)|枚举:无|写入:下单时|FK:幂等/回滚锚点|红线:不可变(支付网关侧已登记)
  168. Status int8 `gorm:"index:idx_status;column:status;not null;default:0" json:"status"` // 状态(-2 失败/-1 取消/0 未支付/1 成功)|枚举:OrderPayStatus*|写入:下单/取消/回调|FK:无|红线:状态机单向(成功不可回退),回调重复须幂等
  169. Type *string `gorm:"column:type;size:100" json:"type,omitempty"` // 订单类型(充值/报课…,旧字符串原样)|枚举:旧字符串枚举(不收敛)|写入:下单时|FK:无|红线:仅展示/分流,不参与金额计算
  170. InstitutionID *int64 `gorm:"column:institution_id" json:"institutionId,omitempty"` // 机构 ID(原 schoolid;作用域列;可空)|枚举:无|写入:下单时|FK:→ t_institution|红线:NULL ≠ 0,勿混判
  171. CampusID *int64 `gorm:"column:campus_id" json:"campusId,omitempty"` // 校区 ID(原 campusid;作用域列;可空)|枚举:无|写入:下单时|FK:→ t_campus|红线:NULL ≠ 0
  172. UserID *string `gorm:"index:idx_user_create,priority:1;column:user_id;size:26" json:"userId,omitempty"` // 付款用户(ULID;原 userid)|枚举:无|写入:下单时|FK:→ t_user(逻辑)|红线:家长代付场景 ≠ create_user_id
  173. PayOrder *string `gorm:"index:idx_pay_order;column:pay_order;size:50" json:"payOrder,omitempty"` // 交易单号(第三方流水号)|枚举:无|写入:支付回调|FK:弱关联 t_payment_log|红线:非唯一,索引仅用于排障
  174. PayType *string `gorm:"column:pay_type;size:10" json:"payType,omitempty"` // 支付方式(JSAPI 微信 / W02 银联 AllinPay)|枚举:JSAPI/W02|写入:下单时|FK:无|红线:新增通道须评审(对账口径)
  175. PayAccount *string `gorm:"column:pay_account;size:100" json:"-"` // 支付平台用户标识(原 pay_acct;openid/卡号)|枚举:无|写入:下单时|FK:无|红线:**json:"-" 不外泄**(openid/卡号属个人信息)
  176. PayMoney string `gorm:"column:pay_money;type:decimal(12,2);not null;default:0.00" json:"payMoney"` // 交易金额(元;旧 float(10,2) 收敛)|枚举:无|写入:下单时锁定|FK:无|红线:回调金额与订单金额不符 → 拒绝入账
  177. PayTime *time.Time `gorm:"column:pay_time" json:"payTime,omitempty"` // 支付时间(datetime(3))|枚举:无|写入:回调成功|FK:无|红线:成功态必须非 NULL
  178. PayReturnData *string `gorm:"column:pay_return_data;type:longtext" json:"-"` // 支付返回数据(JSON 直存;含敏感信息)|枚举:无|写入:回调|FK:无|红线:**API 与日志均不得回显**(json:"-")
  179. CreateUserID *string `gorm:"column:create_user_id;size:26" json:"createUserId,omitempty"` // 下单人(ULID)|枚举:无|写入:下单时|FK:→ t_user(逻辑)|红线:家长代付场景与 user_id 不同
  180. CreateTime time.Time `gorm:"index:idx_user_create,priority:2;column:create_time;not null;default:CURRENT_TIMESTAMP" json:"createTime"` // 创建时间(datetime(3))|枚举:无|写入:DB 默认|FK:无|红线:勿手改
  181. Remark *string `gorm:"column:remark;size:500" json:"remark,omitempty"` // 用户备注|枚举:无|写入:下单时|FK:无|红线:用户输入,展示需转义
  182. }
  183. // TableName 指定表名。
  184. func (OrderPay) TableName() string { return "t_order_pay" }
  185. // PaymentLog 支付网关日志表 t_payment_log(**只读历史**;旧 UUID 主键 → int64 + biz_no)。
  186. //
  187. // 本期纳管为只读:迁移保留、不新增写入(新支付回调日志写 t_audit_log + t_order_pay.pay_return_data,
  188. // 设计 §11-9)。是否重建新通道日志表列 P2。
  189. type PaymentLog struct {
  190. ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // 日志 ID(D3 自增)|枚举:无|写入:仅迁移期|FK:无|红线:只读历史,禁止新写入
  191. BizNo string `gorm:"uniqueIndex:uk_biz_no;column:biz_no;size:100;not null" json:"bizNo"` // 对外订单号(旧主键原样保留;UNIQUE)|枚举:无|写入:迁移搬入|FK:弱关联 t_order_pay.biz_no|红线:与 t_order_pay.biz_no 长度不同(100 vs 32),勿混用
  192. UserID *string `gorm:"index:idx_user;column:user_id;size:26" json:"userId,omitempty"` // 用户(ULID)|枚举:无|写入:迁移搬入|FK:→ t_user(逻辑)|红线:历史可能缺失
  193. OrderID *int64 `gorm:"index:idx_order;column:order_id" json:"orderId,omitempty"` // 订单 ID(原 order_id int → t_order_pay.id;迁移期 NULL 待回填)|枚举:无|写入:迁移回填|FK:→ t_order_pay.id|红线:NULL 期间不得据此做资金判断
  194. PaymentType int32 `gorm:"column:payment_type;not null;default:0" json:"paymentType"` // 支付网关类型(旧 int 编码原样)|枚举:旧编码(不收敛,需对照 PHP 常量)|写入:迁移搬入|FK:无|红线:语义未定前不做分支
  195. ReceiveTime *time.Time `gorm:"column:receive_time" json:"receiveTime,omitempty"` // 收到支付消息时间(datetime(3))|枚举:无|写入:迁移搬入|FK:无|红线:只读
  196. ReturnData *string `gorm:"column:return_data;type:longtext" json:"-"` // 网关原始数据(JSON 直存)|枚举:无|写入:迁移搬入|FK:无|红线:**敏感,API/日志不得回显**
  197. CreateTime time.Time `gorm:"column:create_time;not null;default:CURRENT_TIMESTAMP" json:"createTime"` // 生成时间(datetime(3))|枚举:无|写入:DB 默认/迁移|FK:无|红线:只读
  198. Result int32 `gorm:"index:idx_result;column:result;not null;default:0" json:"result"` // 处理结果(旧编码原样)|枚举:旧编码(不收敛)|写入:迁移搬入|FK:无|红线:只读
  199. }
  200. // TableName 指定表名。
  201. func (PaymentLog) TableName() string { return "t_payment_log" }