finance.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. /**
  2. * finance.ts —— 费用管理接口封装(academic 域;对应旧 PHP 财务可操作面)。
  3. *
  4. * 覆盖旧系统用户可操作方法(对齐 modules/user + courses/RequestController):
  5. * 账户快照/可退额、充值、消费、退费、自定义退费(§3.1)、转结、坏账、余额清零(§3.2)、
  6. * spend 扣减、资金工单(建/审/驳/撤)、流水查询、仪表盘。
  7. *
  8. * 典型使用(views/academic/fee/index.vue):
  9. * - 流水列表:useListPage<ExpenseLogDTO> fetcher=listLogs({...scope.toQuery(), userId, page, pageSize})
  10. * - 各操作:直接调对应方法,后端恒 200 + body code;业务错误由调用方判 code
  11. *
  12. * 契约对齐(academic-frontend-contract-v1 v1.9;v1.6 B1/C1 已回改):
  13. * - 金额恒字符串两位小数(元);时间 RFC3339;分页 PageData 五字段;
  14. * - 写操作带 requestId 走幂等(重复提交 → repeated=true 回放首次)。
  15. */
  16. import { http } from '@/api/http';
  17. import type { PageR, R } from '@/types/api';
  18. /* ================= 读 ================= */
  19. /** 资金流水 DTO(端点 11 列表行;v1.5:流水表无作用域列,不含 institutionId/campusId) */
  20. export interface ExpenseLogDTO {
  21. /** 流水 id(int64 主键) */
  22. id: number;
  23. /** 用户 id(canonical_user_id ULID 字符串) */
  24. userId: string;
  25. /** 流水类型编码(100 充值 + / 101,201 消费 − / 207 退费 − / 208 清零 − / 106,206 转结 / 108 坏账 +) */
  26. type: number;
  27. /** 金额(字符串两位小数) */
  28. money: string;
  29. /** 变动后余额(字符串两位小数,全局账户快照) */
  30. balanceAfter: string;
  31. /** 业务时间(RFC3339;v1.6 C1:← legacy create_time) */
  32. createTime: string;
  33. /** 备注 */
  34. remark: string;
  35. }
  36. /** 流水查询参数(作用域由 useScope().toQuery() 注入;userId 定位账户) */
  37. export type LogQuery = {
  38. institutionId: number;
  39. campusId?: number;
  40. userId?: string;
  41. type?: number;
  42. from?: string;
  43. to?: string;
  44. page?: number;
  45. pageSize?: number;
  46. };
  47. /** 资金流水分页列表(端点 11,GET /api/admin/finance/logs)。 */
  48. export function listLogs(params: LogQuery): Promise<PageR<ExpenseLogDTO>> {
  49. return http.get<PageR<ExpenseLogDTO>>('/api/admin/finance/logs', { params }).then((r) => r.data);
  50. }
  51. /** 账户快照 DTO(端点 1;扁平结构) */
  52. export interface AccountDTO {
  53. userId: string;
  54. institutionId: number;
  55. campusId: number;
  56. totalBalance: string;
  57. campusBalance: string;
  58. specialExpenses: string;
  59. cumulativeIncome: string;
  60. }
  61. /** 账户快照(端点 1,GET /api/admin/finance/accounts/:userId)。 */
  62. export function fetchAccount(userId: string, params: { institutionId: number; campusId?: number }): Promise<R<AccountDTO>> {
  63. return http.get<R<AccountDTO>>(`/api/admin/finance/accounts/${userId}`, { params }).then((r) => r.data);
  64. }
  65. /** 可退额(端点 2,GET /api/admin/finance/accounts/:userId/refundable)。 */
  66. export function fetchRefundable(
  67. userId: string,
  68. params: { institutionId: number; campusId?: number },
  69. ): Promise<R<{ refundableAmount: string }>> {
  70. return http
  71. .get<R<{ refundableAmount: string }>>(`/api/admin/finance/accounts/${userId}/refundable`, { params })
  72. .then((r) => r.data);
  73. }
  74. /** 仪表盘 DTO(端点 21;账户维度 + 校区余额分布) */
  75. export interface DashboardDTO {
  76. balance: string;
  77. totalIncome: string;
  78. totalExpenditure: string;
  79. specialExpenses: string;
  80. netReceivable: string;
  81. balanceDistribution: { campusId: number; balance: string }[];
  82. }
  83. /** 仪表盘(端点 21,GET /api/admin/finance/dashboard)。 */
  84. export function fetchDashboard(params: { institutionId: number; campusId?: number; userId?: string }): Promise<R<DashboardDTO>> {
  85. return http.get<R<DashboardDTO>>('/api/admin/finance/dashboard', { params }).then((r) => r.data);
  86. }
  87. /* ================= 写(用户可操作;对齐旧 PHP 财务方法) ================= */
  88. /** 资金工单 DTO(端点 12) */
  89. export interface BalanceRequestDTO {
  90. id: number;
  91. bizNo: string;
  92. type: number;
  93. userId: string;
  94. institutionId: number;
  95. campusId: number;
  96. money: string;
  97. approved: number; // 0 待审 / 1 通过 / -1 驳回 / -2 撤销
  98. applicantId: string;
  99. approverId: string;
  100. remark: string;
  101. /** 申请时间(RFC3339;v1.6 B2:requestTime ← request_time) */
  102. requestTime: string;
  103. /** 审批时间(RFC3339;未审批时 null) */
  104. responseTime: string | null;
  105. }
  106. /** 工单列表(端点 12,GET /api/admin/finance/requests)。 */
  107. export function listRequests(params: {
  108. institutionId: number;
  109. campusId?: number;
  110. type?: number;
  111. approved?: number;
  112. userId?: string;
  113. page?: number;
  114. pageSize?: number;
  115. }): Promise<PageR<BalanceRequestDTO>> {
  116. return http.get<PageR<BalanceRequestDTO>>('/api/admin/finance/requests', { params }).then((r) => r.data);
  117. }
  118. /** 充值(端点 3,POST /api/admin/finance/recharges)。入参:userId+campusId+money+requestId。 */
  119. export function recharge(body: {
  120. userId: string;
  121. campusId: number;
  122. money: string;
  123. requestId: string;
  124. institutionId: number;
  125. }): Promise<R<{ logId: number; balance: string; repeated: boolean }>> {
  126. return http.post<R<{ logId: number; balance: string; repeated: boolean }>>('/api/admin/finance/recharges', body).then((r) => r.data);
  127. }
  128. /** 消费(端点 4,POST /api/admin/finance/expenditures)。 */
  129. export function expenditure(body: {
  130. userId: string;
  131. campusId: number;
  132. type?: number;
  133. money: string;
  134. institutionId: number;
  135. }): Promise<R<{ logId: number; balance: string }>> {
  136. return http.post<R<{ logId: number; balance: string }>>('/api/admin/finance/expenditures', body).then((r) => r.data);
  137. }
  138. /** 退费(端点 5,POST /api/admin/finance/refunds)。余额≤0 → 3102。 */
  139. export function refund(body: {
  140. userId: string;
  141. campusId: number;
  142. money: string;
  143. institutionId: number;
  144. }): Promise<R<{ logId: number; balance: string; changed: boolean }>> {
  145. return http.post<R<{ logId: number; balance: string; changed: boolean }>>('/api/admin/finance/refunds', body).then((r) => r.data);
  146. }
  147. /** 自定义退费结果(端点 6 §3.1;可退不足 → 3103 warn + badDebt;校区不存在 → 3104) */
  148. export interface CustomRefundResult {
  149. requested: string;
  150. refunded: string;
  151. badDebt: string;
  152. refundableAmount: string;
  153. balance: string;
  154. totalBalance: string;
  155. totalIncome: string;
  156. specialExpenses: string;
  157. logId207: number;
  158. logId108: number | null;
  159. expiredActivityIds: number[];
  160. warnings: { code: number; message: string }[];
  161. repeated: boolean;
  162. }
  163. /** 自定义退费(端点 6,POST /api/admin/finance/refunds/custom)。 */
  164. export function customRefund(body: {
  165. userId: string;
  166. campusId: number;
  167. money: string;
  168. requestId: string;
  169. institutionId: number;
  170. }): Promise<R<CustomRefundResult>> {
  171. return http.post<R<CustomRefundResult>>('/api/admin/finance/refunds/custom', body).then((r) => r.data);
  172. }
  173. /** 转结(端点 7,POST /api/admin/finance/transfers;校区间转账)。 */
  174. export function transfer(body: {
  175. userId: string;
  176. fromCampusId: number;
  177. toCampusId: number;
  178. money: string;
  179. institutionId: number;
  180. }): Promise<R<{ logId106: number; logId206: number }>> {
  181. return http.post<R<{ logId106: number; logId206: number }>>('/api/admin/finance/transfers', body).then((r) => r.data);
  182. }
  183. /** 坏账(端点 8,POST /api/admin/finance/bad-debts)。 */
  184. export function badDebt(body: {
  185. userId: string;
  186. campusId: number;
  187. money: string;
  188. requestId: string;
  189. institutionId: number;
  190. }): Promise<R<{ logId: number; specialExpenses: string; repeated: boolean }>> {
  191. return http.post<R<{ logId: number; specialExpenses: string; repeated: boolean }>>('/api/admin/finance/bad-debts', body).then((r) => r.data);
  192. }
  193. /** 余额清零结果(端点 9 §3.2;前置不满足 → 3120) */
  194. export interface ClearBalanceResult {
  195. cleared: string;
  196. campusBalanceBefore: string;
  197. totalBalanceBefore: string;
  198. balance: string;
  199. totalBalance: string;
  200. specialExpenses: string;
  201. logId208: number;
  202. expiredActivityIds: number[];
  203. repeated: boolean;
  204. }
  205. /** 余额清零(端点 9,POST /api/admin/finance/cleared-balances)。 */
  206. export function clearBalance(body: { userId: string; campusId: number; institutionId: number }): Promise<R<ClearBalanceResult>> {
  207. return http.post<R<ClearBalanceResult>>('/api/admin/finance/cleared-balances', body).then((r) => r.data);
  208. }
  209. /** spend 扣减(端点 10,POST /api/admin/finance/spends;课时扣费;v1.6 B1:旧库只 UPDATE spend 列不写流水 → 仅回 `{spend}`,无 logId)。 */
  210. export function spend(body: { userId: string; campusId: number; spendMoney: string; institutionId: number }): Promise<R<{ spend: string }>> {
  211. return http.post<R<{ spend: string }>>('/api/admin/finance/spends', body).then((r) => r.data);
  212. }
  213. /** 建工单(端点 13,POST /api/admin/finance/requests)。 */
  214. export function createRequest(body: {
  215. type: number;
  216. userId: string;
  217. campusId: number;
  218. money: string;
  219. remark?: string;
  220. institutionId: number;
  221. }): Promise<R<{ id: number; bizNo: string }>> {
  222. return http.post<R<{ id: number; bizNo: string }>>('/api/admin/finance/requests', body).then((r) => r.data);
  223. }
  224. /** 审批通过(端点 14,POST /api/admin/finance/requests/:id/approve)。已处理 → 3140。 */
  225. export function approveRequest(id: number, params: { institutionId: number; campusId?: number }): Promise<R<{ approved: number; result: { logId: number; balance: string } }>> {
  226. return http.post<R<{ approved: number; result: { logId: number; balance: string } }>>(`/api/admin/finance/requests/${id}/approve`, {}, { params }).then((r) => r.data);
  227. }
  228. /** 驳回(端点 15,POST /api/admin/finance/requests/:id/reject)。 */
  229. export function rejectRequest(id: number, params: { institutionId: number; campusId?: number }): Promise<R<{ approved: number }>> {
  230. return http.post<R<{ approved: number }>>(`/api/admin/finance/requests/${id}/reject`, {}, { params }).then((r) => r.data);
  231. }
  232. /** 撤销(端点 16,POST /api/admin/finance/requests/:id/cancel;仅待审)。 */
  233. export function cancelRequest(id: number, params: { institutionId: number; campusId?: number }): Promise<R<{ approved: number }>> {
  234. return http.post<R<{ approved: number }>>(`/api/admin/finance/requests/${id}/cancel`, {}, { params }).then((r) => r.data);
  235. }
  236. /* ================= 收支记录 / 收支字典(端点 60;v1.9 三条修正) ================= */
  237. /** 收支记录 DTO(端点 60 列表行;记录表无 type/name/direction 列——type/name 由后端 JOIN 字典下发)。 */
  238. export interface ExpenditureRecordDTO {
  239. /** 记录 id */
  240. id: number;
  241. /** 收支字典 id(← expenditure_id;编辑表单回填用) */
  242. expenditureId: number;
  243. /** 收支方向编码:1 = 支出 / 2 = 收入(用 type 判方向,两表无 direction 列) */
  244. type: number;
  245. /** 收支类型名(JOIN 字典 t_expenditure.name) */
  246. name: string;
  247. /** 金额(字符串两位小数) */
  248. money: string;
  249. /** 记账人(← create_user_id;语义 = 记账人,非"收付对象";可空) */
  250. createUserId: string | null;
  251. campusId: number;
  252. /** 业务单号 */
  253. bizNo: string;
  254. /** 业务时间(RFC3339) */
  255. createTime: string;
  256. }
  257. /** 收支记录查询参数(作用域由 useScope().toQuery() 注入;type 1 支出 / 2 收入) */
  258. export type ExpenditureQuery = {
  259. institutionId: number;
  260. campusId?: number;
  261. type?: number;
  262. page?: number;
  263. pageSize?: number;
  264. };
  265. /** 收支记录分页列表(端点 60,GET /api/admin/expenditures)。 */
  266. export function listExpenditures(params: ExpenditureQuery): Promise<PageR<ExpenditureRecordDTO>> {
  267. return http.get<PageR<ExpenditureRecordDTO>>('/api/admin/expenditures', { params }).then((r) => r.data);
  268. }
  269. /** 收支类型字典项(端点 60 GET /types 行) */
  270. export interface ExpenditureTypeDTO {
  271. /** 字典 id(记录 expenditureId 指向它) */
  272. id: number;
  273. /** 收支方向编码:1 = 支出 / 2 = 收入 */
  274. type: number;
  275. name: string;
  276. /** 父级字典 id(0 = 顶级) */
  277. parentId: number;
  278. }
  279. /** 收支类型字典(端点 60,GET /api/admin/expenditures/types)。
  280. * ⚠ POST /types(新增收支类型)本轮不做——需要时提需求。 */
  281. export function fetchExpenditureTypes(params: { institutionId: number; campusId?: number }): Promise<R<{ types: ExpenditureTypeDTO[] }>> {
  282. return http.get<R<{ types: ExpenditureTypeDTO[] }>>('/api/admin/expenditures/types', { params }).then((r) => r.data);
  283. }
  284. /** 新增收支记录(端点 60,POST /api/admin/expenditures)→ {id, bizNo}。
  285. * expenditureId 指向字典(type/name 由后端 JOIN 字典得出,前端不传);createUserId 可空。 */
  286. export function createExpenditure(body: {
  287. expenditureId: number;
  288. money: string;
  289. campusId: number;
  290. createUserId?: string | null;
  291. institutionId: number;
  292. }): Promise<R<{ id: number; bizNo: string }>> {
  293. return http.post<R<{ id: number; bizNo: string }>>('/api/admin/expenditures', body).then((r) => r.data);
  294. }