// Package model 组织域 GORM 领域模型(与 unified/02-data-model/core-model-ddl.sql 逐列对齐)。 // // 设计决策(T1 D1/D2/D3,2026-11-06 用户拍板): // - D2 机构/校区主键 = int64 自增(InstitutionID/CampusID,非 ULID;对齐 scope.Scope 作用域字段 int64)。 // - D1 引用人的外键列(Membership.CanonicalUserID / GuardianLink.GuardianID / StudentID)= string(ULID,size:26)。 // - D3 关系表(Membership/GuardianLink)主键 = int64 自增。 // - Role 为平台级字典表(不带作用域),role_code 为主键(字典码,非自增)。 // // GORM 类型映射纪律(架构规格 §3.6,T2 验收"DDL 逐表与 GORM Model 对齐"的硬要求): // - **string 必须带 size:N**(GORM 未指定 size 的 string 默认 longtext,T2 对账实证漂移): // 每个 string/*string 字段的 size 严格等于 DDL 的 varchar(N) 的 N。 // - **tinyint(1) 用 bool**:GORM bool → tinyint(1)(t_role.login_allowed / t_membership 无)。 // - 列名显式 `gorm:"column:xxx"`,不依赖默认命名。 // - 时间列 CreatedAt/UpdatedAt:GORM 自动管理(GORM v1.31 MySQL 默认 datetime(3),与 DDL 一致)。 // // 多租户作用域(架构规格 §3.5): // - Institution/Campus/Membership/GuardianLink 四表均带 institution_id / campus_id 列, // Repository 强制 scoping(跨校区越权防线);Role 是平台级字典,Repository 不继承 BaseRepo。 package model import "time" // InstitutionStatus 机构/校区状态。 const ( StatusActive = "active" // 正常 StatusDisabled = "disabled" // 停用 ) // Institution 机构表 t_institution(多租户根·品牌级)。 // // D2:主键 InstitutionID int64 自增(作用域根,scope.Scope.InstitutionID 引用此值)。 type Institution struct { InstitutionID int64 `gorm:"column:institution_id;primaryKey" json:"institutionId"` InstitutionName string `gorm:"column:institution_name;size:128;not null" json:"institutionName"` InstitutionCode *string `gorm:"column:institution_code;size:32" json:"institutionCode,omitempty"` Address *string `gorm:"column:address;size:255" json:"address,omitempty"` Phone *string `gorm:"column:phone;size:20" json:"phone,omitempty"` Status string `gorm:"column:status;size:16;not null" json:"status"` CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"` UpdatedAt time.Time `gorm:"column:updated_at" json:"updatedAt"` } // TableName 指定表名(GORM 默认 Institution → institutions,与 DDL 单数 t_institution 不符)。 func (Institution) TableName() string { return "t_institution" } // Campus 校区表 t_campus(隶属机构)。 // // D2:主键 CampusID int64 自增;InstitutionID int64(→ t_institution,作用域字段)。 // SelfSupport 为 2026-11-09 academic 域设计 §11.1 拍板 A 的增量列(AC-01 并入): // 来源旧库 `yuansiwei.t_school_campus.self_support`,供 academic 域判定活动失效范围 // (自营 → `campus_id IN (x,0)`,加盟 → `= x`;经 port.CampusReader 只读获取,不跨库)。 type Campus struct { CampusID int64 `gorm:"column:campus_id;primaryKey" json:"campusId"` InstitutionID int64 `gorm:"column:institution_id;not null" json:"institutionId"` CampusName string `gorm:"column:campus_name;size:128;not null" json:"campusName"` CampusCode *string `gorm:"column:campus_code;size:32" json:"campusCode,omitempty"` Address *string `gorm:"column:address;size:255" json:"address,omitempty"` Phone *string `gorm:"column:phone;size:20" json:"phone,omitempty"` Status string `gorm:"column:status;size:16;not null" json:"status"` SelfSupport int8 `gorm:"column:self_support;not null;default:1" json:"selfSupport"` // 是否自营校区(1 自营 / 0 加盟) CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"` UpdatedAt time.Time `gorm:"column:updated_at" json:"updatedAt"` } // TableName 指定表名(GORM 默认 Campus → campuses,与 DDL 单数 t_campus 不符)。 func (Campus) TableName() string { return "t_campus" } // Role 运营角色(7 类,ADR-007 / 2026-11-05 拍板)。 // role_code 与 Membership.Role 列取值一致(跨包引用时 import 本包常量,避免字符串散落)。 const ( RoleSuperAdmin = "super_admin" // 超级管理员(平台级,跨机构) RoleAcademic = "academic" // 教务(机构/校区级) RoleFinance = "finance" // 财务(机构/校区级) RoleTeacher = "teacher" // 老师(校区级) RoleStudent = "student" // 学生(业务级) RoleParent = "parent" // 家长(业务级,按 GuardianLink 绑定) RoleMarket = "market" // 市场(数据保留、登录禁用,login_allowed=0) ) // ScopeLevel 角色权限层级(t_role.scope_level)。 const ( ScopeLevelPlatform = "platform" // 平台级(超管,跨机构) ScopeLevelInstitution = "institution" // 机构/校区级(教务/财务/市场) ScopeLevelCampus = "campus" // 校区级(老师) ScopeLevelBusiness = "business" // 业务级(学生/家长,按报名/绑定归属) ) // RoleModel 角色字典表 t_role(平台级元数据·不带作用域)。 // // 主键 RoleCode 为 string(字典码,size:32),非自增——GORM primaryKey tag 标注(string 主键不触发自增)。 // 种子数据由 core-model-ddl.sql 内置 INSERT IGNORE;Go 侧只读字典,故无 UpdatedAt 列。 // LoginAllowed 为 bool 对齐 DDL tinyint(1)。 type RoleModel struct { RoleCode string `gorm:"column:role_code;primaryKey;size:32" json:"roleCode"` RoleName string `gorm:"column:role_name;size:32;not null" json:"roleName"` ScopeLevel string `gorm:"column:scope_level;size:16;not null" json:"scopeLevel"` LoginAllowed bool `gorm:"column:login_allowed;not null" json:"loginAllowed"` // tinyint(1) → bool Description *string `gorm:"column:description;size:255" json:"description,omitempty"` CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"` } // TableName 指定表名(GORM 默认 RoleModel → role_models,与 DDL 单数 t_role 不符; // 类型名加 Model 后缀避免与常量 Role* 混淆,表名显式锁定 t_role)。 func (RoleModel) TableName() string { return "t_role" } // MembershipStatus 成员关系状态。 const ( MembershipStatusActive = "active" // 在籍 MembershipStatusInactive = "inactive" // 离开(离职/退学/解绑,left_at 填值) ) // Membership 成员关系表 t_membership(人 ↔ 机构/校区 + 角色,多对多,多租户核心,ADR-007)。 // // D3:主键 MembershipID int64 自增;CanonicalUserID string(ULID,size:26,→ t_user)。 // UNIQUE(canonical_user_id, campus_id, role)——campus 隐含 institution。 // campus_id=0 表示平台级(如超管,跨机构)。DDL 无 updated_at 列,故 model 不设 UpdatedAt。 type Membership struct { MembershipID int64 `gorm:"column:membership_id;primaryKey" json:"membershipId"` CanonicalUserID string `gorm:"column:canonical_user_id;size:26;not null" json:"canonicalUserId"` InstitutionID int64 `gorm:"column:institution_id;not null" json:"institutionId"` // 0=平台级 CampusID int64 `gorm:"column:campus_id;not null" json:"campusId"` // 0=平台级/机构级 Role string `gorm:"column:role;size:32;not null" json:"role"` Status string `gorm:"column:status;size:16;not null" json:"status"` JoinedAt *time.Time `gorm:"column:joined_at" json:"joinedAt,omitempty"` LeftAt *time.Time `gorm:"column:left_at" json:"leftAt,omitempty"` CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"` } // TableName 指定表名(GORM 默认 Membership → memberships,与 DDL 单数 t_membership 不符)。 func (Membership) TableName() string { return "t_membership" } // GuardianRelation 家长-学生关系类型(t_guardian_link.relation,可空)。 const ( GuardianRelationFather = "father" // 父 GuardianRelationMother = "mother" // 母 GuardianRelationGuardian = "guardian" // 监护人 GuardianRelationOther = "other" // 其他 ) // GuardianLink 家长-学生绑定表 t_guardian_link(带作用域,多对多)。 // // D3:主键 LinkID int64 自增;GuardianID / StudentID 均 string(ULID,size:26,→ t_user,D1)。 // 一个学生多家长、一个家长多学生;家长只能看绑定的学生(跨家长越权防线,§3.5)。 type GuardianLink struct { LinkID int64 `gorm:"column:link_id;primaryKey" json:"linkId"` GuardianID string `gorm:"column:guardian_id;size:26;not null" json:"guardianId"` StudentID string `gorm:"column:student_id;size:26;not null" json:"studentId"` InstitutionID int64 `gorm:"column:institution_id;not null" json:"institutionId"` CampusID int64 `gorm:"column:campus_id;not null" json:"campusId"` Relation *string `gorm:"column:relation;size:32" json:"relation,omitempty"` Status string `gorm:"column:status;size:16;not null" json:"status"` CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"` UpdatedAt time.Time `gorm:"column:updated_at" json:"updatedAt"` } // TableName 指定表名(GORM 默认 GuardianLink → guardian_links,与 DDL 单数 t_guardian_link 不符)。 func (GuardianLink) TableName() string { return "t_guardian_link" } // PermissionStatus 权限点状态。 const ( PermissionStatusActive = "active" PermissionStatusDisabled = "disabled" // disabled 不进菜单树/权限集(软下线,不物理删) ) // PermissionType 权限点类型。 const ( PermissionTypeMenu = "menu" // 菜单节点:进菜单树(menuTree 下发),叶子必有 path PermissionTypeButton = "button" // 按钮节点:不进菜单树,仅供 v-perm 权限码判定 ) // Permission 权限点字典表 t_permission(平台级元数据·不带作用域,RBAC 菜单/按钮展示层,T6.5)。 // // 主键 Code 为 string(三段式 `域.资源.动作`,如 org.institution.list;对齐 PC 前端契约 // MenuNode.code / permissions[],前端按此做菜单显隐 + 按钮 v-perm 联动)。 // ParentCode 自引用构成菜单树(type=menu 的子树即菜单树;button 挂对应 menu 下,不进树)。 // 超级管理员**不依赖本表**放行(代码层全量放行,T6.5-d requireRole 中间件), // 本表只服务"非超管角色的可见菜单/按钮"。 type Permission struct { Code string `gorm:"column:code;primaryKey;size:64" json:"code"` Type string `gorm:"column:type;size:16;not null" json:"type"` Name string `gorm:"column:name;size:64;not null" json:"name"` ParentCode *string `gorm:"column:parent_code;size:64" json:"parentCode,omitempty"` Path *string `gorm:"column:path;size:255" json:"path,omitempty"` Icon *string `gorm:"column:icon;size:64" json:"icon,omitempty"` Sort int32 `gorm:"column:sort;not null" json:"sort"` Status string `gorm:"column:status;size:16;not null" json:"status"` CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"` UpdatedAt time.Time `gorm:"column:updated_at" json:"updatedAt"` } // TableName 指定表名(GORM 默认 Permission → permissions,与 DDL 单数 t_permission 不符)。 func (Permission) TableName() string { return "t_permission" } // RolePermission 角色-权限点矩阵表 t_role_permission(平台级,D3 主键自增)。 // // UNIQUE(role_code, permission_code);矩阵由超管端点配置(T6.5-e:删旧插新事务), // 种子为起步态(core-model-ddl.sql v1.1 内置 INSERT IGNORE)。 type RolePermission struct { ID int64 `gorm:"column:id;primaryKey" json:"id"` RoleCode string `gorm:"column:role_code;size:32;not null" json:"roleCode"` PermissionCode string `gorm:"column:permission_code;size:64;not null" json:"permissionCode"` CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"` } // TableName 指定表名(GORM 默认 RolePermission → role_permissions,与 DDL 单数 t_role_permission 不符)。 func (RolePermission) TableName() string { return "t_role_permission" } // AllTables 返回本域全部 model 指针切片(供装配层/集成测试合并后传入 infra/db.Migrate)。 // // 入参:无。 // 出参:[]any——org 域 7 表(Institution/Campus/Membership/Role/GuardianLink/Permission/RolePermission)。 // 说明:返回新切片,调用方可安全 append。 func AllTables() []any { return []any{ &Institution{}, &Campus{}, &Membership{}, &RoleModel{}, &GuardianLink{}, &Permission{}, &RolePermission{}, } }