// 课酬与收支 6 表 model(t_course_payment / _detail / t_teacher_information / // t_teacher_courses_percentage / t_expenditure / t_expenditure_log) // —— 与 academic-model-ddl.sql「第五部分」逐列对齐。 // // 覆盖 SP:course_payment_detail_get_teacher_detail(课酬多级回退匹配), // 匹配顺序(设计 §3.4 #26): // 1. 方案两级回退:campus_id 命中 → 回退 campus_id=0(机构级); // 2. 规则三级回退:个人(teacher_id=uid)→ 持证通用(teacher_id 空 + teacher_cert=1)→ 无证通用(teacher_cert=0); // 3. grade_id / subject_id 支持 −1 通配;教龄区间用 BETWEEN(teacher_exp_min..max)。 // 教龄列保留 **decimal(6,3)**(源 float(10,3)):教龄不是金额、没有"分"的概念; // 收敛到 2 位会把 0.999 变成 1.00,使相邻两档**并列命中** ⇒ 课酬按错档位算(D7 双跑必红)。 // // 无可用方案 → 3112(apperr.ErrPayrollPlanMissing)。 // // 字段注释五维口径:语义与单位 | 枚举全量取值 | 写入时机 | 主外键/关联语义 | 安全红线。 package model import "time" // t_teacher_courses_percentage.payment_method 结算方式编码。 const ( PayrollPaymentMethodPercentage int8 = 1 // 提成结算 PayrollPaymentMethodCash int8 = 2 // 现金结算 ) // t_expenditure.type 收支类型编码。 const ( ExpenditureTypeExpense int32 = 1 // 支出 ExpenditureTypeIncome int32 = 2 // 收入 ) // CoursePayment 课酬方案表 t_course_payment(机构/校区级,按生效日期取最新)。 type CoursePayment struct { ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // 方案 ID(D3 自增)|枚举:无|写入:INSERT 自动|FK:被 course_payment_detail / classify 引用|红线:被规则引用后禁删 InstitutionID int64 `gorm:"index:idx_inst_campus_actived_effect,priority:1;column:institution_id;not null;default:0" json:"institutionId"` // 机构 ID(原 schoolid;0=所有机构;作用域列)|枚举:无|写入:创建方案时|FK:→ t_institution|红线:0 表示通用方案,scoping 时需显式放行(评审确认) CampusID int64 `gorm:"index:idx_inst_campus_actived_effect,priority:2;column:campus_id;not null;default:0" json:"campusId"` // 校区 ID(原 campusid;0=机构所有校区)|枚举:无|写入:创建方案时|FK:→ t_campus|红线:两级回退的第二级依据 Name *string `gorm:"column:name;size:200" json:"name,omitempty"` // 方案名称|枚举:无|写入:创建/编辑|FK:无|红线:展示转义 EffectDate *time.Time `gorm:"index:idx_inst_campus_actived_effect,priority:4;column:effect_date" json:"effectDate,omitempty"` // 生效日期(datetime(3);取 effect_date<=当前 最新一条)|枚举:无|写入:创建/编辑|FK:无|红线:同机构校区多方案按本列 DESC 取首条 Actived bool `gorm:"index:idx_inst_campus_actived_effect,priority:3;column:actived;not null;default:0" json:"actived"` // 是否生效(bit(1)→tinyint(1)/bool)|枚举:false/true|写入:启用/停用|FK:无|红线:仅 actived=true 参与匹配 CreateUserID *string `gorm:"column:create_user_id;size:26" json:"createUserId,omitempty"` // 创建人(ULID;原 create_userid)|枚举:无|写入:创建|FK:→ t_user(逻辑)|红线:禁止旧 int id CreateTime time.Time `gorm:"column:create_time;not null;default:CURRENT_TIMESTAMP" json:"createTime"` // 创建时间(datetime(3))|枚举:无|写入:DB 默认|FK:无|红线:勿手改 Memo *string `gorm:"column:memo;type:longtext" json:"memo,omitempty"` // 备注|枚举:无|写入:创建/编辑|FK:无|红线:脱敏 } // TableName 指定表名。 func (CoursePayment) TableName() string { return "t_course_payment" } // CoursePaymentDetail 课酬明细规则表 t_course_payment_detail(个人→持证通用→无证通用 三级回退)。 // // grade_id / subject_id 支持 −1 通配(匹配逻辑保留旧口径,设计 §3.4 #26); // percentage 为 1 位小数的百分比数字部分(如 65.5 表示 65.5%)。 type CoursePaymentDetail struct { ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // 明细 ID(D3 自增)|枚举:无|写入:INSERT 自动|FK:无|红线:试算按 id ASC 取首条命中(回退优先级靠查询顺序) CoursePaymentID int64 `gorm:"index:idx_payment;column:course_payment_id;not null" json:"coursePaymentId"` // 课酬方案 ID(原 course_paymentid)|枚举:无|写入:创建规则时|FK:→ t_course_payment.id|红线:方案停用即整组失效 GradeID int32 `gorm:"column:grade_id;not null;default:-1" json:"gradeId"` // 年级 ID(-1 所有/0 混合)|枚举:−1 通配 + 年级字典|写入:创建规则|FK:→ 教学域年级|红线:−1 通配逻辑不得写成 `= -1` 强制条件 SubjectID int32 `gorm:"column:subject_id;not null;default:-1" json:"subjectId"` // 科目 ID(-1 所有/0 混合)|枚举:−1 通配 + 科目字典|写入:创建规则|FK:→ 教学域科目|红线:同上 ClassType int32 `gorm:"index:idx_teacher_classify,priority:2;column:class_type;not null;default:0" json:"classType"` // 班级类型(对应 t_courses_classify.class_type)|枚举:班型编码|写入:创建规则|FK:弱关联 t_courses_classify.class_type|红线:匹配维度 ClassifyID int64 `gorm:"index:idx_teacher_classify,priority:3;column:classify_id;not null;default:0" json:"classifyId"` // 课程分类 ID(原 classifyid;个人配置维度)|枚举:无|写入:创建规则|FK:→ t_courses_classify.id|红线:0=不限定分类 TeacherID *string `gorm:"index:idx_teacher_classify,priority:1;column:teacher_id;size:26" json:"teacherId,omitempty"` // 老师(ULID;NULL/空=通用规则)|枚举:无|写入:个人规则时|FK:→ t_user(逻辑)|红线:三级回退的第一级判据(个人优先) Name *string `gorm:"column:name;size:300" json:"name,omitempty"` // 计算方式名称|枚举:无|写入:创建规则|FK:无|红线:展示转义 Percentage string `gorm:"column:percentage;type:decimal(5,1);not null;default:0.0" json:"percentage"` // 课酬比例(百分比数字部分,1 位小数;旧 float(10,1))|枚举:0~100 常见|写入:创建规则|FK:无|红线:精度例外列;参与计算须走整数运算(分×比例→四舍五入) MinPayment string `gorm:"column:min_payment;type:decimal(12,2);not null;default:0.00" json:"minPayment"` // 最低课酬标准(元)|枚举:无|写入:创建规则|FK:无|红线:<= max_payment(0 表示不限制) MaxPayment string `gorm:"column:max_payment;type:decimal(12,2);not null;default:0.00" json:"maxPayment"` // 最高课酬标准(元)|枚举:无|写入:创建规则|FK:无|红线:0 表示不限制 Reward string `gorm:"column:reward;type:decimal(12,2);not null;default:0.00" json:"reward"` // 额外课酬(元)|枚举:无|写入:创建规则|FK:无|红线:在比例基础上追加 TeacherCert bool `gorm:"index:idx_cert_exp,priority:1;column:teacher_cert;not null;default:0" json:"teacherCert"` // 是否持证(bit(1)→tinyint(1)/bool)|枚举:false 无证/true 持证|写入:创建规则|FK:弱关联 t_teacher_information.teacher_cert|红线:第二级回退判据(持证通用) TeacherExpMin string `gorm:"index:idx_cert_exp,priority:2;column:teacher_exp_min;type:decimal(6,3);not null;default:0.000" json:"teacherExpMin"` // 教龄下限(年;旧 float(10,3) → **保留 3 位**)|枚举:无|写入:创建规则|FK:无|红线:精度例外列;BETWEEN 匹配;**收敛到 2 位会改变区间命中集合**(0.999→1.00 ⇒ [0.000,1.000] 与 [1.000,1.999] 并列命中,课酬按错档位算) TeacherExpMax string `gorm:"index:idx_cert_exp,priority:3;column:teacher_exp_max;type:decimal(6,3);not null;default:0.000" json:"teacherExpMax"` // 教龄上限(年;**保留 3 位**,理由同 teacher_exp_min)|枚举:无|写入:创建规则|FK:无|红线:>= teacher_exp_min Actived bool `gorm:"column:actived;not null;default:0" json:"actived"` // 是否生效(bit(1)→tinyint(1)/bool)|枚举:false/true|写入:启用/停用|FK:无|红线:仅 true 参与匹配 Memo *string `gorm:"column:memo;type:longtext" json:"memo,omitempty"` // 备注|枚举:无|写入:创建/编辑|FK:无|红线:脱敏 } // TableName 指定表名。 func (CoursePaymentDetail) TableName() string { return "t_course_payment_detail" } // TeacherInformation 教师扩展信息表 t_teacher_information(资格证/教龄·自然键 teacher_id)。 // // 作用域:**无** institution_id/campus_id(教师是平台级人员属性,跨机构共享)—— // Repository 不继承 BaseRepo,越权断言在 Service 层(设计 §1.7 例外一)。 type TeacherInformation struct { TeacherID string `gorm:"column:teacher_id;primaryKey;size:26;not null" json:"teacherId"` // 老师(ULID;原 teacherid,主键)|枚举:无|写入:首次填资质时|FK:→ t_user(逻辑)|红线:一人一行 TeacherCert bool `gorm:"column:teacher_cert;not null;default:0" json:"teacherCert"` // 教师资格证(bit(1)→tinyint(1)/bool;0 无/1 有)|枚举:false/true|写入:资质维护|FK:无|红线:课酬第二级回退判据 TeacherExp *time.Time `gorm:"column:teacher_exp;type:date" json:"teacherExp,omitempty"` // 教龄起算日(date)|枚举:无|写入:资质维护|FK:无|红线:教龄年数由本列与试算日期差算出,勿存冗余年数 CreatedAt time.Time `gorm:"column:created_at;not null;default:CURRENT_TIMESTAMP" json:"createdAt"` // 创建时间(datetime(3))|枚举:无|写入:DB 默认|FK:无|红线:勿手改 UpdatedAt time.Time `gorm:"column:updated_at;not null;default:CURRENT_TIMESTAMP" json:"updatedAt"` // 更新时间(datetime(3),ON UPDATE)|枚举:无|写入:DB 自动|FK:无|红线:勿手改 } // TableName 指定表名。 func (TeacherInformation) TableName() string { return "t_teacher_information" } // TeacherCoursesPercentage 老师课酬提成表 t_teacher_courses_percentage(提成/固定两制·UK(type, teacher_id))。 // // `*_extend` 列为按年级/科目分档的 JSON/逗号扩展串,目标态**原样保留字符串**不结构化 // (保结构稳定;解析在 Service 层,设计 §11-3 待补盘点)。 type TeacherCoursesPercentage struct { ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // 记录 ID(D3 自增)|枚举:无|写入:INSERT 自动|FK:无|红线:UK(type,teacher_id) 保证一人一类型一行 Type int32 `gorm:"uniqueIndex:uk_type_teacher,priority:1;column:type;not null;default:1000" json:"type"` // 课程类型(class_type)|枚举:班型编码(1000 集体班…)|写入:配置时|FK:弱关联 t_courses_classify.class_type|红线:UK 第 1 列 PaymentMethod int8 `gorm:"column:payment_method;not null;default:1" json:"paymentMethod"` // 结算方式(1 提成结算/2 现金结算)|枚举:PayrollPaymentMethod*|写入:配置时|FK:无|红线:决定试算分支 TeacherID string `gorm:"uniqueIndex:uk_type_teacher,priority:2;column:teacher_id;size:26;not null" json:"teacherId"` // 老师(ULID;原 teacherid)|枚举:无|写入:配置时|FK:→ t_user(逻辑)|红线:UK 第 2 列 Percentage int32 `gorm:"column:percentage;not null;default:0" json:"percentage"` // 提成比例(%,整数)|枚举:0~100|写入:配置时|FK:无|红线:与 extend 分档冲突时以 extend 为准(旧口径) Cost string `gorm:"column:cost;type:decimal(12,2);not null;default:0.00" json:"cost"` // 一次课课酬(元)|枚举:无|写入:配置时|FK:无|红线:现金结算基数 AddedCost string `gorm:"column:added_cost;type:decimal(12,2);not null;default:0.00" json:"addedCost"` // 额外费用(元;比例基础上追加)|枚举:无|写入:配置时|FK:无|红线:追加项须可追溯 PercentageExtend *string `gorm:"column:percentage_extend;size:500" json:"percentageExtend,omitempty"` // 提成比例扩展(按年级/科目分档,原样保留)|枚举:旧扩展串格式|写入:配置时|FK:无|红线:**不结构化**(解析在 Service),勿在此做 JSON 校验 CostExtend *string `gorm:"column:cost_extend;size:500" json:"costExtend,omitempty"` // 固定课酬扩展(原样保留)|枚举:旧扩展串格式|写入:配置时|FK:无|红线:同上 MaxReward string `gorm:"column:max_reward;type:decimal(12,2);not null;default:0.00" json:"maxReward"` // 最大课酬(元/一次课)|枚举:无|写入:配置时|FK:无|红线:0=不限制 MaxRewardExtend *string `gorm:"column:max_reward_extend;type:longtext" json:"maxRewardExtend,omitempty"` // 最大课酬扩展(按年级分档)|枚举:旧扩展串格式|写入:配置时|FK:无|红线:同上 MinReward string `gorm:"column:min_reward;type:decimal(12,2);not null;default:0.00" json:"minReward"` // 最小课酬(元/一次课)|枚举:无|写入:配置时|FK:无|红线:0=不限制 MinRewardExtend *string `gorm:"column:min_reward_extend;size:500" json:"minRewardExtend,omitempty"` // 最小课酬扩展(原样保留)|枚举:旧扩展串格式|写入:配置时|FK:无|红线:同上 EffectiveDate *time.Time `gorm:"column:effective_date;type:date" json:"effectiveDate,omitempty"` // 课酬生效日期(date)|枚举:无|写入:配置时|FK:无|红线:取 <= 试算日期的最新一条 CreatedAt time.Time `gorm:"column:created_at;not null;default:CURRENT_TIMESTAMP" json:"createdAt"` // 创建时间(datetime(3))|枚举:无|写入:DB 默认|FK:无|红线:勿手改 UpdatedAt time.Time `gorm:"column:updated_at;not null;default:CURRENT_TIMESTAMP" json:"updatedAt"` // 更新时间(datetime(3),ON UPDATE)|枚举:无|写入:DB 自动|FK:无|红线:勿手改 } // TableName 指定表名。 func (TeacherCoursesPercentage) TableName() string { return "t_teacher_courses_percentage" } // Expenditure 收支类型字典表 t_expenditure(自引用树·支出/收入)。 type Expenditure struct { ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // 类型 ID(D3 自增)|枚举:无|写入:INSERT 自动|FK:被 expenditure_log.expenditure_id 引用|红线:被引用后禁删(改 disabled 语义由 status 缺列限制,本期只禁删) Type int32 `gorm:"index:idx_inst_type,priority:2;column:type;not null;default:1" json:"type"` // 类型(1 支出/2 收入)|枚举:ExpenditureType*|写入:创建字典项|FK:无|红线:决定报表方向(正负) ParentID int64 `gorm:"index:idx_parent;column:parent_id;not null;default:0" json:"parentId"` // 父类型 ID(原 parentid;0=顶级)|枚举:无|写入:创建字典项|FK:→ 本表 id(自引用)|红线:禁止成环 Name string `gorm:"column:name;size:500;not null" json:"name"` // 名称|枚举:无|写入:创建/编辑|FK:无|红线:展示转义 Remark *string `gorm:"column:remark;type:longtext" json:"remark,omitempty"` // 备注|枚举:无|写入:创建/编辑|FK:无|红线:脱敏 InstitutionID int64 `gorm:"index:idx_inst_type,priority:1;column:institution_id;not null;default:0" json:"institutionId"` // 机构 ID(新增作用域列;0=平台通用字典)|枚举:无|写入:创建时|FK:→ t_institution|红线:0 为平台字典(scoping 需放行 0 行,评审确认) } // TableName 指定表名。 func (Expenditure) TableName() string { return "t_expenditure" } // ExpenditureLog 收支记录表 t_expenditure_log(旧 UUID 主键 → int64 + biz_no)。 // // year/month 为报表分桶冗余列(原 year year 类型 / month tinyint),保留以避免月度报表改 SQL。 type ExpenditureLog struct { ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // 记录 ID(D3 自增)|枚举:无|写入:INSERT 自动|FK:无|红线:对外单号是 biz_no BizNo string `gorm:"uniqueIndex:uk_biz_no;column:biz_no;size:32;not null" json:"bizNo"` // 业务单号(旧 UUID 原样保留;UNIQUE)|枚举:无|写入:记账时|FK:迁移/回滚锚点|红线:不可变 InstitutionID int64 `gorm:"index:idx_inst_campus_time,priority:1;column:institution_id;not null;default:0" json:"institutionId"` // 机构 ID(原 schoolid;作用域列)|枚举:无|写入:记账时|FK:→ t_institution|红线:scoping 强依赖 CampusID int64 `gorm:"index:idx_inst_campus_time,priority:2;column:campus_id;not null;default:0" json:"campusId"` // 校区 ID(原 campusid;作用域列)|枚举:无|写入:记账时|FK:→ t_campus|红线:同上 ExpenditureID int64 `gorm:"index:idx_expenditure;column:expenditure_id;not null;default:0" json:"expenditureId"` // 收支类型 ID(原 expenditureid)|枚举:无|写入:记账时|FK:→ t_expenditure.id|红线:决定支出/收入方向 Cost string `gorm:"column:cost;type:decimal(12,2);not null;default:0.00" json:"cost"` // 费用(元;旧 float(10,3) 收敛)|枚举:无|写入:记账时|FK:无|红线:符号由 type 决定,本列存正数 CreateUserID *string `gorm:"column:create_user_id;size:26" json:"createUserId,omitempty"` // 创建人(ULID;原 create_userid)|枚举:无|写入:记账时|FK:→ t_user(逻辑)|红线:禁止旧 int id CreateTime time.Time `gorm:"index:idx_inst_campus_time,priority:3;column:create_time;not null;default:CURRENT_TIMESTAMP" json:"createTime"` // 创建时间(datetime(3))|枚举:无|写入:DB 默认|FK:无|红线:勿手改 Year *int16 `gorm:"index:idx_year_month,priority:1;column:year" json:"year,omitempty"` // 收支年份(报表分桶冗余列,原 year 类型)|枚举:如 2026|写入:记账时冗余|FK:无|红线:与 create_time 年份须一致(一致性校验) Month *int8 `gorm:"index:idx_year_month,priority:2;column:month" json:"month,omitempty"` // 收支月份(报表分桶冗余列,原 month tinyint)|枚举:1~12|写入:记账时冗余|FK:无|红线:同上 Remark *string `gorm:"column:remark;type:longtext" json:"remark,omitempty"` // 备注|枚举:无|写入:记账时|FK:无|红线:脱敏 Imgs *string `gorm:"column:imgs;type:longtext" json:"-"` // 图片地址(JSON 直存)|枚举:无|写入:记账时|FK:无|红线:**凭证可能含敏感信息,json:"-"**(按需授权下载) } // TableName 指定表名。 func (ExpenditureLog) TableName() string { return "t_expenditure_log" }