| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- // 课程域 6 表 model(t_courses / t_courses_classify / t_courses_class / t_courses_student /
- // t_courses_record / t_courses_record_student)——与 academic-model-ddl.sql「第四部分」逐列对齐。
- //
- // 本期范围(设计 §11-3 复盘结论):只做「排班生成 + 打卡 + 结束扣课时」最小闭环;
- // 调课/插班/结课申请/上课通知/学员档案列 P2(表未建)。
- //
- // 表语义分层:
- //
- // t_courses(课程模板:周规则+次数+价格+时长)
- // → t_courses_class(排班规则:某课在某班由某老师按 week+时分 排课)
- // → t_courses_record(排班实例:一次具体上课,含 income/teacher_reward)
- // → t_courses_record_student(打卡/考勤/扣课时:一次打卡一行)
- // → t_courses_student(报课订单 = 学费账户/课次账户)
- //
- // ⚠️ 时间列语义特例:t_courses_class.start_time/end_time 旧库为"时分"(date 部分无意义),
- // 目标态保留 datetime(3);新写入统一以 1970-01-01 日期 + 时分承载(设计 §11-3)。
- //
- // 字段注释五维口径:语义与单位 | 枚举全量取值 | 写入时机 | 主外键/关联语义 | 安全红线。
- package model
- import "time"
- // t_courses.status 课程状态编码。
- const (
- CoursesStatusUnpublished int32 = 0 // 未发布
- CoursesStatusPublished int32 = 1 // 发布未开始
- CoursesStatusOngoing int32 = 2 // 进行中
- CoursesStatusFinished int32 = 3 // 已结束
- )
- // t_courses_class.status / t_courses_class.confirm 编码。
- const (
- CoursesClassStatusOngoing int32 = 1 // 进行中
- CoursesClassStatusEnded int32 = 2 // 结束
- CoursesClassConfirmNo int8 = 0 // 未确认
- CoursesClassConfirmYes int8 = 1 // 已确认
- )
- // t_courses_student.type / status 编码(报课类型与状态)。
- const (
- CoursesStudentTypeNormal int8 = 1 // 正常上课
- CoursesStudentTypeTransfer int8 = 2 // 插班
- CoursesStudentStatusNormal int8 = 1 // 正常
- CoursesStudentStatusEnded int8 = 2 // 结束
- )
- // t_courses_record.status / 调课标记 编码。
- const (
- CoursesRecordStatusNotStarted int32 = 0 // 未开始
- CoursesRecordStatusOngoing int32 = 1 // 正在进行
- CoursesRecordStatusFinished int32 = 2 // 已结束
- CoursesRecordChangingCourseNo int8 = 0 // 非调课
- CoursesRecordChangingCourseYes int8 = 1 // 调课
- CoursesRecordTransferStudentNo int8 = 0 // 无插班生
- CoursesRecordTransferStudentYes int8 = 1 // 有插班生
- CoursesRecordRewardTypeNormal int8 = 1 // 正常结算
- CoursesRecordRewardTypeNoMinimum int8 = 2 // 按原课程老师课酬无最低课酬
- )
- // t_courses_record_student.type / pay 编码。
- const (
- CoursesRecordStudentTypeNormal int32 = 1 // 正常
- CoursesRecordStudentTypeTransfer int32 = 2 // 插班
- CoursesRecordStudentTypeGive int32 = 3 // 赠送
- CoursesRecordStudentPayUnpaid int8 = 0 // 未支付
- CoursesRecordStudentPayPaid int8 = 1 // 已支付
- CoursesWeekAll string = "-1" // week 列的特殊取值:全部周
- )
- // Courses 课程信息表 t_courses(课程模板:价格/次数/周规则/时长)。
- //
- // week 语义(字符串原样保留,不做位图化,保迁移稳定):
- // "-1" 全部周 / "0" 周日 / "1".."6" 周一..周六 / 多值英文逗号分隔(如 "1,3,5")。
- type Courses struct {
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // 课程 ID(D3 自增)|枚举:无|写入:INSERT 自动|FK:被 classify/class/student/record 引用|红线:已排课的课程禁止物理删除
- SubjectID int32 `gorm:"index:idx_subject_grade,priority:1;column:subject_id;not null;default:0" json:"subjectId"` // 科目 ID(0 混合科)|枚举:科目字典(跨域)|写入:创建/编辑|FK:→ 教学域科目(无物理 FK)|红线:仅分类维度
- GradeID int32 `gorm:"index:idx_subject_grade,priority:2;column:grade_id;not null;default:0" json:"gradeId"` // 年级 ID(0 混合年级)|枚举:年级字典(跨域)|写入:创建/编辑|FK:→ 教学域年级|红线:仅分类维度
- Type int32 `gorm:"column:type;not null;default:1" json:"type"` // 类型(1000 集体班/2000 VIP…)|枚举:班型编码(与 classify.class_type 对应)|写入:创建/编辑|FK:无|红线:影响课酬匹配,改动须评审
- InstitutionID int64 `gorm:"index:idx_inst_campus_status,priority:1;column:institution_id;not null;default:0" json:"institutionId"` // 机构 ID(原 schoolid;作用域列)|枚举:无|写入:创建时锁定|FK:→ t_institution|红线:scoping 强依赖
- CampusID int64 `gorm:"index:idx_inst_campus_status,priority:2;column:campus_id;not null;default:0" json:"campusId"` // 校区 ID(原 campusid;作用域列)|枚举:无|写入:创建/回填(campus_id=0 的多校区课程不更新)|FK:→ t_campus|红线:backfill 仅唯一校区时回填(设计 §3.5 #29)
- ClassifyID int64 `gorm:"index:idx_classify;column:classify_id;not null;default:0" json:"classifyId"` // 课程分类 ID(原 classifyid)|枚举:无|写入:创建/编辑|FK:→ t_courses_classify.id|红线:回填 max_people 依赖本列
- // ⚠ 本列**故意不写 `default:1`**:GORM 对"有 default tag 且字段为零值"的列会**跳过该列**、改由 DB 默认值填充,
- // 而 0(未发布)恰恰是本列最常用的零值 ⇒ 新建课程会被静默写成 status=1("新建即发布")。
- // 去掉 tag 后 GORM 总是显式写 status,0 能落库;DDL 的 `DEFAULT 1` 保持不变(只影响裸 SQL 插入)。
- // 同类坑本项目已踩两次(另一次是 `create_time`):凡有 `default:` 又可能被赋零值的列,先问"零值能落库吗"。
- Status int32 `gorm:"index:idx_inst_campus_status,priority:3;column:status;not null" json:"status"` // 状态(0 未发布/1 发布未开始/2 进行中/3 已结束)|枚举:CoursesStatus*|写入:发布/结课任务|FK:无|红线:状态机单向推进
- Name string `gorm:"column:name;size:500;not null" json:"name"` // 课程名称|枚举:无|写入:创建/编辑|FK:无|红线:用户输入,展示转义
- Number int32 `gorm:"column:number;not null;default:0" json:"number"` // 上课次数(课次)|枚举:无|写入:创建/编辑|FK:无|红线:与 record 生成条数关联,改动须校验已开课
- Duration string `gorm:"column:duration;type:decimal(5,1);not null;default:0.0" json:"duration"` // 单节课时长(课时,1 位小数,保留旧精度)|枚举:无|写入:创建/编辑|FK:无|红线:精度例外列(非 12,2)
- Week string `gorm:"column:week;size:50;not null;default:'-1'" json:"week"` // 周规则("-1" 全部/"0" 周日/"1".."6"/多值逗号分隔)|枚举:见上方 week 语义|写入:创建/编辑|FK:无|红线:解析逻辑须与旧 PHP 一致(排班展开依赖)
- StartTime *time.Time `gorm:"column:start_time" json:"startTime,omitempty"` // 课程开始时间(datetime(3))|枚举:无|写入:创建/编辑|FK:无|红线:<= end_time
- EndTime *time.Time `gorm:"column:end_time" json:"endTime,omitempty"` // 课程结束时间(datetime(3))|枚举:无|写入:创建/编辑|FK:无|红线:同上
- Address *string `gorm:"column:address;type:longtext" json:"address,omitempty"` // 上课地址|枚举:无|写入:创建/编辑|FK:无|红线:展示转义
- OriginalCost string `gorm:"column:original_cost;type:decimal(12,2);not null;default:0.00" json:"originalCost"` // 原价(元;旧 float(10,3) → 2 位收敛)|枚举:无|写入:创建/编辑|FK:无|红线:报课快照进 courses_student,事后改不影响存量
- RealPrice string `gorm:"column:real_price;type:decimal(12,2);not null;default:0.00" json:"realPrice"` // 实价(元)|枚举:无|写入:创建/编辑|FK:无|红线:同上
- DiscountCash string `gorm:"column:discount_cash;type:decimal(12,2);not null;default:0.00" json:"discountCash"` // 现金折扣(元)|枚举:无|写入:创建/编辑|FK:无|红线:可为负(加价场景),勿强校验非负
- DiscountRatio int32 `gorm:"column:discount_ratio;not null;default:0" json:"discountRatio"` // 折扣率(百分比数字部分,如 85 表示 85%)|枚举:整数百分比|写入:创建/编辑|FK:无|红线:仅展示/试算,实际金额以 real_price 为准
- Remark *string `gorm:"column:remark;type:longtext" json:"remark,omitempty"` // 备注|枚举:无|写入:创建/编辑|FK:无|红线:脱敏
- MaxPeople int32 `gorm:"column:max_people;not null;default:0" json:"maxPeople"` // 最多人数(0 时由 courses_update 从 classify 继承)|枚举:无|写入:创建/编辑/修复 SP|FK:无|红线:回填幂等(仅 max_people=0 行)
- UpdateUserID *string `gorm:"column:update_user_id;size:26" json:"updateUserId,omitempty"` // 最后修改人(ULID;原 update_userid)|枚举:无|写入:每次编辑|FK:→ t_user(逻辑)|红线:禁止旧 int id
- UpdateTime *time.Time `gorm:"column:update_time" json:"updateTime,omitempty"` // 最后修改时间(datetime(3))|枚举:无|写入:每次编辑|FK:无|红线:与 update_user_id 同写
- CreatedAt time.Time `gorm:"column:created_at;not null;default:CURRENT_TIMESTAMP" json:"createdAt"` // 创建时间(datetime(3))|枚举:无|写入:DB 默认|FK:无|红线:勿手改
- }
- // TableName 指定表名。
- func (Courses) TableName() string { return "t_courses" }
- // CoursesClassify 课程分类表 t_courses_classify(班级类型/单价/人数区间 + 课酬方案绑定)。
- type CoursesClassify struct {
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // 分类 ID(D3 自增)|枚举:无|写入:INSERT 自动|FK:被 t_courses.classify_id 引用|红线:被引用后禁删
- InstitutionID int64 `gorm:"index:idx_inst_campus,priority:1;column:institution_id;not null;default:0" json:"institutionId"` // 机构 ID(原 schoolid;作用域列)|枚举:无|写入:创建时锁定|FK:→ t_institution|红线:scoping 强依赖
- CampusID int64 `gorm:"index:idx_inst_campus,priority:2;column:campus_id;not null;default:0" json:"campusId"` // 校区 ID(原 campusid;作用域列)|枚举:无|写入:创建时锁定|FK:→ t_campus|红线:同上
- ClassType int32 `gorm:"column:class_type;not null;default:1000" json:"classType"` // 班级类型(1000 集体班/1001 小班…)|枚举:班型编码(与 course_payment_detail.class_type 对应)|写入:创建/编辑|FK:无|红线:课酬匹配维度
- CoursePaymentID int64 `gorm:"index:idx_payment;column:course_payment_id;not null;default:0" json:"coursePaymentId"` // 课酬方案 ID(原 course_paymentid)|枚举:无|写入:创建/编辑|FK:→ t_course_payment.id|红线:0=未绑定方案(课酬试算回退通用档)
- GradeID int32 `gorm:"column:grade_id;not null;default:0" json:"gradeId"` // 年级 ID(0 混合)|枚举:年级字典|写入:创建/编辑|FK:→ 教学域年级|红线:仅分类维度
- SubjectID int32 `gorm:"column:subject_id;not null;default:0" json:"subjectId"` // 科目 ID(0 混合)|枚举:科目字典|写入:创建/编辑|FK:→ 教学域科目|红线:同上
- Name string `gorm:"column:name;size:500;not null" json:"name"` // 分类名称|枚举:无|写入:创建/编辑|FK:无|红线:展示转义
- UnitPrice string `gorm:"column:unit_price;type:decimal(12,2);not null;default:0.00" json:"unitPrice"` // 单价(元/一次课;旧 float(10,3) 收敛)|枚举:无|写入:创建/编辑|FK:无|红线:报课计费依据,改动须评估存量课程
- MinPeople int32 `gorm:"column:min_people;not null;default:1" json:"minPeople"` // 最少人数|枚举:无|写入:创建/编辑|FK:无|红线:<= max_people
- MaxPeople int32 `gorm:"column:max_people;not null;default:1000" json:"maxPeople"` // 最多人数|枚举:无|写入:创建/编辑|FK:无|红线:同上
- }
- // TableName 指定表名。
- func (CoursesClassify) TableName() string { return "t_courses_classify" }
- // CoursesClass 课程班级关系表 t_courses_class(排班规则:某课在某班由某老师按 week+时分 排课)。
- //
- // ⚠️ start_time/end_time 旧库仅"时分"语义;目标态保留 datetime(3) 兼容旧数据,
- // 新写入统一 1970-01-01 + 时分(设计 §11-3)。
- type CoursesClass struct {
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // 关系 ID(D3 自增)|枚举:无|写入:INSERT 自动|FK:无|红线:排班规则行,被 record 生成依赖
- Status int32 `gorm:"column:status;not null;default:1" json:"status"` // 状态(1 进行中/2 结束)|枚举:CoursesClassStatus*|写入:创建/结课|FK:无|红线:结束后不再生成 record
- CoursesID int64 `gorm:"index:idx_courses;column:courses_id;not null" json:"coursesId"` // 课程 ID(原 coursesid)|枚举:无|写入:创建|FK:→ t_courses.id|红线:禁止改绑
- ClassID int64 `gorm:"index:idx_class;column:class_id;not null" json:"classId"` // 班级 ID(原 classid;旧库为外部班级体系 id,本期仅存值)|枚举:无|写入:创建|FK:外部班级体系(无物理 FK)|红线:不做业务联查
- TeacherID *string `gorm:"index:idx_teacher;column:teacher_id;size:26" json:"teacherId,omitempty"` // 老师(ULID;原 teacherid;0→NULL)|枚举:无|写入:排课时|FK:→ t_user(逻辑)|红线:课酬计算输入,禁止旧 int id
- InstitutionID int64 `gorm:"index:idx_inst_campus,priority:1;column:institution_id;not null;default:0" json:"institutionId"` // 机构 ID(原 schoolid;作用域列)|枚举:无|写入:创建时锁定|FK:→ t_institution|红线:scoping 强依赖
- CampusID int64 `gorm:"index:idx_inst_campus,priority:2;column:campus_id;not null;default:0" json:"campusId"` // 校区 ID(原 campusid;作用域列)|枚举:无|写入:创建时锁定|FK:→ t_campus|红线:同上
- StandardLessonCost string `gorm:"column:standard_lesson_cost;type:decimal(12,2);not null;default:0.00" json:"standardLessonCost"` // 课程标准单价(元;旧 float(10,3) 收敛)|枚举:无|写入:排课时|FK:无|红线:与 classify.unit_price 口径区分(本列=排班时快照)
- Number int32 `gorm:"column:number;not null;default:0" json:"number"` // 开课总量(课次)|枚举:无|写入:排课时|FK:无|红线:与 finished_number 比较判结课
- LessonCost string `gorm:"column:lesson_cost;type:decimal(12,2);not null;default:0.00" json:"lessonCost"` // 一次课的课费(元)|枚举:无|写入:排课时|FK:无|红线:打卡扣课时依据
- DiscountCash string `gorm:"column:discount_cash;type:decimal(12,2);not null;default:0.00" json:"discountCash"` // 课程优惠(元)|枚举:无|写入:排课时|FK:无|红线:可为负
- FinishedNumber int32 `gorm:"column:finished_number;not null;default:0" json:"finishedNumber"` // 已上课量(课次)|枚举:无|写入:结束上课时 +1|FK:无|红线:不得超过 number
- Confirm int8 `gorm:"column:confirm;not null;default:0" json:"confirm"` // 是否确认(0 未确认/1 确认)|枚举:CoursesClassConfirm*|写入:确认动作|FK:无|红线:未确认不得开始上课(业务校验)
- Week string `gorm:"column:week;size:50;not null;default:'-1'" json:"week"` // 周规则("-1" 全部/"0" 周日/"1".."6"/多值逗号分隔)|枚举:同 t_courses.week|写入:排课时|FK:无|红线:排班展开依据
- StartTime *time.Time `gorm:"column:start_time" json:"startTime,omitempty"` // 上课时间(时分语义;旧 NOT NULL,目标态放宽)|枚举:无|写入:排课时(1970-01-01+时分)|FK:无|红线:date 部分无业务含义,勿用于区间查询
- EndTime *time.Time `gorm:"column:end_time" json:"endTime,omitempty"` // 下课时间(时分语义)|枚举:无|写入:排课时|FK:无|红线:同上
- Address *string `gorm:"column:address;type:longtext" json:"address,omitempty"` // 上课地址|枚举:无|写入:排课时|FK:无|红线:展示转义
- Remark *string `gorm:"column:remark;type:longtext" json:"remark,omitempty"` // 备注|枚举:无|写入:排课时|FK:无|红线:脱敏
- CreatedAt time.Time `gorm:"column:created_at;not null;default:CURRENT_TIMESTAMP" json:"createdAt"` // 创建时间(datetime(3))|枚举:无|写入:DB 默认|FK:无|红线:勿手改
- }
- // TableName 指定表名。
- func (CoursesClass) TableName() string { return "t_courses_class" }
- // CoursesStudent 学生报课信息表 t_courses_student(学费账户/课次账户;旧 UUID 主键 → int64 + biz_no)。
- //
- // 该表同时是"报课订单"载体:t_courses_activity_user_discount_log.courses_student_id /
- // t_courses_activity_used_log.order_no 均指向本表;旧 UUID 必须保留在 biz_no(设计 §2「19.」)。
- //
- // ⚠️ activity_ids 旧为"多活动英文逗号串":目标态保留 varchar(200) 原样,**不拆关联表**
- // (拆表引入不可逆迁移风险且旧报表依赖该列,设计 §2.3)。
- // ⚠️ confirm_discount_user_id 为空/NULL 表示"不可充值"(业务语义,设计 §4.2)。
- type CoursesStudent struct {
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // 报课记录 ID(D3 自增)|枚举:无|写入:INSERT 自动|FK:被 activity 两张 log 引用|红线:对外单号是 biz_no
- BizNo string `gorm:"uniqueIndex:uk_biz_no;column:biz_no;size:32;not null" json:"bizNo"` // 业务单号(旧 UUID 原样保留;UNIQUE)|枚举:无|写入:报课时|FK:迁移幂等/回滚锚点|红线:不可变(旧 UUID 引用列靠它重映射)
- CoursesID int64 `gorm:"index:idx_courses;column:courses_id;not null" json:"coursesId"` // 课程 ID(原 coursesid)|枚举:无|写入:报课时|FK:→ t_courses.id|红线:与课程作用域须一致
- ClassID int64 `gorm:"column:class_id;not null;default:0" json:"classId"` // 班级 ID(原 classid)|枚举:无|写入:报课时|FK:外部/本域班级(无物理 FK)|红线:仅存值
- InstitutionID int64 `gorm:"index:idx_inst_campus,priority:1;column:institution_id;not null;default:0" json:"institutionId"` // 机构 ID(原 schoolid;作用域列)|枚举:无|写入:报课时|FK:→ t_institution|红线:scoping 强依赖
- CampusID int64 `gorm:"index:idx_inst_campus,priority:2;column:campus_id;not null;default:0" json:"campusId"` // 校区 ID(原 campusid;作用域列)|枚举:无|写入:报课时|FK:→ t_campus|红线:同上
- StudentID string `gorm:"index:idx_student_status,priority:1;index:idx_unpaid,priority:1;column:student_id;size:26;not null" json:"studentId"` // 学生(ULID;原 studentid)|枚举:无|写入:报课时|FK:→ t_user(逻辑)|红线:欠费核销按本列过滤
- Type int8 `gorm:"column:type;not null;default:1" json:"type"` // 上课类型(1 正常/2 插班)|枚举:CoursesStudentType*|写入:报课时|FK:无|红线:影响扣课时规则
- Status int8 `gorm:"index:idx_student_status,priority:2;column:status;not null;default:1" json:"status"` // 状态(1 正常/2 结束)|枚举:CoursesStudentStatus*|写入:报课/结课|FK:无|红线:结束后不再参与打卡
- Receivable string `gorm:"column:receivable;type:decimal(12,2);not null;default:0.00" json:"receivable"` // 应收(元)|枚举:无|写入:报课时|FK:无|红线:= net_receipts + unpaid(对账恒等式)
- NetReceipts string `gorm:"column:net_receipts;type:decimal(12,2);not null;default:0.00" json:"netReceipts"` // 实收(元;学生实际缴纳)|枚举:无|写入:缴费/退费|FK:无|红线:与资金流水累计一致(对账)
- Unpaid string `gorm:"index:idx_unpaid,priority:2;column:unpaid;type:decimal(12,2);not null;default:0.00" json:"unpaid"` // 欠费(元)|枚举:无|写入:缴费/核销|FK:无|红线:>0 即进入欠费核销候选(idx_unpaid)
- LessonCost string `gorm:"column:lesson_cost;type:decimal(12,2);not null;default:0.00" json:"lessonCost"` // 一节课的费用(元)|枚举:无|写入:报课时快照|FK:无|红线:打卡扣费依据,扣课时不得改本列
- Number int32 `gorm:"column:number;not null;default:0" json:"number"` // 总课次|枚举:无|写入:报课时|FK:无|红线:含 give_number(赠送)口径差异见旧 PHP
- FinishedNumber int32 `gorm:"column:finished_number;not null;default:0" json:"finishedNumber"` // 已上课次|枚举:无|写入:打卡 +1|FK:无|红线:不得超过 number+give_number
- DiscountCash string `gorm:"column:discount_cash;type:decimal(12,2);not null;default:0.00" json:"discountCash"` // 现金折扣(元)|枚举:无|写入:报课时|FK:无|红线:可为负
- ActivityIDs *string `gorm:"column:activity_ids;size:200" json:"activityIds,omitempty"` // 报课使用的活动 ID(英文逗号分隔;原 activityid)|枚举:活动 ID 列表串|写入:报课时|FK:弱关联 t_courses_activity.id(不拆表)|红线:勿当 JSON 解析,按英文逗号 split
- GiveNumber int32 `gorm:"column:give_number;not null;default:0" json:"giveNumber"` // 赠送课次|枚举:无|写入:报课时|FK:无|红线:扣课时先扣赠送还是实付须按旧口径
- GiveCost string `gorm:"column:give_cost;type:decimal(12,2);not null;default:0.00" json:"giveCost"` // 使用的赠送金额(元)|枚举:无|写入:报课时|FK:无|红线:与 discount_log 累计一致
- ChangingCourseNumber int32 `gorm:"column:changing_course_number;not null;default:0" json:"changingCourseNumber"` // 调课次数|枚举:无|写入:调课(P2)|FK:无|红线:本期只读
- RemainderNumber int32 `gorm:"column:remainder_number;not null;default:0" json:"remainderNumber"` // 剩余课次|枚举:无|写入:报课/打卡|FK:无|红线:>=0(负数=数据异常,须修复)
- RemainderCost string `gorm:"column:remainder_cost;type:decimal(12,2);not null;default:0.00" json:"remainderCost"` // 剩余费用(元;缴费扣除每次课费后余额)|枚举:无|写入:报课/打卡|FK:无|红线:与 net_receipts−已扣课费 一致(对账)
- RemainderReceivable string `gorm:"column:remainder_receivable;type:decimal(12,2);not null;default:0.00" json:"remainderReceivable"` // 剩余应收(元)|枚举:无|写入:报课/打卡|FK:无|红线:<= receivable
- BadDebt string `gorm:"column:bad_debt;type:decimal(12,2);not null;default:0.00" json:"badDebt"` // 坏账(元)|枚举:无|写入:退费差额/坏账处理|FK:无|红线:与 special_expenses 同源口径
- ConfirmDiscountUserID *string `gorm:"column:confirm_discount_user_id;size:26" json:"confirmDiscountUserId,omitempty"` // 确认学生折扣的操作人(原 confirm_discount_userid;空/NULL=不可充值)|枚举:无|写入:折扣确认动作|FK:→ t_user(逻辑)|红线:**NULL 语义=不可充值**,勿用 0 替代
- ConfirmTime *time.Time `gorm:"column:confirm_time" json:"confirmTime,omitempty"` // 确认时间(datetime(3))|枚举:无|写入:折扣确认|FK:无|红线:与 confirm_discount_user_id 同写
- RecommenderUserID *string `gorm:"column:recommender_user_id;size:26" json:"recommenderUserId,omitempty"` // 推荐人(原 recommender_userid)|枚举:无|写入:报课时|FK:→ t_user(逻辑)|红线:分账树上行起点(设计 §3.3)
- RecommenderTime *time.Time `gorm:"column:recommender_time" json:"recommenderTime,omitempty"` // 推荐时间(datetime(3))|枚举:无|写入:报课时|FK:无|红线:勿手改
- CreateTime time.Time `gorm:"column:create_time;not null;default:CURRENT_TIMESTAMP" json:"createTime"` // 创建时间(datetime(3))|枚举:无|写入:DB 默认|FK:无|红线:欠费核销排序键(create_time ASC)
- Remark *string `gorm:"column:remark;type:longtext" json:"remark,omitempty"` // 备注|枚举:无|写入:报课/编辑|FK:无|红线:脱敏
- }
- // TableName 指定表名。
- func (CoursesStudent) TableName() string { return "t_courses_student" }
- // CoursesRecord 课程记录 t_courses_record(课表/排班实例;旧 UUID 主键 → int64 + biz_no)。
- //
- // 流转(设计 §7.3 流程外延):开始上课写 actual_start_time;结束上课写 actual_end_time + income
- // + teacher_reward,并联动 t_courses_record_student / 余额 spend / 分账 / 课酬(同事务)。
- type CoursesRecord struct {
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // 上课记录 ID(D3 自增)|枚举:无|写入:INSERT 自动|FK:被 record_student.record_id 引用|红线:对外单号是 biz_no
- BizNo string `gorm:"uniqueIndex:uk_biz_no;column:biz_no;size:32;not null" json:"bizNo"` // 业务单号(旧 UUID 原样保留;UNIQUE)|枚举:无|写入:排班生成时|FK:迁移/回滚锚点|红线:不可变
- CoursesID int64 `gorm:"index:idx_courses_start,priority:1;column:courses_id;not null" json:"coursesId"` // 课程 ID(原 coursesid)|枚举:无|写入:排班生成|FK:→ t_courses.id|红线:与 start_time 组排班唯一性判据(3122)
- ClassID int64 `gorm:"column:class_id;not null" json:"classId"` // 班级 ID(原 classid)|枚举:无|写入:排班生成|FK:弱关联 t_courses_class.class_id|红线:仅存值
- TeacherID *string `gorm:"index:idx_teacher_start,priority:1;column:teacher_id;size:26" json:"teacherId,omitempty"` // 老师(ULID;原 teacherid)|枚举:无|写入:排班/调课|FK:→ t_user(逻辑)|红线:课酬归属人,禁止改绑已结束记录
- InstitutionID int64 `gorm:"index:idx_inst_campus,priority:1;column:institution_id;not null;default:0" json:"institutionId"` // 机构 ID(原 schoolid;作用域列)|枚举:无|写入:排班生成|FK:→ t_institution|红线:scoping 强依赖
- CampusID int64 `gorm:"index:idx_inst_campus,priority:2;column:campus_id;not null;default:0" json:"campusId"` // 校区 ID(原 campusid;作用域列)|枚举:无|写入:排班生成|FK:→ t_campus|红线:同上
- Status int32 `gorm:"index:idx_status;column:status;not null;default:0" json:"status"` // 状态(0 未开始/1 正在进行/2 已结束)|枚举:CoursesRecordStatus*|写入:开始/结束上课|FK:无|红线:状态机校验(非法迁移 → 3121)
- ChangingCourse int8 `gorm:"column:changing_course;not null;default:0" json:"changingCourse"` // 是否调课(1 调课)|枚举:CoursesRecordChangingCourse*|写入:调课(P2)|FK:无|红线:本期只读
- StudentChangingCourse int32 `gorm:"column:student_changing_course;not null;default:0" json:"studentChangingCourse"` // 单学生调课数|枚举:无|写入:调课(P2)|FK:无|红线:本期只读
- TransferStudent int8 `gorm:"column:transfer_student;not null;default:0" json:"transferStudent"` // 是否有插班生(1 有)|枚举:CoursesRecordTransferStudent*|写入:插班(P2)|FK:无|红线:本期只读
- OriginalRecordNo *string `gorm:"column:original_record_no;size:32" json:"originalRecordNo,omitempty"` // 原上课记录单号(原 original_recordid UUID)|枚举:无|写入:调课生成|FK:弱关联本表 biz_no|红线:字符串保留(非 int64 FK)
- StartTime *time.Time `gorm:"index:idx_courses_start,priority:2;index:idx_teacher_start,priority:2;column:start_time" json:"startTime,omitempty"` // 上课时间(datetime(3))|枚举:无|写入:排班生成|FK:无|红线:排班唯一性判据之一
- EndTime *time.Time `gorm:"column:end_time" json:"endTime,omitempty"` // 下课时间(datetime(3))|枚举:无|写入:排班生成|FK:无|红线:>= start_time
- Address *string `gorm:"column:address;type:longtext" json:"address,omitempty"` // 上课地点|枚举:无|写入:排班生成/编辑|FK:无|红线:展示转义
- ActualStartTime *time.Time `gorm:"column:actual_start_time" json:"actualStartTime,omitempty"` // 实际上课时间(datetime(3))|枚举:无|写入:开始上课|FK:无|红线:只写一次(重复开始 → 3121)
- ActualEndTime *time.Time `gorm:"column:actual_end_time" json:"actualEndTime,omitempty"` // 实际下课时间(datetime(3))|枚举:无|写入:结束上课|FK:无|红线:结束上课的幂等判据(非空即已结束)
- StudentNumber int32 `gorm:"column:student_number;not null;default:0" json:"studentNumber"` // 上课人数|枚举:无|写入:结束上课汇总|FK:无|红线:与 record_student 行数一致
- Income string `gorm:"column:income;type:decimal(12,2);not null;default:0.00" json:"income"` // 课程收入(元)|枚举:无|写入:结束上课|FK:无|红线:= Σ record_student.cost(对账)
- TeacherReward string `gorm:"column:teacher_reward;type:decimal(12,2);not null;default:0.00" json:"teacherReward"` // 老师课酬(元)|枚举:无|写入:结束上课(课酬试算)|FK:无|红线:**条件式**恒等 —— 有非赠送生时 = Σ(record_student.teacher_reward WHERE type∈{1,2});全班赠送(type 全为 3)时学生行全 0、本列仍写课酬(PHP `if($stuCount)` 语义,见 `applyTeacherRewards`)
- TeacherRewardType int8 `gorm:"column:teacher_reward_type;not null;default:1" json:"teacherRewardType"` // 课酬结算方式(1 正常/2 按原课程老师课酬无最低课酬)|枚举:CoursesRecordRewardType*|写入:结束上课|FK:无|红线:影响试算分支
- ExpectStudentIDs *string `gorm:"column:expect_student_ids;size:500" json:"expectStudentIds,omitempty"` // 要求上课的学生 id 列表(逗号分隔;原 expect_studentids)|枚举:ULID 列表串|写入:排班生成|FK:弱关联 t_user|红线:字符串保留,勿当数组入 SQL
- Remark *string `gorm:"column:remark;type:longtext" json:"remark,omitempty"` // 备注|枚举:无|写入:编辑|FK:无|红线:脱敏
- CreatedAt time.Time `gorm:"column:created_at;not null;default:CURRENT_TIMESTAMP" json:"createdAt"` // 创建时间(datetime(3))|枚举:无|写入:DB 默认|FK:无|红线:勿手改
- }
- // TableName 指定表名。
- func (CoursesRecord) TableName() string { return "t_courses_record" }
- // CoursesRecordStudent 课程上课记录 t_courses_record_student(打卡/考勤/扣课时;旧 UUID → int64 + biz_no)。
- //
- // 语义:一次打卡 = 一行;cost 为本次扣除课费;give_cost 为本次使用的赠送金额;
- // `divide_into_log_delete` 用本表"首次 cost>0 且 end_time 非空"的记录判定分成归属(设计 §3.3 #23)。
- type CoursesRecordStudent 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:迁移/回滚锚点|红线:不可变
- CoursesID int64 `gorm:"column:courses_id;not null" json:"coursesId"` // 课程 ID(原 coursesid)|枚举:无|写入:打卡时冗余|FK:→ t_courses.id|红线:与 record 的课程一致
- ClassID int64 `gorm:"column:class_id;not null;default:0" json:"classId"` // 班级 ID(原 classid)|枚举:无|写入:打卡时冗余|FK:弱关联|红线:仅存值
- InstitutionID int64 `gorm:"index:idx_inst_campus,priority:1;column:institution_id;not null;default:0" json:"institutionId"` // 机构 ID(原 schoolid;作用域列)|枚举:无|写入:打卡时冗余|FK:→ t_institution|红线:scoping 强依赖
- CampusID int64 `gorm:"index:idx_inst_campus,priority:2;column:campus_id;not null;default:0" json:"campusId"` // 校区 ID(原 campusid;作用域列)|枚举:无|写入:打卡时冗余|FK:→ t_campus|红线:同上
- RecordID int64 `gorm:"index:idx_record;column:record_id;not null" json:"recordId"` // 上课记录 ID(原 recordid UUID → 迁移 JOIN biz_no 重映射)|枚举:无|写入:打卡时|FK:→ t_courses_record.id|红线:分账/课酬汇总按本列分组
- StudentID string `gorm:"index:idx_student_time,priority:1;column:student_id;size:26;not null" json:"studentId"` // 学生(ULID;原 studentid)|枚举:无|写入:打卡时|FK:→ t_user(逻辑)|红线:首次扣费判定按 (student, start_time)
- Type int32 `gorm:"column:type;not null;default:1" json:"type"` // 上课类型(1 正常/2 插班/3 赠送)|枚举:CoursesRecordStudentType*|写入:打卡时|FK:无|红线:赠送课次不动余额(只动 give_cost)
- StartTime time.Time `gorm:"index:idx_student_time,priority:2;column:start_time;not null" json:"startTime"` // 上课时间(datetime(3))|枚举:无|写入:打卡时|FK:无|红线:只增不改(考勤凭证)
- EndTime *time.Time `gorm:"column:end_time" json:"endTime,omitempty"` // 下课时间(datetime(3))|枚举:无|写入:结束上课|FK:无|红线:非空 = 已完成(分成归属判据)
- Cost string `gorm:"column:cost;type:decimal(12,2);not null;default:0.00" json:"cost"` // 本次课程费用(元;扣课时金额)|枚举:无|写入:结束上课|FK:无|红线:Σ cost = record.income(对账)
- GiveCost string `gorm:"column:give_cost;type:decimal(12,2);not null;default:0.00" json:"giveCost"` // 本次使用的赠送金额(元)|枚举:无|写入:结束上课|FK:无|红线:与 discount_log 累计一致
- TeacherReward string `gorm:"column:teacher_reward;type:decimal(12,2);not null;default:0.00" json:"teacherReward"` // 本次课老师课酬(元)|枚举:无|写入:结束上课(课酬试算)|FK:无|红线:**`type=3`(赠送)行恒为 0**(PHP 只写 `type IN (1,2)`);非赠送行之和 = record.teacher_reward
- Pay int8 `gorm:"column:pay;not null;default:0" json:"pay"` // 是否已支付(0 未/1 已支付)|枚举:CoursesRecordStudentPay*|写入:扣费成功/欠费核销|FK:无|红线:未支付即欠费候选
- Remark *string `gorm:"column:remark;type:longtext" json:"remark,omitempty"` // 备注|枚举:无|写入:打卡/结束|FK:无|红线:脱敏
- }
- // TableName 指定表名。
- func (CoursesRecordStudent) TableName() string { return "t_courses_record_student" }
|