dto_payroll.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. // 课酬 / 维护 端点请求 DTO(端点 59、61、62、63)——AC-13d 第一批。
  2. //
  3. // 依据:unified/03-migration/academic-frontend-contract-v1.md §2.5(端点表)/ §3.4 D 表(字段级);
  4. // 设计 `academic-domain-design.md` §4.5(端点清单与角色)/ §3.5(SP28/SP29)/ §3.6(SP30/SP31)。
  5. //
  6. // ---------------------------------------------------------------------------
  7. // 本批**只做 59/61/62/63**,58 与 60 因契约形状冲突暂停(已登记偏差,等裁决)
  8. // ---------------------------------------------------------------------------
  9. //
  10. // 58 课酬方案 —— 契约把"方案(t_course_payment)"与"明细(t_course_payment_detail)"**压成一行**:
  11. // `id/schoolId/campusId/effectDate` 是方案列,`teacherId/classifyId/percentage/min/max/reward` 是明细列,
  12. // `actived` 两级都有;且 `teacherExp:number` **一个字段要承载 `teacher_exp_min` + `teacher_exp_max` 两列**。
  13. // ⇒ 需要契约先回答三件事:入参是否嵌套 `details[]`、`id` 指方案还是明细、教龄区间怎么表达。
  14. // 在此之前**不落代码**(按"字段准入条件"硬门槛,写不出源列就不进契约)。
  15. //
  16. // 60 收支 —— 契约 `GET/POST /admin/expenditures[/types]` 把**字典**(`t_expenditure`)与
  17. // **记录**(`t_expenditure_log`)放在同一组路径下,而行字段 `type/name/direction` 三个里
  18. // `name`/`direction` 在记录表**不存在**(记录表只有 `expenditure_id`,方向要 JOIN 字典的 `type`);
  19. // `bizNo` 只在记录表有。⇒ 需要契约把"字典"与"记录"的响应分别定义(当前一行行结构无法同时满足两者)。
  20. //
  21. // 下面四个端点的字段全部可逐列溯源,故先行落地。
  22. package handler
  23. // ---------------------------------------------------------------- 端点 59:课酬试算
  24. // PayrollQuoteReq 端点 59 课酬试算请求体(`POST /admin/payroll/quote`)。
  25. //
  26. // 契约 §2.5 行 59 原文入参:`teacherId,schoolId,campusId,classifyId,teacherExp,date`(六个都必填)。
  27. //
  28. // ⚠ **本端点无法给出"最终课酬金额"**:最终金额还需要 `income`(本次课收入)、`studentCount`、
  29. // `teacherRewardType` 三个入参,契约没给 ⇒ 本端点只返回**命中的规则**(含 percentage/min/max/reward),
  30. // 由前端自行展示;**最终金额只在结课(端点 51)产生**,两者共用同一 `computeRecordReward`(不重复实现公式)。
  31. // 已在交付报告登记为偏差(若产品要"试算金额",契约补三个入参即可)。
  32. type PayrollQuoteReq struct {
  33. // TeacherID 老师 ULID(必填)。
  34. TeacherID string `json:"teacherId" validate:"required,max=26"`
  35. // SchoolID 机构 ID(必填 >0;旧口径 schoolid = 目标态 institution_id)。
  36. SchoolID int64 `json:"schoolId" validate:"required,gt=0"`
  37. // CampusID 校区 ID(0 = 机构级方案;契约要求必传,0 合法)。
  38. CampusID int64 `json:"campusId"`
  39. // ClassifyID 课程分类 ID(必填 >0;提供 grade/subject/class_type 三个维度)。
  40. ClassifyID int64 `json:"classifyId" validate:"required,gt=0"`
  41. // TeacherExp 教龄(年;可含小数;负数按 0,与 dump 763609 一致)。
  42. TeacherExp float64 `json:"teacherExp"`
  43. // Date 试算日期(`2006-01-02` 或 RFC3339;方案 `effect_date <= date`,**含当日**)。
  44. Date string `json:"date" validate:"required"`
  45. }
  46. // payrollQuoteView 端点 59 响应(契约 §4.6 `PayrollQuote` 的 9 个字段,**不得裁掉溯源字段**)。
  47. //
  48. // 字段来源(逐列溯源):
  49. //
  50. // detailId ← t_course_payment_detail.id
  51. // schemeId ← t_course_payment.id
  52. // schemeCampusId ← t_course_payment.campus_id(0 = 机构级方案)
  53. // schemeScope ← 由命中层级推导(campus | institution)——**不是列**,而是"从哪个 campus_id 查到的"
  54. // ruleTier ← 由命中规则类型推导(personal_classify | personal_grade_subject | cert_general | nocert_general)
  55. // percentage ← t_course_payment_detail.percentage(**百分数字符串**,非金额)
  56. // min ← min_payment;max ← max_payment;reward ← reward
  57. //
  58. // `schemeScope` / `ruleTier` 属**可推导的溯源标签**(不是新造业务值):它们回答"这条规则为什么被命中",
  59. // 是排障必需项(§4.6 v0.1.29 明确"不得裁掉")。
  60. type payrollQuoteView struct {
  61. // DetailID 命中的课酬明细规则 ID。
  62. DetailID int64 `json:"detailId"`
  63. // SchemeID 命中的课酬方案 ID。
  64. SchemeID int64 `json:"schemeId"`
  65. // SchemeCampusID 命中方案所属校区(0 = 机构级)。
  66. SchemeCampusID int64 `json:"schemeCampusId"`
  67. // SchemeScope 方案层级(campus / institution)。
  68. SchemeScope string `json:"schemeScope"`
  69. // RuleTier 规则层级(personal_classify / personal_grade_subject / cert_general / nocert_general)。
  70. RuleTier string `json:"ruleTier"`
  71. // Percentage 课酬比例(**百分数字符串** "85.0",不是 0.85、不是金额)。
  72. Percentage string `json:"percentage"`
  73. // Min 最低课酬标准(元字符串;"0.00" = 不限制)。
  74. Min string `json:"min"`
  75. // Max 最高课酬标准(元字符串;"0.00" = 不限制)。
  76. Max string `json:"max"`
  77. // Reward 额外课酬(元字符串;在比例基础上追加)。
  78. Reward string `json:"reward"`
  79. }
  80. // ---------------------------------------------------------------- 端点 61 / 62 / 63:维护
  81. // MaintenanceResultResp 维护类端点(61/62/63)的响应(契约 §3.4 D 表:`affected` + `message`)。
  82. //
  83. // `affected` = 本次**实际写库**的行数;`message` = 面向运维的一段人话(含"可重跑/已是最新"判据)。
  84. // 三个端点响应同构,用同一结构体(避免三份一模一样的东西漂移)。
  85. type MaintenanceResultResp struct {
  86. // Affected 本次实际影响(写入)的行数。
  87. Affected int64 `json:"affected"`
  88. // Message 结果说明(含幂等判据:第二次执行应为 0 / 已是最新)。
  89. Message string `json:"message"`
  90. }
  91. // ActivitySyncReq 端点 63 活动数据同步请求体(`POST /admin/maintenance/activity-sync`)。
  92. //
  93. // 契约 §2.5 行 63:`mode` ∈ {`used-log`, `signed`};缺省/非法 → **2001**(契约明确写 2001,
  94. // 不是 2002——因为"缺参"与"枚举非法"在这里都按"必须指定同步模式"处理)。
  95. type ActivitySyncReq struct {
  96. // Mode 同步模式(必填:`used-log` → SP30 重建 used_log;`signed` → SP31 重放 signed)。
  97. Mode string `json:"mode" validate:"required"`
  98. }