// 报课记录的作用域回查(AC-08 老带新结算用)——`t_courses_student` 的只读投影。 // // 为什么独立文件:`course_repo.go` 是 AC-11 的交付文件,本批只**新增**一个只读方法 // (对既有行为零改动),集中在本文件便于评审与回滚。 // // dump 依据(`sp_courses_activity_use_activity_recommend`,yuansiwei_finance.sql:763339-763347): // // IF (selectSchoolid<=0) THEN // SELECT(SELECT schoolid FROM t_courses_student WHERE studentid=inputUserid AND id=inputOrder) INTO selectSchoolid; // END IF; // IF (selectCampusid<=0) THEN // SELECT(SELECT campusid FROM t_courses_student WHERE studentid=inputUserid AND id=inputOrder) INTO selectCampusid; // END IF; // IF(selectCampusid<=0 || selectSchoolid<=0) THEN LEAVE readLoop; END IF; // // 语义:活动行上的 `schoolid/campusid` 为 0("全机构池"活动)时,用**报课记录**的机构/校区 // 兜底——钱必须落到具体校区账户上。 package repository import ( "context" "strings" infraerrors "yuansiwei/unified-backend/internal/infra/errors" ) // StudentCourseScope 报课记录的作用域投影(机构 + 校区)。 // // 字段:StudentCourseID 报课记录 ID;InstitutionID 原 schoolid;CampusID 原 campusid。 type StudentCourseScope struct { // StudentCourseID t_courses_student.id。 StudentCourseID int64 `gorm:"column:student_course_id"` // InstitutionID 机构 ID(原 schoolid)。 InstitutionID int64 `gorm:"column:institution_id"` // CampusID 校区 ID(原 campusid)。 CampusID int64 `gorm:"column:campus_id"` } // GetStudentCourseScope 按 (报课记录 id, 学生) 取机构/校区(dump 763339 的两次子查询合并为一次)。 // // 入参: // - ctx 请求上下文; // - studentCourseID 报课记录 ID(<=0 → ErrBadParam); // - studentID 学生(ULID;空 → ErrBadParam;**必须与报课记录归属人一致**,否则 ErrNotFound)。 // // 出参:(*StudentCourseScope, nil) 命中;ErrNotFound(记录不存在或不属于该学生)/ ErrBadParam / ErrDBError。 // // 作用域:与 dump 一致**按 (id, student_id) 精确定位、不叠作用域过滤**—— // dump 的子查询同样没有机构/校区条件;越权风险为 0(`student_id` 来自登录态, // 且本方法只读两个作用域列,写入口账户时仍由活动/报课记录的机构决定)。 func (r *CourseRepo) GetStudentCourseScope( ctx context.Context, studentCourseID int64, studentID string, ) (*StudentCourseScope, error) { if studentCourseID <= 0 { return nil, infraerrors.WithCause(infraerrors.ErrBadParam, errString("repository: GetStudentCourseScope 需要 student_course_id>0")) } if strings.TrimSpace(studentID) == "" { return nil, infraerrors.WithCause(infraerrors.ErrBadParam, errString("repository: GetStudentCourseScope 需要 student_id")) } db, err := r.db(ctx) if err != nil { return nil, err } var out StudentCourseScope q := db.Table("t_courses_student"). Select("id AS student_course_id, institution_id, campus_id"). Where("id = ? AND student_id = ?", studentCourseID, strings.TrimSpace(studentID)) if err := q.Take(&out).Error; err != nil { return nil, wrapQueryErr(err, "GetStudentCourseScope(t_courses_student)") } return &out, nil }