dto_fund.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. // 财务域请求 DTO(端点 1~17、21 的入参形状 + 少量响应拼装结构)。
  2. //
  3. // 依据:unified/03-migration/academic-domain-design.md §4.1(资金)/ §4.2 端点 17(账单)/ §4.6(响应契约)。
  4. //
  5. // ---------------------------------------------------------------------------
  6. // 三条 DTO 铁律(AC-12 验收项 ②/④/⑥ 的落点)
  7. // ---------------------------------------------------------------------------
  8. //
  9. // 1. **金额一律 `string`**(`"100.00"`),Handler 用 `parseMoney` → `money.Parse` 解析。
  10. // 绝不声明成 `float64`/`money.Amount`:前者引入二进制浮点(0.1+0.2 级误差进对账),
  11. // 后者让 JSON number 也能解码(等于偷偷接受 float)。前端契约 v1.2 已按字符串对接。
  12. // 2. **必填/格式 2001 由 `validate` tag 表达**(`required` / `gt=0` / `oneof=...`);
  13. // **值非法 2002 由 Handler 显式判定**(金额 Parse 失败、枚举不在白名单)。
  14. // 两者分工见 `parseMoney` 注释,不要互相顶替。
  15. // 3. **响应 DTO 逐字段声明,不直接序列化 model**:`imgs`(凭证)/`pay_return_data`(渠道原始报文)
  16. // 这类字段必须"压根不在结构里",而不是"靠 model 的 `json:"-"` 兜底"——
  17. // 兜底只要有人加一个 `json:"-"` 漏掉字母就会泄露(AC-12 验收 ⑥)。
  18. //
  19. // 命名:字段用 camelCase JSON(前端契约同源),Go 侧保持缩写惯例(ID 后缀全大写)。
  20. package handler
  21. import "time"
  22. // RechargeReq 端点 3 充值请求体(`POST /admin/finance/recharges`)。
  23. //
  24. // 字段:
  25. // - UserID 充值用户(ULID;必填);
  26. // - InstitutionID / CampusID 机构/校区(必填 >0:资金落库必须有明确归属);
  27. // - Money 金额字符串(必填;>0 由 Service 校验 → 2002);
  28. // - Type 流水类型(可空:<100 一律归 100,对齐 dump `IF inputType<100`);
  29. // - RequestID 幂等请求号(可空:空 = 不享幂等,重复提交会二次入账——前端务必传);
  30. // - Remark 备注(可空:按类型取默认文案)。
  31. type RechargeReq struct {
  32. UserID string `json:"userId" validate:"required,max=26"`
  33. InstitutionID int64 `json:"institutionId" validate:"required,gt=0"`
  34. CampusID int64 `json:"campusId" validate:"required,gt=0"`
  35. Money string `json:"money" validate:"required"`
  36. Type int32 `json:"type"`
  37. RequestID string `json:"requestId" validate:"max=64"`
  38. Remark string `json:"remark" validate:"max=1000"`
  39. }
  40. // ExpenditureReq 端点 4 消费请求体(`POST /admin/finance/expenditures`)。
  41. //
  42. // `Type` 必填且必须属消费白名单(101/104/105/200/201/204/205)——不在白名单 → **2002**
  43. // (白名单来自 model.ExpenseLogType*,Service 也会再校验一次;Handler 抢先是为了给出准确字段名)。
  44. type ExpenditureReq struct {
  45. UserID string `json:"userId" validate:"required,max=26"`
  46. InstitutionID int64 `json:"institutionId" validate:"required,gt=0"`
  47. CampusID int64 `json:"campusId" validate:"required,gt=0"`
  48. Money string `json:"money" validate:"required"`
  49. Type int32 `json:"type" validate:"required"`
  50. RequestID string `json:"requestId" validate:"max=64"`
  51. Remark string `json:"remark" validate:"max=1000"`
  52. }
  53. // RefundReq 端点 5 退费请求体(`POST /admin/finance/refunds`)。
  54. //
  55. // `Type` 必填(流水类型,通常 207 余额退费);`Money<=0` 由 Service 统一 2002(§3.8-R6),
  56. // 因此响应体的 `changed` **恒 true**(字段仅为契约兼容保留,见 §4.1 端点 5 注)。
  57. type RefundReq struct {
  58. UserID string `json:"userId" validate:"required,max=26"`
  59. InstitutionID int64 `json:"institutionId" validate:"required,gt=0"`
  60. CampusID int64 `json:"campusId" validate:"required,gt=0"`
  61. Money string `json:"money" validate:"required"`
  62. Type int32 `json:"type" validate:"required"`
  63. RequestID string `json:"requestId" validate:"max=64"`
  64. Remark string `json:"remark" validate:"max=1000"`
  65. }
  66. // CustomRefundReq 端点 6 自定义退费请求体(`POST /admin/finance/refunds/custom`)。
  67. //
  68. // `RequestID` **必填**(设计 §4.1 端点 6 原文):本流程含"活动失效"副作用,
  69. // 没有幂等键时重试会把同一批活动重复置为失效并重复写 207/108 双流水。
  70. type CustomRefundReq struct {
  71. UserID string `json:"userId" validate:"required,max=26"`
  72. InstitutionID int64 `json:"institutionId" validate:"required,gt=0"`
  73. CampusID int64 `json:"campusId" validate:"required,gt=0"`
  74. Money string `json:"money" validate:"required"`
  75. RequestID string `json:"requestId" validate:"required,max=64"`
  76. Remark string `json:"remark" validate:"max=1000"`
  77. }
  78. // TransferReq 端点 7 课费转结请求体(`POST /admin/finance/transfers`)。
  79. //
  80. // 四个维度都必填:转出/转入的机构+校区(跨机构转结需超管作用域,Service 层校验)。
  81. type TransferReq struct {
  82. UserID string `json:"userId" validate:"required,max=26"`
  83. FromInstitutionID int64 `json:"fromInstitutionId" validate:"required,gt=0"`
  84. FromCampusID int64 `json:"fromCampusId" validate:"required,gt=0"`
  85. ToInstitutionID int64 `json:"toInstitutionId" validate:"required,gt=0"`
  86. ToCampusID int64 `json:"toCampusId" validate:"required,gt=0"`
  87. Money string `json:"money" validate:"required"`
  88. RequestID string `json:"requestId" validate:"max=64"`
  89. Remark string `json:"remark" validate:"max=1000"`
  90. }
  91. // BadDebtReq 端点 8 坏账请求体(`POST /admin/finance/bad-debts`)。
  92. //
  93. // ⚠ dump 口径(§3.8-A18):独立坏账是 **`balance` 与 `special_expenses` 双增**,
  94. // 不是"只加特殊费用"——响应里 `balance` 已包含本次增量。
  95. type BadDebtReq struct {
  96. UserID string `json:"userId" validate:"required,max=26"`
  97. InstitutionID int64 `json:"institutionId" validate:"required,gt=0"`
  98. CampusID int64 `json:"campusId" validate:"required,gt=0"`
  99. Money string `json:"money" validate:"required"`
  100. RequestID string `json:"requestId" validate:"max=64"`
  101. Remark string `json:"remark" validate:"max=1000"`
  102. }
  103. // ClearBalanceReq 端点 9 余额清零请求体(`POST /admin/finance/cleared-balances`)。
  104. //
  105. // **无金额入参**(清零额 = 清零前校区余额,由 Service 读库决定);dump 亦无请求号,
  106. // 故本端点不享幂等(重复调用第二次会因"余额已 0"命中 3120)。
  107. type ClearBalanceReq struct {
  108. UserID string `json:"userId" validate:"required,max=26"`
  109. InstitutionID int64 `json:"institutionId" validate:"required,gt=0"`
  110. CampusID int64 `json:"campusId" validate:"required,gt=0"`
  111. Remark string `json:"remark" validate:"max=1000"`
  112. }
  113. // SpendReq 端点 10 累计消费请求体(`POST /admin/finance/spends`)。
  114. //
  115. // `SpendMoney` 语义(§3.1 #11):累加到 `spend` 列(**不动 balance**);
  116. // 负数由 Service 归 0(dump INSERT 分支同口径),故 Handler 不做 >0 断言。
  117. type SpendReq struct {
  118. UserID string `json:"userId" validate:"required,max=26"`
  119. InstitutionID int64 `json:"institutionId" validate:"required,gt=0"`
  120. CampusID int64 `json:"campusId" validate:"required,gt=0"`
  121. SpendMoney string `json:"spendMoney" validate:"required"`
  122. }
  123. // CreateRequestReq 端点 13 创建余额工单请求体(`POST /admin/finance/requests`)。
  124. //
  125. // 字段:
  126. // - Type 工单类型(必填;白名单 100/101/202/203/300 → 非法 2002);
  127. // - ToInstitutionID/ToCampusID 仅 type=300 转结使用(其余类型传了也忽略);
  128. // - Imgs 凭证图片(**只写不读**:响应里不会出现,见 RequestView)。
  129. type CreateRequestReq struct {
  130. Type int32 `json:"type" validate:"required"`
  131. UserID string `json:"userId" validate:"required,max=26"`
  132. InstitutionID int64 `json:"institutionId" validate:"required,gt=0"`
  133. CampusID int64 `json:"campusId"`
  134. ToInstitutionID int64 `json:"toInstitutionId"`
  135. ToCampusID int64 `json:"toCampusId"`
  136. Money string `json:"money" validate:"required"`
  137. TransferAccountsType int8 `json:"transferAccountsType"`
  138. OrderNo string `json:"orderNo" validate:"max=50"`
  139. Item string `json:"item" validate:"max=1000"`
  140. Imgs string `json:"imgs" validate:"max=65535"`
  141. Remark string `json:"remark" validate:"max=1000"`
  142. }
  143. // ApproveReq 端点 14 审批通过请求体(`POST /admin/finance/requests/:id/approve`)。
  144. //
  145. // 无必填字段(审批意见可空)——**没有 body 也能通过**(前端"一键通过"场景)。
  146. type ApproveReq struct {
  147. ResponseMsg string `json:"responseMsg" validate:"max=1000"`
  148. }
  149. // RejectReq 端点 15 驳回请求体(`POST /admin/finance/requests/:id/reject`)。
  150. //
  151. // `ResponseMsg` **必填**:驳回必须留理由(对账/申诉依据)。
  152. type RejectReq struct {
  153. ResponseMsg string `json:"responseMsg" validate:"required,max=1000"`
  154. }
  155. // BillQuery 端点 17 账单查询参数(`GET /student/bill`)。
  156. //
  157. // 字段:
  158. // - UserID 目标学生(**非本人需 super_admin/parent**,见 BillHandler 权限判定);
  159. // - CampusIDs 校区逗号串(dump 第二入参;**只回显不过滤**,见 service.GetBillInput);
  160. // - From/To 时间窗(`to` **不含**上界);
  161. // - Page/PageSize 分页(已钳制)。
  162. type BillQuery struct {
  163. // UserID 学生(空 = 用作用域用户)。
  164. UserID string
  165. // CampusIDs 校区逗号串(可空)。
  166. CampusIDs string
  167. // From 起始时间(含)。
  168. From time.Time
  169. // To 结束时间(不含)。
  170. To time.Time
  171. // Page 页码。
  172. Page int
  173. // PageSize 每页条数。
  174. PageSize int
  175. }