| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 |
- from typing import Annotated
- from fastapi import APIRouter, Depends, Path, Query
- from fastapi.responses import JSONResponse
- from app.api.v1.module_system.auth.schema import AuthSchema
- from app.common.response import ResponseSchema, SuccessResponse
- from app.core.dependencies import AuthPermission
- from app.core.logger import log
- from app.core.router_class import OperationLogRoute
- from .schema import (
- AdjustQuotaSchema,
- ExpenseQuotaCreateSchema,
- ExpenseQuotaDeleteSchema,
- ExpenseQuotaModifySchema,
- ExpenseQuotaQuerySchema,
- IssueBatchCancelOutSchema,
- IssueBatchCancelSchema,
- IssueBatchCreateOutSchema,
- IssueBatchCreateSchema,
- IssueBatchListOutSchema,
- IssueBatchRecordsQueryOutSchema,
- IssueBatchRecordsQuerySchema,
- QuotaCreateSchema,
- QuotaListOutSchema,
- QuotaOperationOutSchema,
- QuotaOutSchema,
- QuotaUpdateSchema,
- )
- from .service import QuotaService
- from .outsource_schema import OutsourceNotifySchema, OutsourceNotifyOutSchema
- from .outsource_service import OutsourceNotifyService
- QuotaRouter = APIRouter(
- route_class=OperationLogRoute,
- prefix="/quota",
- tags=["额度管理"],
- )
- @QuotaRouter.post(
- "/expense/create",
- summary="创建余额/点券",
- description="创建余额或点券 (alipay.ebpp.invoice.expensecontrol.quota.create)",
- response_model=ResponseSchema[QuotaOperationOutSchema],
- )
- async def create_expense_quota_controller(
- data: ExpenseQuotaCreateSchema,
- auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:expense:create"]))],
- ) -> JSONResponse:
- result = await QuotaService.create_expense_quota_service(auth=auth, data=data)
- log.info(f"创建余额/点券成功: out_biz_no={result.out_biz_no}")
- return SuccessResponse(data=result, msg="创建余额/点券成功")
- @QuotaRouter.post(
- "/expense/query",
- summary="查询余额/点券",
- description="查询余额或点券 (alipay.ebpp.invoice.expensecontrol.quota.query)",
- response_model=ResponseSchema[ExpenseQuotaQuerySchema],
- )
- async def query_expense_quota_controller(
- data: ExpenseQuotaQuerySchema,
- auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:expense:query"]))],
- ) -> JSONResponse:
- result = await QuotaService.query_expense_quota_service(auth=auth, data=data)
- log.info(f"查询余额/点券成功")
- return SuccessResponse(data=result, msg="查询余额/点券成功")
- @QuotaRouter.put(
- "/expense/{out_biz_no}",
- summary="修改余额/点券",
- description="修改余额或点券 (alipay.ebpp.invoice.expensecontrol.quota.modify)",
- response_model=ResponseSchema[QuotaOperationOutSchema],
- )
- async def modify_expense_quota_controller(
- out_biz_no: Annotated[str, Path(description="外部业务编号")],
- data: ExpenseQuotaModifySchema,
- auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:expense:modify"]))],
- ) -> JSONResponse:
- result = await QuotaService.modify_expense_quota_service(auth=auth, out_biz_no=out_biz_no, data=data)
- log.info(f"修改余额/点券成功: {out_biz_no}")
- return SuccessResponse(data=result, msg="修改余额/点券成功")
- @QuotaRouter.delete(
- "/expense/{out_biz_no}",
- summary="删除额度",
- description="删除额度 (alipay.ebpp.invoice.expensecontrol.quota.delete)",
- response_model=ResponseSchema[QuotaOperationOutSchema],
- )
- async def delete_expense_quota_controller(
- out_biz_no: Annotated[str, Path(description="外部业务编号")],
- data: ExpenseQuotaDeleteSchema,
- auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:expense:delete"]))],
- ) -> JSONResponse:
- result = await QuotaService.delete_expense_quota_service(auth=auth, out_biz_no=out_biz_no, data=data)
- log.info(f"删除额度成功: {out_biz_no}")
- return SuccessResponse(data=result, msg="删除额度成功")
- @QuotaRouter.post(
- "",
- summary="创建额度",
- description="创建额度",
- response_model=ResponseSchema[QuotaOperationOutSchema],
- )
- async def create_quota_controller(
- data: QuotaCreateSchema,
- auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:create"]))],
- ) -> JSONResponse:
- result = await QuotaService.create_quota_service(auth=auth, data=data)
- log.info(f"创建额度成功: out_biz_no={result.out_biz_no}")
- return SuccessResponse(data=result, msg="创建额度成功")
- @QuotaRouter.get(
- "",
- summary="查询额度列表",
- description="分页查询额度列表",
- response_model=ResponseSchema[QuotaListOutSchema],
- )
- async def list_quota_controller(
- auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:list"]))],
- page_no: Annotated[int, Query(description="页码")] = 1,
- page_size: Annotated[int, Query(description="每页数量")] = 20,
- institution_id: Annotated[str | None, Query(description="制度ID")] = None,
- employee_id: Annotated[str | None, Query(description="员工ID")] = None,
- ) -> JSONResponse:
- search = {}
- if institution_id:
- search["institution_id"] = institution_id
- if employee_id:
- search["employee_id"] = employee_id
- result = await QuotaService.list_service(
- auth=auth, page_no=page_no, page_size=page_size, search=search
- )
- return SuccessResponse(data=result, msg="查询额度列表成功")
- @QuotaRouter.get(
- "/{quota_id}",
- summary="查询额度详情",
- description="根据额度ID查询额度详情",
- )
- async def detail_quota_controller(
- quota_id: Annotated[str, Path(description="额度ID")],
- auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:detail"]))],
- ) -> JSONResponse:
- from .model import QuotaModel
- from sqlalchemy import select, update as sa_update
- stmt = select(QuotaModel).where(QuotaModel.quota_id == quota_id)
- result = await auth.db.execute(stmt)
- quota = result.scalar_one_or_none()
- # quota_id 未回填时按数据库 id 查找
- if not quota:
- try:
- local_id = int(quota_id)
- stmt = select(QuotaModel).where(QuotaModel.id == local_id)
- result = await auth.db.execute(stmt)
- quota = result.scalar_one_or_none()
- except (ValueError, TypeError):
- pass
- if quota:
- return SuccessResponse(data={
- "id": quota.id,
- "quota_id": quota.quota_id,
- "employee_id": quota.employee_id,
- "institution_id": quota.institution_id,
- "out_biz_no": quota.out_biz_no,
- "quota_type": quota.quota_type,
- "target_type": quota.target_type,
- "target_id": quota.target_id,
- "total_amount": float(quota.total_amount) if quota.total_amount else 0,
- "available_amount": float(quota.available_amount) if quota.available_amount else 0,
- "status": quota.status,
- "valid_from": quota.valid_from.strftime("%Y-%m-%d %H:%M:%S") if quota.valid_from else None,
- "valid_to": quota.valid_to.strftime("%Y-%m-%d %H:%M:%S") if quota.valid_to else None,
- "created_time": str(quota.created_time) if quota.created_time else None,
- "updated_time": str(quota.updated_time) if quota.updated_time else None,
- "enterprise_id": quota.enterprise_id,
- }, msg="查询额度详情成功")
- # 本地未找到时,回查支付宝
- try:
- from alipay.aop.api.request.AlipayEbppInvoiceExpensecontrolQuotaQueryRequest import (
- AlipayEbppInvoiceExpensecontrolQuotaQueryRequest,
- )
- from alipay.aop.api.domain.AlipayEbppInvoiceExpensecontrolQuotaQueryModel import (
- AlipayEbppInvoiceExpensecontrolQuotaQueryModel,
- )
- from alipay.aop.api.response.AlipayEbppInvoiceExpensecontrolQuotaQueryResponse import (
- AlipayEbppInvoiceExpensecontrolQuotaQueryResponse,
- )
- from app.core.alipay_client import AlipayClient
- alipay_model = AlipayEbppInvoiceExpensecontrolQuotaQueryModel()
- alipay_model.owner_type = "ENTERPRISE_PAY_UID"
- alipay_model.quota_id_list = [quota_id]
- alipay_model.page_size = 1
- alipay_model.page_num = 1
- request = AlipayEbppInvoiceExpensecontrolQuotaQueryRequest()
- request.biz_model = alipay_model
- client = AlipayClient.get_client()
- response = client.execute(request)
- if response:
- alipay_result = AlipayEbppInvoiceExpensecontrolQuotaQueryResponse()
- alipay_result.parse_response_content(response)
- if alipay_result.is_success() and hasattr(alipay_result, 'quota_detail_info_list') and alipay_result.quota_detail_info_list:
- q = alipay_result.quota_detail_info_list[0]
- return SuccessResponse(data={
- "quota_id": getattr(q, 'quota_id', None),
- "target_type": getattr(q, 'target_type', None),
- "target_id": getattr(q, 'target_id', None),
- "quota_type": getattr(q, 'quota_type', None),
- "total_amount": float(getattr(q, 'total_amount', 0)) if getattr(q, 'total_amount', None) else 0,
- "available_amount": float(getattr(q, 'available_amount', 0)) if getattr(q, 'available_amount', None) else 0,
- "frozen_amount": float(getattr(q, 'frozen_amount', 0)) if getattr(q, 'frozen_amount', None) else 0,
- "consumed_amount": float(getattr(q, 'consumed_amount', 0)) if getattr(q, 'consumed_amount', None) else 0,
- "status": getattr(q, 'status', "QUOTA_ACTIVE"),
- "valid_from": getattr(q, 'effective_start_date', None),
- "valid_to": getattr(q, 'effective_end_date', None),
- "employee_id": getattr(q, 'owner_id', None),
- "owner_id": getattr(q, 'owner_id', None),
- "owner_open_id": getattr(q, 'owner_open_id', None),
- "owner_type": getattr(q, 'owner_type', None),
- }, msg="查询额度详情成功(支付宝)")
- except Exception as e:
- log.warning(f"查询支付宝额度详情失败: {e}")
- return SuccessResponse(data=None, msg="额度不存在")
- # ========================
- # 手工批量发放额度
- # ========================
- @QuotaRouter.get(
- "/issuebatch/list",
- summary="查询手工发放批次列表",
- description="分页查询手工发放批次列表",
- )
- async def list_issue_batch_controller(
- auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:issuebatch:list"]))],
- page_no: Annotated[int, Query(description="页码")] = 1,
- page_size: Annotated[int, Query(description="每页数量")] = 20,
- institution_id: Annotated[str | None, Query(description="制度ID")] = None,
- ) -> JSONResponse:
- search = {}
- if institution_id:
- search["institution_id"] = institution_id
- result = await QuotaService.list_batch_service(
- auth=auth, page_no=page_no, page_size=page_size, search=search
- )
- return SuccessResponse(data=result, msg="查询批次列表成功")
- @QuotaRouter.post(
- "/issuebatch/create",
- summary="手工批量发放额度",
- description="批量对企业下的员工进行额度发放 (alipay.ebpp.invoice.expensecontrol.issuebatch.create)",
- response_model=ResponseSchema[IssueBatchCreateOutSchema],
- )
- async def issue_batch_create_controller(
- data: IssueBatchCreateSchema,
- auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:issuebatch:create"]))],
- ) -> JSONResponse:
- result = await QuotaService.issue_batch_create_service(auth=auth, data=data)
- log.info(f"手工批量发放额度成功: batch_no={data.batch_no}, issue_batch_id={result.issue_batch_id}")
- return SuccessResponse(data=result, msg="手工批量发放额度成功")
- @QuotaRouter.post(
- "/issuebatch/cancel",
- summary="作废手工发放批次",
- description="作废当前批次下发放的额度 (alipay.ebpp.invoice.expensecontrol.issuebatch.cancel)",
- response_model=ResponseSchema[IssueBatchCancelOutSchema],
- )
- async def issue_batch_cancel_controller(
- data: IssueBatchCancelSchema,
- auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:issuebatch:cancel"]))],
- ) -> JSONResponse:
- result = await QuotaService.issue_batch_cancel_service(auth=auth, data=data)
- log.info(f"作废手工发放批次成功: issue_batch_id={data.issue_batch_id}")
- return SuccessResponse(data=result, msg="作废手工发放批次成功")
- @QuotaRouter.post(
- "/issuebatch/records",
- summary="查询手工发放发放明细",
- description="根据批次号分页查询手工发放的发放明细 (alipay.ebpp.invoice.issuebatch.issuerecords.batchquery)",
- response_model=ResponseSchema[IssueBatchRecordsQueryOutSchema],
- )
- async def issue_batch_records_query_controller(
- data: IssueBatchRecordsQuerySchema,
- auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:issuebatch:records"]))],
- ) -> JSONResponse:
- result = await QuotaService.issue_batch_records_query_service(auth=auth, data=data)
- log.info(f"查询手工发放发放明细成功: issue_batch_id={data.issue_batch_id}")
- return SuccessResponse(data=result, msg="查询手工发放发放明细成功")
- @QuotaRouter.post(
- "/outsource/notify",
- summary="外部消费额度同步",
- description="将外部消费同步到支付宝额度系统 (alipay.ebpp.invoice.expensecomsue.outsource.notify)",
- response_model=ResponseSchema[OutsourceNotifyOutSchema],
- )
- async def outsource_notify_controller(
- data: OutsourceNotifySchema,
- auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:outsource:notify"]))],
- ) -> JSONResponse:
- result = await OutsourceNotifyService.notify_service(auth=auth, data=data)
- log.info(f"外部消费额度同步: out_source_id={result.out_source_id}, success={result.success}")
- return SuccessResponse(data=result, msg="外部消费额度同步成功" if result.success else "外部消费额度同步失败")
- @QuotaRouter.get(
- "/employee/{employee_id}/records",
- summary="查询员工额度发放记录",
- description="查询该员工在指定/所有制度下的额度发放记录",
- )
- async def list_employee_quota_records_controller(
- employee_id: Annotated[str, Path(description="员工ID")],
- auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:list"]))],
- institution_id: Annotated[str | None, Query(description="制度ID")] = None,
- ) -> JSONResponse:
- items = await QuotaService.list_employee_quota_records_service(
- auth=auth, employee_id=employee_id, institution_id=institution_id,
- )
- return SuccessResponse(data={"items": items, "total": len(items)}, msg="查询员工额度记录成功")
- @QuotaRouter.post(
- "/{quota_id}/adjust",
- summary="调整额度金额",
- description="调整额度可用金额 (alipay.ebpp.invoice.expensecontrol.quota.modify)",
- )
- async def adjust_quota_controller(
- quota_id: Annotated[str, Path(description="额度ID")],
- data: AdjustQuotaSchema,
- auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:update"]))],
- ) -> JSONResponse:
- result = await QuotaService.adjust_quota_service(auth=auth, data=data)
- log.info(f"调整额度成功: quota_id={quota_id}, before={result['before_amount']}, after={result['after_amount']}")
- return SuccessResponse(data=result, msg="调整额度成功")
- @QuotaRouter.get(
- "/{quota_id}/changes",
- summary="查询额度变更记录",
- description="查询该额度的所有变更记录",
- )
- async def list_quota_changes_controller(
- quota_id: Annotated[str, Path(description="额度ID")],
- auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:expense:quota:detail"]))],
- ) -> JSONResponse:
- items = await QuotaService.list_quota_changes_service(auth=auth, quota_id=quota_id)
- return SuccessResponse(data={"items": items, "total": len(items)}, msg="查询变更记录成功")
|