models.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. // Package model 组织域 GORM 领域模型(与 unified/02-data-model/core-model-ddl.sql 逐列对齐)。
  2. //
  3. // 设计决策(T1 D1/D2/D3,2026-11-06 用户拍板):
  4. // - D2 机构/校区主键 = int64 自增(InstitutionID/CampusID,非 ULID;对齐 scope.Scope 作用域字段 int64)。
  5. // - D1 引用人的外键列(Membership.CanonicalUserID / GuardianLink.GuardianID / StudentID)= string(ULID,size:26)。
  6. // - D3 关系表(Membership/GuardianLink)主键 = int64 自增。
  7. // - Role 为平台级字典表(不带作用域),role_code 为主键(字典码,非自增)。
  8. //
  9. // GORM 类型映射纪律(架构规格 §3.6,T2 验收"DDL 逐表与 GORM Model 对齐"的硬要求):
  10. // - **string 必须带 size:N**(GORM 未指定 size 的 string 默认 longtext,T2 对账实证漂移):
  11. // 每个 string/*string 字段的 size 严格等于 DDL 的 varchar(N) 的 N。
  12. // - **tinyint(1) 用 bool**:GORM bool → tinyint(1)(t_role.login_allowed / t_membership 无)。
  13. // - 列名显式 `gorm:"column:xxx"`,不依赖默认命名。
  14. // - 时间列 CreatedAt/UpdatedAt:GORM 自动管理(GORM v1.31 MySQL 默认 datetime(3),与 DDL 一致)。
  15. //
  16. // 多租户作用域(架构规格 §3.5):
  17. // - Institution/Campus/Membership/GuardianLink 四表均带 institution_id / campus_id 列,
  18. // Repository 强制 scoping(跨校区越权防线);Role 是平台级字典,Repository 不继承 BaseRepo。
  19. package model
  20. import "time"
  21. // InstitutionStatus 机构/校区状态。
  22. const (
  23. StatusActive = "active" // 正常
  24. StatusDisabled = "disabled" // 停用
  25. )
  26. // Institution 机构表 t_institution(多租户根·品牌级)。
  27. //
  28. // D2:主键 InstitutionID int64 自增(作用域根,scope.Scope.InstitutionID 引用此值)。
  29. type Institution struct {
  30. InstitutionID int64 `gorm:"column:institution_id;primaryKey" json:"institutionId"`
  31. InstitutionName string `gorm:"column:institution_name;size:128;not null" json:"institutionName"`
  32. InstitutionCode *string `gorm:"column:institution_code;size:32" json:"institutionCode,omitempty"`
  33. Address *string `gorm:"column:address;size:255" json:"address,omitempty"`
  34. Phone *string `gorm:"column:phone;size:20" json:"phone,omitempty"`
  35. Status string `gorm:"column:status;size:16;not null" json:"status"`
  36. CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"`
  37. UpdatedAt time.Time `gorm:"column:updated_at" json:"updatedAt"`
  38. }
  39. // TableName 指定表名(GORM 默认 Institution → institutions,与 DDL 单数 t_institution 不符)。
  40. func (Institution) TableName() string { return "t_institution" }
  41. // Campus 校区表 t_campus(隶属机构)。
  42. //
  43. // D2:主键 CampusID int64 自增;InstitutionID int64(→ t_institution,作用域字段)。
  44. // SelfSupport 为 2026-11-09 academic 域设计 §11.1 拍板 A 的增量列(AC-01 并入):
  45. // 来源旧库 `yuansiwei.t_school_campus.self_support`,供 academic 域判定活动失效范围
  46. // (自营 → `campus_id IN (x,0)`,加盟 → `= x`;经 port.CampusReader 只读获取,不跨库)。
  47. type Campus struct {
  48. CampusID int64 `gorm:"column:campus_id;primaryKey" json:"campusId"`
  49. InstitutionID int64 `gorm:"column:institution_id;not null" json:"institutionId"`
  50. CampusName string `gorm:"column:campus_name;size:128;not null" json:"campusName"`
  51. CampusCode *string `gorm:"column:campus_code;size:32" json:"campusCode,omitempty"`
  52. Address *string `gorm:"column:address;size:255" json:"address,omitempty"`
  53. Phone *string `gorm:"column:phone;size:20" json:"phone,omitempty"`
  54. Status string `gorm:"column:status;size:16;not null" json:"status"`
  55. SelfSupport bool `gorm:"column:self_support;not null;default:1" json:"selfSupport"` // 是否自营校区(true 自营 / false 加盟;tinyint(1)↔bool,迁移源 bit(1))
  56. CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"`
  57. UpdatedAt time.Time `gorm:"column:updated_at" json:"updatedAt"`
  58. }
  59. // TableName 指定表名(GORM 默认 Campus → campuses,与 DDL 单数 t_campus 不符)。
  60. func (Campus) TableName() string { return "t_campus" }
  61. // Role 运营角色(7 类,ADR-007 / 2026-11-05 拍板)。
  62. // role_code 与 Membership.Role 列取值一致(跨包引用时 import 本包常量,避免字符串散落)。
  63. const (
  64. RoleSuperAdmin = "super_admin" // 超级管理员(平台级,跨机构)
  65. RoleAcademic = "academic" // 教务(机构/校区级)
  66. RoleFinance = "finance" // 财务(机构/校区级)
  67. RoleTeacher = "teacher" // 老师(校区级)
  68. RoleStudent = "student" // 学生(业务级)
  69. RoleParent = "parent" // 家长(业务级,按 GuardianLink 绑定)
  70. RoleMarket = "market" // 市场(数据保留、登录禁用,login_allowed=0)
  71. )
  72. // ScopeLevel 角色权限层级(t_role.scope_level)。
  73. const (
  74. ScopeLevelPlatform = "platform" // 平台级(超管,跨机构)
  75. ScopeLevelInstitution = "institution" // 机构/校区级(教务/财务/市场)
  76. ScopeLevelCampus = "campus" // 校区级(老师)
  77. ScopeLevelBusiness = "business" // 业务级(学生/家长,按报名/绑定归属)
  78. )
  79. // RoleModel 角色字典表 t_role(平台级元数据·不带作用域)。
  80. //
  81. // 主键 RoleCode 为 string(字典码,size:32),非自增——GORM primaryKey tag 标注(string 主键不触发自增)。
  82. // 种子数据由 core-model-ddl.sql 内置 INSERT IGNORE;Go 侧只读字典,故无 UpdatedAt 列。
  83. // LoginAllowed 为 bool 对齐 DDL tinyint(1)。
  84. type RoleModel struct {
  85. RoleCode string `gorm:"column:role_code;primaryKey;size:32" json:"roleCode"`
  86. RoleName string `gorm:"column:role_name;size:32;not null" json:"roleName"`
  87. ScopeLevel string `gorm:"column:scope_level;size:16;not null" json:"scopeLevel"`
  88. LoginAllowed bool `gorm:"column:login_allowed;not null" json:"loginAllowed"` // tinyint(1) → bool
  89. Description *string `gorm:"column:description;size:255" json:"description,omitempty"`
  90. CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"`
  91. }
  92. // TableName 指定表名(GORM 默认 RoleModel → role_models,与 DDL 单数 t_role 不符;
  93. // 类型名加 Model 后缀避免与常量 Role* 混淆,表名显式锁定 t_role)。
  94. func (RoleModel) TableName() string { return "t_role" }
  95. // MembershipStatus 成员关系状态。
  96. const (
  97. MembershipStatusActive = "active" // 在籍
  98. MembershipStatusInactive = "inactive" // 离开(离职/退学/解绑,left_at 填值)
  99. )
  100. // Membership 成员关系表 t_membership(人 ↔ 机构/校区 + 角色,多对多,多租户核心,ADR-007)。
  101. //
  102. // D3:主键 MembershipID int64 自增;CanonicalUserID string(ULID,size:26,→ t_user)。
  103. // UNIQUE(canonical_user_id, campus_id, role)——campus 隐含 institution。
  104. // campus_id=0 表示平台级(如超管,跨机构)。DDL 无 updated_at 列,故 model 不设 UpdatedAt。
  105. type Membership struct {
  106. MembershipID int64 `gorm:"column:membership_id;primaryKey" json:"membershipId"`
  107. CanonicalUserID string `gorm:"column:canonical_user_id;size:26;not null" json:"canonicalUserId"`
  108. InstitutionID int64 `gorm:"column:institution_id;not null" json:"institutionId"` // 0=平台级
  109. CampusID int64 `gorm:"column:campus_id;not null" json:"campusId"` // 0=平台级/机构级
  110. Role string `gorm:"column:role;size:32;not null" json:"role"`
  111. Status string `gorm:"column:status;size:16;not null" json:"status"`
  112. JoinedAt *time.Time `gorm:"column:joined_at" json:"joinedAt,omitempty"`
  113. LeftAt *time.Time `gorm:"column:left_at" json:"leftAt,omitempty"`
  114. CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"`
  115. }
  116. // TableName 指定表名(GORM 默认 Membership → memberships,与 DDL 单数 t_membership 不符)。
  117. func (Membership) TableName() string { return "t_membership" }
  118. // GuardianRelation 家长-学生关系类型(t_guardian_link.relation,可空)。
  119. const (
  120. GuardianRelationFather = "father" // 父
  121. GuardianRelationMother = "mother" // 母
  122. GuardianRelationGuardian = "guardian" // 监护人
  123. GuardianRelationOther = "other" // 其他
  124. )
  125. // GuardianLink 家长-学生绑定表 t_guardian_link(带作用域,多对多)。
  126. //
  127. // D3:主键 LinkID int64 自增;GuardianID / StudentID 均 string(ULID,size:26,→ t_user,D1)。
  128. // 一个学生多家长、一个家长多学生;家长只能看绑定的学生(跨家长越权防线,§3.5)。
  129. type GuardianLink struct {
  130. LinkID int64 `gorm:"column:link_id;primaryKey" json:"linkId"`
  131. GuardianID string `gorm:"column:guardian_id;size:26;not null" json:"guardianId"`
  132. StudentID string `gorm:"column:student_id;size:26;not null" json:"studentId"`
  133. InstitutionID int64 `gorm:"column:institution_id;not null" json:"institutionId"`
  134. CampusID int64 `gorm:"column:campus_id;not null" json:"campusId"`
  135. Relation *string `gorm:"column:relation;size:32" json:"relation,omitempty"`
  136. Status string `gorm:"column:status;size:16;not null" json:"status"`
  137. CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"`
  138. UpdatedAt time.Time `gorm:"column:updated_at" json:"updatedAt"`
  139. }
  140. // TableName 指定表名(GORM 默认 GuardianLink → guardian_links,与 DDL 单数 t_guardian_link 不符)。
  141. func (GuardianLink) TableName() string { return "t_guardian_link" }
  142. // PermissionStatus 权限点状态。
  143. const (
  144. PermissionStatusActive = "active"
  145. PermissionStatusDisabled = "disabled" // disabled 不进菜单树/权限集(软下线,不物理删)
  146. )
  147. // PermissionType 权限点类型。
  148. const (
  149. PermissionTypeMenu = "menu" // 菜单节点:进菜单树(menuTree 下发),叶子必有 path
  150. PermissionTypeButton = "button" // 按钮节点:不进菜单树,仅供 v-perm 权限码判定
  151. )
  152. // Permission 权限点字典表 t_permission(平台级元数据·不带作用域,RBAC 菜单/按钮展示层,T6.5)。
  153. //
  154. // 主键 Code 为 string(三段式 `域.资源.动作`,如 org.institution.list;对齐 PC 前端契约
  155. // MenuNode.code / permissions[],前端按此做菜单显隐 + 按钮 v-perm 联动)。
  156. // ParentCode 自引用构成菜单树(type=menu 的子树即菜单树;button 挂对应 menu 下,不进树)。
  157. // 超级管理员**不依赖本表**放行(代码层全量放行,T6.5-d requireRole 中间件),
  158. // 本表只服务"非超管角色的可见菜单/按钮"。
  159. type Permission struct {
  160. Code string `gorm:"column:code;primaryKey;size:64" json:"code"`
  161. Type string `gorm:"column:type;size:16;not null" json:"type"`
  162. Name string `gorm:"column:name;size:64;not null" json:"name"`
  163. ParentCode *string `gorm:"column:parent_code;size:64" json:"parentCode,omitempty"`
  164. Path *string `gorm:"column:path;size:255" json:"path,omitempty"`
  165. Icon *string `gorm:"column:icon;size:64" json:"icon,omitempty"`
  166. Sort int32 `gorm:"column:sort;not null" json:"sort"`
  167. Status string `gorm:"column:status;size:16;not null" json:"status"`
  168. CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"`
  169. UpdatedAt time.Time `gorm:"column:updated_at" json:"updatedAt"`
  170. }
  171. // TableName 指定表名(GORM 默认 Permission → permissions,与 DDL 单数 t_permission 不符)。
  172. func (Permission) TableName() string { return "t_permission" }
  173. // RolePermission 角色-权限点矩阵表 t_role_permission(平台级,D3 主键自增)。
  174. //
  175. // UNIQUE(role_code, permission_code);矩阵由超管端点配置(T6.5-e:删旧插新事务),
  176. // 种子为起步态(core-model-ddl.sql v1.1 内置 INSERT IGNORE)。
  177. type RolePermission struct {
  178. ID int64 `gorm:"column:id;primaryKey" json:"id"`
  179. RoleCode string `gorm:"column:role_code;size:32;not null" json:"roleCode"`
  180. PermissionCode string `gorm:"column:permission_code;size:64;not null" json:"permissionCode"`
  181. CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"`
  182. }
  183. // TableName 指定表名(GORM 默认 RolePermission → role_permissions,与 DDL 单数 t_role_permission 不符)。
  184. func (RolePermission) TableName() string { return "t_role_permission" }
  185. // AllTables 返回本域全部 model 指针切片(供装配层/集成测试合并后传入 infra/db.Migrate)。
  186. //
  187. // 入参:无。
  188. // 出参:[]any——org 域 7 表(Institution/Campus/Membership/Role/GuardianLink/Permission/RolePermission)。
  189. // 说明:返回新切片,调用方可安全 append。
  190. func AllTables() []any {
  191. return []any{
  192. &Institution{},
  193. &Campus{},
  194. &Membership{},
  195. &RoleModel{},
  196. &GuardianLink{},
  197. &Permission{},
  198. &RolePermission{},
  199. }
  200. }