dto_course.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. // 课程域请求 / 响应 DTO(端点 44~57 的**入参形状**与响应拼装)——AC-13c。
  2. //
  3. // 依据:unified/03-migration/academic-frontend-contract-v1.md
  4. // - §2.4 课程/排班/打卡(端点 44~57 的 method/path/角色/请求要点);
  5. // - §3.4 C 表(响应字段名,**唯一权威**)+ 总原则 ③「字段准入条件」;
  6. // - §1.5 B1/B2(时间与量纲:`startTime`/`endTime` 是业务时间列,金额是两位小数字符串)。
  7. //
  8. // ---------------------------------------------------------------------------
  9. // 三条 DTO 铁律(与 `dto_fund.go` / `dto_activity.go` 同源,**不另立一套**)
  10. // ---------------------------------------------------------------------------
  11. //
  12. // 1. **金额一律 `string`**(`"100.00"`),Handler 用 `parseMoney` / `parseMoneyOrZero` 解析;
  13. // 不声明 float64(浮点误差)也不声明 money.Amount(会让 JSON number 被静默接受)。
  14. // 2. **缺参 → 2001 由 `validate` tag 表达;值非法 → 2002 由 Handler 显式判**。枚举字段
  15. // 不写 `oneof=`(`oneof` 失败会被归成 2001,而"传了非法枚举"属 2002)。
  16. // 3. **响应字段逐个列溯源**:每个 JSON 键都必须能追到一个具体列,写不出来的一律不做
  17. // (本批因此删了 `room` 与 `divide[].level`,并保留了 `week` 的字符串原值——见各结构注释)。
  18. //
  19. // ---------------------------------------------------------------------------
  20. // 本批**不接受**的请求字段(写了也无效,故不声明)
  21. // ---------------------------------------------------------------------------
  22. //
  23. // institutionId 由请求作用域决定(非超管传别的机构 → 1004,由 `resolveInstitutionID` 判)
  24. // status(课程) 发布状态机专用入口,不在 CRUD 里改(AC-11 `CreateCourse` 恒落"未发布")
  25. // numberSign /
  26. // unpaid / unpaid 类欠费列 —— **废弃列**(2018-05 停更),任何入参都不写它
  27. package handler
  28. import "time"
  29. // ---------------------------------------------------------------- 端点 45:课程 CRUD
  30. // CreateCourseReq 端点 45 新建课程请求体(`POST /admin/courses`)。
  31. //
  32. // 字段 → 目标列(DDL 第四部分 t_courses):
  33. //
  34. // name→name / subjectId→subject_id / gradeId→grade_id / type→type / classifyId→classify_id /
  35. // number→number / duration→duration / week→week / startTime→start_time / endTime→end_time /
  36. // address→address / originalCost→original_cost / realPrice→real_price /
  37. // discountCash→discount_cash / discountRatio→discount_ratio / maxPeople→max_people / remark→remark
  38. type CreateCourseReq struct {
  39. // Name 课程名称(必填)。
  40. Name string `json:"name" validate:"required,max=500"`
  41. // SubjectID 科目(0 = 混合)。
  42. SubjectID int32 `json:"subjectId"`
  43. // GradeID 年级(0 = 混合)。
  44. GradeID int32 `json:"gradeId"`
  45. // Type 班型编码(0 → 1000 集体班)。
  46. Type int32 `json:"type"`
  47. // ClassifyID 课程分类(0 = 不挂分类;>0 时须存在且同作用域,否则 3001)。
  48. ClassifyID int64 `json:"classifyId"`
  49. // Number 上课次数(>=0)。
  50. Number int32 `json:"number"`
  51. // Duration 单节课时长("1.5" 这类课时字符串,1 位小数;**必填**,>0)。
  52. Duration string `json:"duration" validate:"required,max=10"`
  53. // Week 周规则("-1" 全部 / "0" 周日 / "1".."6" / 逗号多值;空 → "-1")。
  54. Week string `json:"week" validate:"max=50"`
  55. // StartTime 课程有效期起(RFC3339 或 `2006-01-02`;可空)。
  56. StartTime string `json:"startTime"`
  57. // EndTime 课程有效期止(同上;不得早于 StartTime → 否则 2002)。
  58. EndTime string `json:"endTime"`
  59. // Address 上课地址。
  60. Address string `json:"address"`
  61. // OriginalCost 原价(元字符串;可空 → "0.00")。
  62. OriginalCost string `json:"originalCost"`
  63. // RealPrice 实价(元字符串;可空 → "0.00")。
  64. RealPrice string `json:"realPrice"`
  65. // DiscountCash 现金折扣(元字符串;**允许负**,模型红线)。
  66. DiscountCash string `json:"discountCash"`
  67. // DiscountRatio 折扣率(整数百分比,仅展示/试算)。
  68. DiscountRatio int32 `json:"discountRatio"`
  69. // MaxPeople 最多人数(0 → 由修复 SP 从分类继承)。
  70. MaxPeople int32 `json:"maxPeople"`
  71. // Remark 备注。
  72. Remark string `json:"remark"`
  73. }
  74. // UpdateCourseReq 端点 45 更新课程请求体(`PUT /admin/courses/:id`)。
  75. //
  76. // 语义:**部分更新**(nil = 不改)。与新建的唯一差异是"全部可空",因此一律用指针,
  77. // 以便区分"没传"(不改)与"传了零值"(改成 0 / 空串)。
  78. type UpdateCourseReq struct {
  79. // Name 课程名称(传了就不得为空)。
  80. Name *string `json:"name"`
  81. // SubjectID 科目。
  82. SubjectID *int32 `json:"subjectId"`
  83. // GradeID 年级。
  84. GradeID *int32 `json:"gradeId"`
  85. // Type 班型。
  86. Type *int32 `json:"type"`
  87. // ClassifyID 分类(>=0;>0 须存在)。
  88. ClassifyID *int64 `json:"classifyId"`
  89. // Number 上课次数(>=0)。
  90. Number *int32 `json:"number"`
  91. // Duration 单节课时长(课时字符串,>0)。
  92. Duration *string `json:"duration"`
  93. // Week 周规则。
  94. Week *string `json:"week"`
  95. // StartTime 有效期起。
  96. StartTime *string `json:"startTime"`
  97. // EndTime 有效期止。
  98. EndTime *string `json:"endTime"`
  99. // Address 上课地址。
  100. Address *string `json:"address"`
  101. // OriginalCost 原价。
  102. OriginalCost *string `json:"originalCost"`
  103. // RealPrice 实价。
  104. RealPrice *string `json:"realPrice"`
  105. // DiscountCash 现金折扣(允许负)。
  106. DiscountCash *string `json:"discountCash"`
  107. // DiscountRatio 折扣率。
  108. DiscountRatio *int32 `json:"discountRatio"`
  109. // MaxPeople 最多人数。
  110. MaxPeople *int32 `json:"maxPeople"`
  111. // Remark 备注。
  112. Remark *string `json:"remark"`
  113. }
  114. // ---------------------------------------------------------------- 端点 47:新建排班规则
  115. // CreateClassReq 端点 47 新建排班规则请求体(`POST /admin/courses/:id/classes`)。
  116. //
  117. // 时分用 `"HH:MM"` 字符串(而非 datetime):旧库该两列是"时分语义",date 部分无业务含义。
  118. type CreateClassReq struct {
  119. // ClassID 班级 ID(旧库外部班级 id;0 = 不指定)。
  120. ClassID int64 `json:"classId"`
  121. // TeacherID 老师 ULID(可空:空 = 未定老师;给了才会做 3122 冲突判定)。
  122. TeacherID string `json:"teacherId" validate:"max=26"`
  123. // Week 周规则("-1"/"0".."6"/逗号多值;空 → "-1")。
  124. Week string `json:"week" validate:"max=50"`
  125. // Start 上课时分(`"09:00"`;**必填**)。
  126. Start string `json:"start" validate:"required,max=5"`
  127. // End 下课时分(`"10:30"`;可空 → 按课程 duration 推算;给了必须晚于 Start)。
  128. End string `json:"end" validate:"max=5"`
  129. // StandardLessonCost 课程标准单价(元字符串;可空 → "0.00")。
  130. StandardLessonCost string `json:"standardLessonCost"`
  131. // LessonCost 一次课课费(元字符串;可空 → "0.00";打卡扣课时依据)。
  132. LessonCost string `json:"lessonCost"`
  133. // DiscountCash 课程优惠(元字符串;允许负)。
  134. DiscountCash string `json:"discountCash"`
  135. // Number 开课总量(课次;>=0)。
  136. Number int32 `json:"number"`
  137. // Address 上课地址。
  138. Address string `json:"address"`
  139. // Remark 备注。
  140. Remark string `json:"remark"`
  141. }
  142. // ---------------------------------------------------------------- 端点 49:生成课表
  143. // GenerateRecordsReq 端点 49 生成课表请求体(`POST /admin/courses/:id/records/generate`)。
  144. //
  145. // 两个字段都可空(空 → days=7、from=今天),但**传了必须合法**(非法 → 2002)。
  146. type GenerateRecordsReq struct {
  147. // Days 生成窗口天数(<=0 → 7;>366 → 钳制 366)。
  148. Days int `json:"days"`
  149. // From 窗口起点(`2006-01-02` 或 RFC3339;只取日期部分;空 → 服务端当天)。
  150. From string `json:"from"`
  151. }
  152. // ---------------------------------------------------------------- 端点 51 / 52:打卡明细
  153. // AttendanceItemReq 单条打卡明细(端点 52 请求 `list[]` 元素,端点 51 的 `items[]` 同构)。
  154. //
  155. // 字段 → 目标列(t_courses_record_student):
  156. //
  157. // userId→student_id / type→type / pay→pay / cost→cost / giveCost→give_cost / remark→remark
  158. //
  159. // ⚠ `pay` 是**是否已支付**(int8 0/1),不是金额;金额是 `cost`。契约 §3.4 C 行 52 明确
  160. // 请求体为 `list:[{userId,type,pay,cost,giveCost}]`(mock 旧名 `attendance/payCost` 已废弃)。
  161. type AttendanceItemReq struct {
  162. // UserID 学生 ULID(必填)。
  163. UserID string `json:"userId" validate:"required,max=26"`
  164. // Type 上课类型(1 正常 / 2 插班 / 3 赠送;0 → 1;其它 → 2002)。
  165. Type int32 `json:"type"`
  166. // Pay 是否已支付(0 未付 / 1 已付;nil = 不改,结课时按"已报课即已付")。
  167. Pay *int8 `json:"pay"`
  168. // Cost 本次课费(元字符串;可空 = 取报课记录的 lesson_cost 快照)。
  169. Cost string `json:"cost"`
  170. // GiveCost 本次使用赠送额(元字符串;可空 → 0)。
  171. GiveCost string `json:"giveCost"`
  172. // Remark 备注。
  173. Remark string `json:"remark"`
  174. }
  175. // MarkAttendanceReq 端点 52 打卡请求体(`POST /admin/courses/records/:recordId/attendance`)。
  176. type MarkAttendanceReq struct {
  177. // List 打卡明细(必填非空;同一学生出现两次 → 3140)。
  178. List []AttendanceItemReq `json:"list" validate:"required,min=1,dive"`
  179. }
  180. // FinishRecordReq 端点 51 结束上课请求体(`POST /admin/courses/records/:recordId/finish`)。
  181. type FinishRecordReq struct {
  182. // Items 本次打卡明细(**可空**:空 = 按在读学生全勤自动生成,成本取 lesson_cost 快照)。
  183. Items []AttendanceItemReq `json:"items"`
  184. // ActualEndTime 实际下课时间(RFC3339 或 `2006-01-02`;空 → 服务端当前时刻)。
  185. ActualEndTime string `json:"actualEndTime"`
  186. // RewardType 课酬结算方式(0 → 1 正常 / 2 按原课程老师课酬无最低课酬;其它 → 2002)。
  187. RewardType int8 `json:"rewardType"`
  188. // Remark 结课备注(当前仅日志展示,不落库——`t_courses_record.remark` 属排班备注)。
  189. Remark string `json:"remark"`
  190. }
  191. // ---------------------------------------------------------------- 端点 54:报名
  192. // EnrollReq 端点 54 报名请求体(`POST /admin/courses/:id/students`)。
  193. //
  194. // ⚠ **契约 §2.4 端点 54 只给了响应字段,没有入参形状** ⇒ 本请求体是 AC-13c 自定义的
  195. // 最小可对账集合(诊断见 `service.EnrollInput` 注释),已在交付报告登记为「契约缺口待补」。
  196. type EnrollReq struct {
  197. // UserID 学生 ULID(必填)。
  198. UserID string `json:"userId" validate:"required,max=26"`
  199. // ClassID 班级 ID(0 = 不指定)。
  200. ClassID int64 `json:"classId"`
  201. // CampusID 校区(0 = 用作用域校区;>0 且与作用域校区不一致 → 1004)。
  202. CampusID int64 `json:"campusId"`
  203. // Pay 本次实收(元字符串,必填;**不写资金流水**,只做报课行快照)。
  204. Pay string `json:"pay" validate:"required"`
  205. // RemainderReceivable 剩余应收(元字符串;可空 → 0 = 全额缴清)。
  206. RemainderReceivable string `json:"remainderReceivable"`
  207. // LessonCost 单节课费(元字符串,必填;扣课时依据)。
  208. LessonCost string `json:"lessonCost" validate:"required"`
  209. // Number 课次(>=0)。
  210. Number int32 `json:"number"`
  211. // GiveNumber 赠送课次(>=0)。
  212. GiveNumber int32 `json:"giveNumber"`
  213. // DiscountCash 现金折扣(元字符串;允许负)。
  214. DiscountCash string `json:"discountCash"`
  215. // GiveCost 已用赠送额(元字符串;可空 → 0)。
  216. GiveCost string `json:"giveCost"`
  217. // RecommenderUserID 推荐人 ULID(可空;分账上行起点)。
  218. RecommenderUserID string `json:"recommenderUserId" validate:"max=26"`
  219. // Remark 备注。
  220. Remark string `json:"remark"`
  221. }
  222. // ---------------------------------------------------------------- 端点 56:欠费核销
  223. // CounteractArrearsReq 端点 56 欠费核销请求体(`POST /admin/courses/arrears/counteract`)。
  224. //
  225. // 契约 §2.4 行 56 原文:`studentId,institutionId,campusId,money`(**四个都必填**)。
  226. // `institutionId`/`campusId` 是 SP27 的**显式数据范围参数**(不是作用域推断),
  227. // 因此非超管传了与本作用域不一致的值 → **1004**(不是 2002)。
  228. type CounteractArrearsReq struct {
  229. // StudentID 学生 ULID(必填)。
  230. StudentID string `json:"studentId" validate:"required,max=26"`
  231. // InstitutionID 机构 ID(必填 >0)。
  232. InstitutionID int64 `json:"institutionId" validate:"required,gt=0"`
  233. // CampusID 校区 ID(必填 >0)。
  234. CampusID int64 `json:"campusId" validate:"required,gt=0"`
  235. // Money 待核销金额(元字符串,必填;<=0 → 服务端 no-op,不是错误)。
  236. Money string `json:"money" validate:"required"`
  237. }
  238. // ---------------------------------------------------------------- 响应拼装(只放契约要求的键)
  239. // courseSavedResp 端点 45 响应(契约 §3.4 C 行 45:只要求 `id`)。
  240. type courseSavedResp struct {
  241. // ID 课程 ID。
  242. ID int64 `json:"id"`
  243. }
  244. // courseRowView 端点 44 的行(契约 §3.4 C 行 44)。
  245. //
  246. // 键名 `classify`(不是 `classifyId`)是**契约既定**:前端按 `classify` 读,改名会静默空白。
  247. type courseRowView struct {
  248. // ID 课程 ID。
  249. ID int64 `json:"id"`
  250. // InstitutionID 机构 ID。
  251. InstitutionID int64 `json:"institutionId"`
  252. // Name 课程名称。
  253. Name string `json:"name"`
  254. // Classify 课程分类 ID(源列 `classify_id`;契约键名 `classify`)。
  255. Classify int64 `json:"classify"`
  256. // Status 课程状态(0 未发布 / 1 发布未开始 / 2 进行中 / 3 已结束)。
  257. Status int32 `json:"status"`
  258. // TeacherIDs 排班规则上的老师(去重升序;空 → [])。
  259. TeacherIDs []string `json:"teacherIds"`
  260. // RecordCount 排班实例条数(全状态)。
  261. RecordCount int64 `json:"recordCount"`
  262. }
  263. // classCreatedResp 端点 47 响应(契约 §3.4 C 行 47:`classId`)。
  264. type classCreatedResp struct {
  265. // ClassID 新规则的班级 ID(`t_courses_class.class_id`)。
  266. ClassID int64 `json:"classId"`
  267. }
  268. // classRowView 端点 46 的行(契约 §3.4 C 行 46 + 本批两处偏差)。
  269. //
  270. // ⚠ 与契约的两处差异(已在交付报告登记):
  271. //
  272. // ① `week` 下发**原值字符串**而不是 1~7 的数字:该列取值 `"-1"`(全部周)/`"0"`..`"6"`/
  273. // 逗号多值,"1~7 数字"表达不了「全部周」与「多值」;
  274. // ② **无 `room`**:`t_courses_class` 没有 room 列(只有 address/remark),按 §3.4 ③ 不下发。
  275. type classRowView struct {
  276. // ClassID 班级 ID(源列 `class_id`)。
  277. ClassID int64 `json:"classId"`
  278. // CourseID 课程 ID。
  279. CourseID int64 `json:"courseId"`
  280. // TeacherID 老师 ULID(可空 → 空串)。
  281. TeacherID string `json:"teacherId"`
  282. // Week 周规则原值("-1"/"0".."6"/逗号多值)。
  283. Week string `json:"week"`
  284. // Start 上课时分(**date 部分恒 1970-01-01**,只取时分;见 model.CoursesClass 注释)。
  285. Start *time.Time `json:"start"`
  286. // End 下课时分(可空)。
  287. End *time.Time `json:"end"`
  288. // CampusID 校区 ID。
  289. CampusID int64 `json:"campusId"`
  290. }
  291. // recordRowView 端点 48 的行(契约 §3.4 C 行 48)。
  292. //
  293. // ⚠ **无 `room`**(同端点 46:`t_courses_record` 只有 `address`,没有 room 列)。
  294. type recordRowView struct {
  295. // RecordID 排班实例 ID。
  296. RecordID int64 `json:"recordId"`
  297. // CourseID 课程 ID。
  298. CourseID int64 `json:"courseId"`
  299. // TeacherID 老师 ULID。
  300. TeacherID string `json:"teacherId"`
  301. // CampusID 校区 ID。
  302. CampusID int64 `json:"campusId"`
  303. // StartTime 上课时间。
  304. StartTime *time.Time `json:"startTime"`
  305. // EndTime 下课时间。
  306. EndTime *time.Time `json:"endTime"`
  307. // Status 上课状态(0 未开始 / 1 上课中 / 2 已结束)。
  308. Status int32 `json:"status"`
  309. // StudentCount 上课人数(源列 `student_number`,结课时写入)。
  310. StudentCount int32 `json:"studentCount"`
  311. }
  312. // generateRecordsResp 端点 49 响应(契约 §3.4 C 行 49)。
  313. type generateRecordsResp struct {
  314. // Generated 本次新建的排班实例条数(幂等:第二次为 0)。
  315. Generated int `json:"generated"`
  316. // Days 生效的生成窗口天数(已钳制)。
  317. Days int `json:"days"`
  318. // Changed 本次是否真的写了库(false = 无新增)。
  319. Changed bool `json:"changed"`
  320. }
  321. // startRecordResp 端点 50 响应(契约 §3.4 C 行 50)。
  322. type startRecordResp struct {
  323. // RecordID 排班实例 ID。
  324. RecordID int64 `json:"recordId"`
  325. // Status 新状态(恒 1 = 上课中)。
  326. Status int32 `json:"status"`
  327. // CourseID 课程 ID。
  328. CourseID int64 `json:"courseId"`
  329. }
  330. // attendanceResp 端点 52 响应(契约 §3.4 C 行 52:`recordId` + `checked`)。
  331. type attendanceResp struct {
  332. // RecordID 排班实例 ID。
  333. RecordID int64 `json:"recordId"`
  334. // Checked 当前打卡人数(= 该实例的打卡行总数,含此前已打卡的)。
  335. Checked int32 `json:"checked"`
  336. }
  337. // divideGrantView 端点 51 `divide[]` 的元素(契约 §3.4 C 行 51 去掉无列字段 `level`)。
  338. type divideGrantView struct {
  339. // UserID 获得分成的用户(源列 `t_user_divide_into_log.user_id`;契约未列,用于区分元素)。
  340. UserID string `json:"userId"`
  341. // Quota 本次分成额度(源列 `t_user_divide_into_log.money`,入账当时即节点 quota)。
  342. Quota string `json:"quota"`
  343. // Amount 本次入账金额(同上列)。
  344. Amount string `json:"amount"`
  345. }
  346. // payrollTraceView 端点 51 `payroll` 对象。
  347. type payrollTraceView struct {
  348. // DetailID 命中的课酬规则 ID(0 = 未配置方案/无法定位)。
  349. DetailID int64 `json:"detailId"`
  350. // Reward 本课课酬合计(源列 `t_courses_record.teacher_reward`,**落库值**)。
  351. Reward string `json:"reward"`
  352. }
  353. // perStudentView 端点 51 `perStudent[]` 的元素(契约 §3.4 C 行 51,字段名与端点 52 请求同义)。
  354. type perStudentView struct {
  355. // UserID 学生 ULID。
  356. UserID string `json:"userId"`
  357. // Type 上课类型(1 正常 / 2 插班 / 3 赠送)。
  358. Type int32 `json:"type"`
  359. // Pay 是否已支付。
  360. Pay bool `json:"pay"`
  361. // Cost 本次课费。
  362. Cost string `json:"cost"`
  363. // GiveCost 本次使用赠送额。
  364. GiveCost string `json:"giveCost"`
  365. // Counteracted 该学生本次是否触发了分账入账(源列:本次是否新增
  366. // `t_user_divide_into_log` 行;**不是**欠费核销,契约命名与语义在报告登记)。
  367. Counteracted bool `json:"counteracted"`
  368. }
  369. // finishRecordResp 端点 51 响应(契约 §3.4 C 行 51)。
  370. type finishRecordResp struct {
  371. // RecordID 排班实例 ID。
  372. RecordID int64 `json:"recordId"`
  373. // CourseID 课程 ID。
  374. CourseID int64 `json:"courseId"`
  375. // Status 上课状态(恒 2 = 已结束)。
  376. Status int32 `json:"status"`
  377. // Changed 本次是否真的写了库(false = 幂等命中:未重复扣课时/未重复分账)。
  378. Changed bool `json:"changed"`
  379. // TotalSpend 本次扣课时合计(= `t_courses_record.income` = Σ record_student.cost)。
  380. TotalSpend string `json:"totalSpend"`
  381. // Divide 本次分账入账明细(空 → [])。
  382. Divide []divideGrantView `json:"divide"`
  383. // Payroll 课酬溯源。
  384. Payroll payrollTraceView `json:"payroll"`
  385. // PerStudent 逐学生打卡明细(空 → [])。
  386. PerStudent []perStudentView `json:"perStudent"`
  387. }
  388. // studentRowView 端点 53 的行(契约 §3.4 C 行 53)。
  389. //
  390. // ⚠ **无 `realName`**:姓名在 identity 域(`t_user`),academic 域不持有、跨域只读要经 port,
  391. // 本批未注入姓名解析器 ⇒ 不下发(空字符串会让前端显示空白且无法区分"未填"与"查不到")。
  392. type studentRowView struct {
  393. // UserID 学生 ULID(源列 `student_id`)。
  394. UserID string `json:"userId"`
  395. // Pay 应收(源列 `receivable`)。
  396. Pay string `json:"pay"`
  397. // Cost 实收(源列 `net_receipts`)。
  398. Cost string `json:"cost"`
  399. // GiveCost 已用赠送额(源列 `give_cost`)。
  400. GiveCost string `json:"giveCost"`
  401. // RemainderReceivable 剩余应收(源列 `remainder_receivable`,**欠费真值**)。
  402. RemainderReceivable string `json:"remainderReceivable"`
  403. // Status 报课状态(1 正常 / 2 结束,数字码)。
  404. Status int32 `json:"status"`
  405. }
  406. // enrollResp 端点 54 响应(契约 §3.4 C 行 54)。
  407. type enrollResp struct {
  408. // Enrolled 是否已报名(成功恒 true)。
  409. Enrolled bool `json:"enrolled"`
  410. // CourseID 课程 ID。
  411. CourseID int64 `json:"courseId"`
  412. // UserID 学生 ULID。
  413. UserID string `json:"userId"`
  414. }
  415. // arrearsRowView 端点 55 的行(契约 §3.4 C 行 55)。
  416. type arrearsRowView struct {
  417. // StudentID 学生 ULID。
  418. StudentID string `json:"studentId"`
  419. // CourseID 课程 ID。
  420. CourseID int64 `json:"courseId"`
  421. // RemainderReceivable 剩余应收(>0;**唯一欠费口径**)。
  422. RemainderReceivable string `json:"remainderReceivable"`
  423. // Counteracted 该报课行是否发生过实收/核销(源列 `net_receipts > 0`;契约未定义判据,见报告)。
  424. Counteracted bool `json:"counteracted"`
  425. }
  426. // counteractResp 端点 56 响应(契约 §3.4 C 行 56)。
  427. type counteractResp struct {
  428. // Counteracted 本次是否真的核销了金额(false = 无欠费/金额<=0,服务端 no-op)。
  429. Counteracted bool `json:"counteracted"`
  430. // StudentID 学生 ULID。
  431. StudentID string `json:"studentId"`
  432. // Amount 实际核销总额(元;未核销 → "0.00")。
  433. Amount string `json:"amount"`
  434. }
  435. // scheduleRowView 端点 57 的行(**字段名待移动端契约定稿**,见 service.ScheduleRow 注释)。
  436. type scheduleRowView struct {
  437. // RecordID 排班实例 ID。
  438. RecordID int64 `json:"recordId"`
  439. // CourseID 课程 ID。
  440. CourseID int64 `json:"courseId"`
  441. // CourseName 课程名称(回查 t_courses.name)。
  442. CourseName string `json:"courseName"`
  443. // TeacherID 老师 ULID。
  444. TeacherID string `json:"teacherId"`
  445. // CampusID 校区 ID。
  446. CampusID int64 `json:"campusId"`
  447. // StartTime 上课时间。
  448. StartTime *time.Time `json:"startTime"`
  449. // EndTime 下课时间。
  450. EndTime *time.Time `json:"endTime"`
  451. // Status 上课状态(0/1/2)。
  452. Status int32 `json:"status"`
  453. // Cost 本次课费(无打卡行 → "0.00")。
  454. Cost string `json:"cost"`
  455. // GiveCost 本次使用赠送额(无打卡行 → "0.00")。
  456. GiveCost string `json:"giveCost"`
  457. // Pay 是否已支付(无打卡行 → false)。
  458. Pay bool `json:"pay"`
  459. // Checked 是否已有本人的打卡行。
  460. Checked bool `json:"checked"`
  461. }