models_divide.go 8.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. // 分账域 2 表 model(t_user_divide_into / t_user_divide_into_log)——Nested Set 左右值树。
  2. //
  3. // 与 academic-model-ddl.sql「第三部分」逐列对齐;覆盖 4 个 SP:
  4. // divide_into_add(加入下线)/ divide_into_log_add(上行分成,递归→迭代 BFS)/
  5. // divide_into_log_delete(撤销/改绑)/ divide_into_update_quota(额度级联降)。
  6. //
  7. // 作用域说明(设计 §1.7 例外二 + §11.1 拍板 11-12):本域 2 表**无** institution_id/campus_id
  8. // (旧库为全局推广体系),**不继承 BaseRepo**;越权防线 = Handler 角色白名单(super_admin/academic)
  9. // + 显式数据范围参数,代码评审须在此显式标注"平台级/全局数据"。
  10. //
  11. // 字段注释五维口径:语义与单位 | 枚举全量取值 | 写入时机 | 主外键/关联语义 | 安全红线。
  12. package model
  13. import "time"
  14. // t_user_divide_into.type / t_user_divide_into_log.type 分成类型编码。
  15. const (
  16. DivideTypeOneOff int8 = 1 // 一次付清(同 user+student 只生成一条)
  17. DivideTypePerLesson int8 = 2 // 按课次(同 user+student+order_no 只生成一条)
  18. )
  19. // UserDivideInto 用户分账表 t_user_divide_into(Nested Set 左右值树·一人一节点·自然键 user_id)。
  20. //
  21. // 树不变式:root_user_id 标识所属树;left_val/right_val 为嵌套集区间(left < right);
  22. // layer 为层级(根=1)。下线 quota 不得超过上线(update_quota 级联约束,超限 → 3111)。
  23. type UserDivideInto struct {
  24. UserID string `gorm:"column:user_id;primaryKey;size:26;not null" json:"userId"` // 节点用户(ULID;原 userid,主键)|枚举:无|写入:加入分账体系时|FK:→ t_user(逻辑)|红线:一人一节点,禁止重复建节点
  25. Type int8 `gorm:"column:type;not null;default:1" json:"type"` // 分成类型(1 一次付清/2 按课次)|枚举:DivideType*|写入:加入/调整时|FK:无|红线:决定幂等维度(per student / per order),运行期改须评审
  26. Quota string `gorm:"column:quota;type:decimal(12,2);not null;default:0.00" json:"quota"` // 分成额度(元/次)|枚举:无|写入:加入时配置/update_quota|FK:无|红线:子树 quota 不得高于上线(级联降)
  27. TotalIncome string `gorm:"column:total_income;type:decimal(12,2);not null;default:0.00" json:"totalIncome"` // 分成总收入(元,累积)|枚举:无|写入:上行分成累加/撤销回滚|FK:无|红线:须与 divide_into_log.money 累计一致(撤销是回滚本列)
  28. TotalIncomePayed string `gorm:"column:total_income_payed;type:decimal(12,2);not null;default:0.00" json:"totalIncomePayed"` // 已支付的分成收入(元)|枚举:无|写入:结算打款时|FK:无|红线:<= total_income
  29. RootUserID *string `gorm:"index:idx_root;index:idx_root_left_right,priority:1;column:root_user_id;size:26" json:"rootUserId,omitempty"` // 树根用户(ULID;自身为根时=user_id)|枚举:无|写入:加入时|FK:→ 本表 user_id(自引用)|红线:★旧 SP 在 subid 已存在分支未初始化 root → NULL,Go 侧须显式取父 root
  30. ParentUserID *string `gorm:"index:idx_parent;column:parent_user_id;size:26" json:"parentUserId,omitempty"` // 上级用户(ULID;根为 NULL)|枚举:无|写入:加入时|FK:→ 本表 user_id(自引用)|红线:BFS 上行链依据,禁止成环
  31. LeftVal int32 `gorm:"index:idx_root_left_right,priority:2;column:left_val;not null;default:1" json:"leftVal"` // 左值(Nested Set)|枚举:无|写入:加入/插入新节点时右值扩张|FK:无|红线:区间扩张须同事务(left>=parentRight 全体 +2)
  32. RightVal int32 `gorm:"index:idx_root_left_right,priority:3;column:right_val;not null;default:2" json:"rightVal"` // 右值(Nested Set)|枚举:无|写入:同上|FK:无|红线:必须恒有 left < right
  33. Layer int32 `gorm:"column:layer;not null;default:1" json:"layer"` // 层级(根=1)|枚举:无|写入:加入时=父+1|FK:无|红线:深度上限 100(超限 → 3111,防递归栈溢出)
  34. CreatedAt time.Time `gorm:"column:created_at;not null;default:CURRENT_TIMESTAMP" json:"createdAt"` // 创建时间(datetime(3))|枚举:无|写入:DB 默认|FK:无|红线:勿手改
  35. UpdatedAt time.Time `gorm:"column:updated_at;not null;default:CURRENT_TIMESTAMP" json:"updatedAt"` // 更新时间(datetime(3),ON UPDATE)|枚举:无|写入:DB 自动|FK:无|红线:勿手改
  36. }
  37. // TableName 指定表名。
  38. func (UserDivideInto) TableName() string { return "t_user_divide_into" }
  39. // UserDivideIntoLog 分账明细表 t_user_divide_into_log(只增;两级幂等)。
  40. //
  41. // 幂等口径(divide_into_log_add,设计 §3.3 #22):
  42. // - type=1(一次付清):同 user+student 只生成一条(不区分 order);
  43. // - type=2(按课次) :同 user+student+order_no 只生成一条。
  44. //
  45. // 撤销语义(log_delete,设计 §3.3 #23):学生已有首次扣费上课记录 → 改 order_no 为首次 record
  46. // (保留分成);否则回滚 total_income 并物理删除该行。
  47. type UserDivideIntoLog struct {
  48. ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // 分成记录 ID(D3 自增)|枚举:无|写入:INSERT 自动|FK:无|红线:撤销路径允许物理删除(旧语义),须与 total_income 回滚同事务
  49. Type int8 `gorm:"index:idx_user_student_type,priority:3;column:type;not null;default:1" json:"type"` // 分成类型(1 一次付清/2 按课次;旧 tinyint ZEROFILL → tinyint)|枚举:DivideType*|写入:上行分成时|FK:无|红线:决定幂等维度,与节点 type 一致
  50. UserID string `gorm:"index:idx_user_student_type,priority:1;column:user_id;size:26;not null" json:"userId"` // 获得分成的用户(ULID;原 userid)|枚举:无|写入:上行分成时|FK:→ t_user_divide_into.user_id|红线:必须是分账树节点(否则不入账)
  51. StudentID string `gorm:"index:idx_user_student_type,priority:2;index:idx_student_order,priority:1;column:student_id;size:26;not null" json:"studentId"` // 报课学生(ULID;原 studentid)|枚举:无|写入:上行分成时|FK:→ t_user(逻辑)|红线:幂等键组成列
  52. OrderNo *string `gorm:"index:idx_student_order,priority:2;column:order_no;size:50" json:"orderNo,omitempty"` // 订单号(原 orderid:type=2 时=上课记录 id)|枚举:无|写入:上行分成/撤销改绑|FK:弱关联 t_courses_record.id|红线:幂等键组成列(type=2),改绑即改幂等语义,须审计
  53. Money string `gorm:"column:money;type:decimal(12,2);not null;default:0.00" json:"money"` // 分成金额(元;旧 float(10,2))|枚举:无|写入:上行分成时取节点 quota|FK:无|红线:与 total_income 累加同事务
  54. CreateTime time.Time `gorm:"index:idx_create_time;column:create_time;not null;default:CURRENT_TIMESTAMP" json:"createTime"` // 创建时间(datetime(3))|枚举:无|写入:DB 默认|FK:无|红线:勿手改(对账依据)
  55. Remark *string `gorm:"column:remark;type:longtext" json:"remark,omitempty"` // 备注(撤销改绑时写"推广分成")|枚举:无|写入:上行/撤销|FK:无|红线:脱敏
  56. }
  57. // TableName 指定表名。
  58. func (UserDivideIntoLog) TableName() string { return "t_user_divide_into_log" }