controller.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. from typing import Annotated
  2. from fastapi import APIRouter, Depends, Path, Query
  3. from fastapi.responses import JSONResponse
  4. from app.api.v1.module_system.auth.schema import AuthSchema
  5. from app.common.response import ResponseSchema, SuccessResponse
  6. from app.core.dependencies import AuthPermission
  7. from app.core.logger import log
  8. from app.core.router_class import OperationLogRoute
  9. from .schema import (
  10. AdjustQuotaSchema,
  11. ExpenseQuotaCreateSchema,
  12. ExpenseQuotaDeleteSchema,
  13. ExpenseQuotaModifySchema,
  14. ExpenseQuotaQuerySchema,
  15. IssueBatchCancelOutSchema,
  16. IssueBatchCancelSchema,
  17. IssueBatchCreateOutSchema,
  18. IssueBatchCreateSchema,
  19. IssueBatchListOutSchema,
  20. IssueBatchRecordsQueryOutSchema,
  21. IssueBatchRecordsQuerySchema,
  22. QuotaCreateSchema,
  23. QuotaListOutSchema,
  24. QuotaOperationOutSchema,
  25. QuotaOutSchema,
  26. QuotaUpdateSchema,
  27. )
  28. from .service import QuotaService
  29. from .outsource_schema import OutsourceNotifySchema, OutsourceNotifyOutSchema
  30. from .outsource_service import OutsourceNotifyService
  31. QuotaRouter = APIRouter(
  32. route_class=OperationLogRoute,
  33. prefix="/quota",
  34. tags=["额度管理"],
  35. )
  36. @QuotaRouter.post(
  37. "/expense/create",
  38. summary="创建余额/点券",
  39. description="创建余额或点券 (alipay.ebpp.invoice.expensecontrol.quota.create)",
  40. response_model=ResponseSchema[QuotaOperationOutSchema],
  41. )
  42. async def create_expense_quota_controller(
  43. data: ExpenseQuotaCreateSchema,
  44. auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:expense:create"]))],
  45. ) -> JSONResponse:
  46. result = await QuotaService.create_expense_quota_service(auth=auth, data=data)
  47. log.info(f"创建余额/点券成功: out_biz_no={result.out_biz_no}")
  48. return SuccessResponse(data=result, msg="创建余额/点券成功")
  49. @QuotaRouter.post(
  50. "/expense/query",
  51. summary="查询余额/点券",
  52. description="查询余额或点券 (alipay.ebpp.invoice.expensecontrol.quota.query)",
  53. response_model=ResponseSchema[ExpenseQuotaQuerySchema],
  54. )
  55. async def query_expense_quota_controller(
  56. data: ExpenseQuotaQuerySchema,
  57. auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:expense:query"]))],
  58. ) -> JSONResponse:
  59. result = await QuotaService.query_expense_quota_service(auth=auth, data=data)
  60. log.info(f"查询余额/点券成功")
  61. return SuccessResponse(data=result, msg="查询余额/点券成功")
  62. @QuotaRouter.put(
  63. "/expense/{out_biz_no}",
  64. summary="修改余额/点券",
  65. description="修改余额或点券 (alipay.ebpp.invoice.expensecontrol.quota.modify)",
  66. response_model=ResponseSchema[QuotaOperationOutSchema],
  67. )
  68. async def modify_expense_quota_controller(
  69. out_biz_no: Annotated[str, Path(description="外部业务编号")],
  70. data: ExpenseQuotaModifySchema,
  71. auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:expense:modify"]))],
  72. ) -> JSONResponse:
  73. result = await QuotaService.modify_expense_quota_service(auth=auth, out_biz_no=out_biz_no, data=data)
  74. log.info(f"修改余额/点券成功: {out_biz_no}")
  75. return SuccessResponse(data=result, msg="修改余额/点券成功")
  76. @QuotaRouter.delete(
  77. "/expense/{out_biz_no}",
  78. summary="删除额度",
  79. description="删除额度 (alipay.ebpp.invoice.expensecontrol.quota.delete)",
  80. response_model=ResponseSchema[QuotaOperationOutSchema],
  81. )
  82. async def delete_expense_quota_controller(
  83. out_biz_no: Annotated[str, Path(description="外部业务编号")],
  84. data: ExpenseQuotaDeleteSchema,
  85. auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:expense:delete"]))],
  86. ) -> JSONResponse:
  87. result = await QuotaService.delete_expense_quota_service(auth=auth, out_biz_no=out_biz_no, data=data)
  88. log.info(f"删除额度成功: {out_biz_no}")
  89. return SuccessResponse(data=result, msg="删除额度成功")
  90. @QuotaRouter.post(
  91. "",
  92. summary="创建额度",
  93. description="创建额度",
  94. response_model=ResponseSchema[QuotaOperationOutSchema],
  95. )
  96. async def create_quota_controller(
  97. data: QuotaCreateSchema,
  98. auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:create"]))],
  99. ) -> JSONResponse:
  100. result = await QuotaService.create_quota_service(auth=auth, data=data)
  101. log.info(f"创建额度成功: out_biz_no={result.out_biz_no}")
  102. return SuccessResponse(data=result, msg="创建额度成功")
  103. @QuotaRouter.get(
  104. "",
  105. summary="查询额度列表",
  106. description="分页查询额度列表",
  107. response_model=ResponseSchema[QuotaListOutSchema],
  108. )
  109. async def list_quota_controller(
  110. auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:list"]))],
  111. page_no: Annotated[int, Query(description="页码")] = 1,
  112. page_size: Annotated[int, Query(description="每页数量")] = 20,
  113. institution_id: Annotated[str | None, Query(description="制度ID")] = None,
  114. employee_id: Annotated[str | None, Query(description="员工ID")] = None,
  115. ) -> JSONResponse:
  116. search = {}
  117. if institution_id:
  118. search["institution_id"] = institution_id
  119. if employee_id:
  120. search["employee_id"] = employee_id
  121. result = await QuotaService.list_service(
  122. auth=auth, page_no=page_no, page_size=page_size, search=search
  123. )
  124. return SuccessResponse(data=result, msg="查询额度列表成功")
  125. @QuotaRouter.get(
  126. "/{quota_id}",
  127. summary="查询额度详情",
  128. description="根据额度ID查询额度详情",
  129. )
  130. async def detail_quota_controller(
  131. quota_id: Annotated[str, Path(description="额度ID")],
  132. auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:detail"]))],
  133. ) -> JSONResponse:
  134. from .model import QuotaModel
  135. from sqlalchemy import select
  136. stmt = select(QuotaModel).where(QuotaModel.quota_id == quota_id)
  137. result = await auth.db.execute(stmt)
  138. quota = result.scalar_one_or_none()
  139. if not quota:
  140. return SuccessResponse(data=None, msg="额度不存在")
  141. return SuccessResponse(data={
  142. "id": quota.id,
  143. "quota_id": quota.quota_id,
  144. "employee_id": quota.employee_id,
  145. "institution_id": quota.institution_id,
  146. "out_biz_no": quota.out_biz_no,
  147. "quota_type": quota.quota_type,
  148. "target_type": quota.target_type,
  149. "target_id": quota.target_id,
  150. "total_amount": float(quota.total_amount) if quota.total_amount else 0,
  151. "available_amount": float(quota.available_amount) if quota.available_amount else 0,
  152. "status": quota.status,
  153. "valid_from": str(quota.valid_from) if quota.valid_from else None,
  154. "valid_to": str(quota.valid_to) if quota.valid_to else None,
  155. "created_time": str(quota.created_time) if quota.created_time else None,
  156. "updated_time": str(quota.updated_time) if quota.updated_time else None,
  157. "enterprise_id": quota.enterprise_id,
  158. }, msg="查询额度详情成功")
  159. # ========================
  160. # 手工批量发放额度
  161. # ========================
  162. @QuotaRouter.get(
  163. "/issuebatch/list",
  164. summary="查询手工发放批次列表",
  165. description="分页查询手工发放批次列表",
  166. )
  167. async def list_issue_batch_controller(
  168. auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:issuebatch:list"]))],
  169. page_no: Annotated[int, Query(description="页码")] = 1,
  170. page_size: Annotated[int, Query(description="每页数量")] = 20,
  171. institution_id: Annotated[str | None, Query(description="制度ID")] = None,
  172. ) -> JSONResponse:
  173. search = {}
  174. if institution_id:
  175. search["institution_id"] = institution_id
  176. result = await QuotaService.list_batch_service(
  177. auth=auth, page_no=page_no, page_size=page_size, search=search
  178. )
  179. return SuccessResponse(data=result, msg="查询批次列表成功")
  180. @QuotaRouter.post(
  181. "/issuebatch/create",
  182. summary="手工批量发放额度",
  183. description="批量对企业下的员工进行额度发放 (alipay.ebpp.invoice.expensecontrol.issuebatch.create)",
  184. response_model=ResponseSchema[IssueBatchCreateOutSchema],
  185. )
  186. async def issue_batch_create_controller(
  187. data: IssueBatchCreateSchema,
  188. auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:issuebatch:create"]))],
  189. ) -> JSONResponse:
  190. result = await QuotaService.issue_batch_create_service(auth=auth, data=data)
  191. log.info(f"手工批量发放额度成功: batch_no={data.batch_no}, issue_batch_id={result.issue_batch_id}")
  192. return SuccessResponse(data=result, msg="手工批量发放额度成功")
  193. @QuotaRouter.post(
  194. "/issuebatch/cancel",
  195. summary="作废手工发放批次",
  196. description="作废当前批次下发放的额度 (alipay.ebpp.invoice.expensecontrol.issuebatch.cancel)",
  197. response_model=ResponseSchema[IssueBatchCancelOutSchema],
  198. )
  199. async def issue_batch_cancel_controller(
  200. data: IssueBatchCancelSchema,
  201. auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:issuebatch:cancel"]))],
  202. ) -> JSONResponse:
  203. result = await QuotaService.issue_batch_cancel_service(auth=auth, data=data)
  204. log.info(f"作废手工发放批次成功: issue_batch_id={data.issue_batch_id}")
  205. return SuccessResponse(data=result, msg="作废手工发放批次成功")
  206. @QuotaRouter.post(
  207. "/issuebatch/records",
  208. summary="查询手工发放发放明细",
  209. description="根据批次号分页查询手工发放的发放明细 (alipay.ebpp.invoice.issuebatch.issuerecords.batchquery)",
  210. response_model=ResponseSchema[IssueBatchRecordsQueryOutSchema],
  211. )
  212. async def issue_batch_records_query_controller(
  213. data: IssueBatchRecordsQuerySchema,
  214. auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:issuebatch:records"]))],
  215. ) -> JSONResponse:
  216. result = await QuotaService.issue_batch_records_query_service(auth=auth, data=data)
  217. log.info(f"查询手工发放发放明细成功: issue_batch_id={data.issue_batch_id}")
  218. return SuccessResponse(data=result, msg="查询手工发放发放明细成功")
  219. @QuotaRouter.post(
  220. "/outsource/notify",
  221. summary="外部消费额度同步",
  222. description="将外部消费同步到支付宝额度系统 (alipay.ebpp.invoice.expensecomsue.outsource.notify)",
  223. response_model=ResponseSchema[OutsourceNotifyOutSchema],
  224. )
  225. async def outsource_notify_controller(
  226. data: OutsourceNotifySchema,
  227. auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:outsource:notify"]))],
  228. ) -> JSONResponse:
  229. result = await OutsourceNotifyService.notify_service(auth=auth, data=data)
  230. log.info(f"外部消费额度同步: out_source_id={result.out_source_id}, success={result.success}")
  231. return SuccessResponse(data=result, msg="外部消费额度同步成功" if result.success else "外部消费额度同步失败")
  232. @QuotaRouter.get(
  233. "/employee/{employee_id}/records",
  234. summary="查询员工额度发放记录",
  235. description="查询该员工在指定/所有制度下的额度发放记录",
  236. )
  237. async def list_employee_quota_records_controller(
  238. employee_id: Annotated[str, Path(description="员工ID")],
  239. auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:list"]))],
  240. institution_id: Annotated[str | None, Query(description="制度ID")] = None,
  241. ) -> JSONResponse:
  242. items = await QuotaService.list_employee_quota_records_service(
  243. auth=auth, employee_id=employee_id, institution_id=institution_id,
  244. )
  245. return SuccessResponse(data={"items": items, "total": len(items)}, msg="查询员工额度记录成功")
  246. @QuotaRouter.post(
  247. "/{quota_id}/adjust",
  248. summary="调整额度金额",
  249. description="调整额度可用金额 (alipay.ebpp.invoice.expensecontrol.quota.modify)",
  250. )
  251. async def adjust_quota_controller(
  252. quota_id: Annotated[str, Path(description="额度ID")],
  253. data: AdjustQuotaSchema,
  254. auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:update"]))],
  255. ) -> JSONResponse:
  256. result = await QuotaService.adjust_quota_service(auth=auth, data=data)
  257. log.info(f"调整额度成功: quota_id={quota_id}, before={result['before_amount']}, after={result['after_amount']}")
  258. return SuccessResponse(data=result, msg="调整额度成功")
  259. @QuotaRouter.get(
  260. "/{quota_id}/changes",
  261. summary="查询额度变更记录",
  262. description="查询该额度的所有变更记录",
  263. )
  264. async def list_quota_changes_controller(
  265. quota_id: Annotated[str, Path(description="额度ID")],
  266. auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:detail"]))],
  267. ) -> JSONResponse:
  268. items = await QuotaService.list_quota_changes_service(auth=auth, quota_id=quota_id)
  269. return SuccessResponse(data={"items": items, "total": len(items)}, msg="查询变更记录成功")