vehicle_urea.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. """车辆尿素 API"""
  2. from datetime import date, datetime
  3. from typing import Any, List, Optional
  4. from pydantic import BaseModel, Field
  5. from fastapi import APIRouter, Body, Depends, HTTPException, Query, Request, status
  6. from sqlalchemy.orm import Session
  7. from app.core.database import get_db
  8. from app.core.db_auth import DbUserContext, get_db_current_user
  9. from app.core.tenant_context import get_tenant_no
  10. from app.schemas.pl.cancel_reason import CancelReasonBody
  11. from app.utils.excel_export import (
  12. EXPORT_MAX_ROWS,
  13. ExportColumn,
  14. ExportValidationError,
  15. export_rows_to_xlsx_response,
  16. filter_export_columns,
  17. )
  18. from app.schemas.pl.vehicle_urea import (
  19. VehicleUreaApplyCreate,
  20. VehicleUreaApplyListResponse,
  21. VehicleUreaApplyRead,
  22. VehicleUreaApplyUpdate,
  23. VehicleUreaDisburseBoardListResponse,
  24. VehicleUreaDisburseCreate,
  25. VehicleUreaDisburseRead,
  26. VehicleUreaDisburseUpdate,
  27. )
  28. from app.services import pl_vehicle_urea_service as svc
  29. apply_router = APIRouter(prefix="/vehicle-urea-apply", tags=["运输计划-车辆尿素申请"])
  30. disburse_router = APIRouter(prefix="/vehicle-urea-disburse", tags=["运输计划-车辆尿素发放"])
  31. def _param_get(params: dict, *keys: str) -> Any:
  32. for key in keys:
  33. if key in params and params[key] not in (None, ""):
  34. return params[key]
  35. return None
  36. def _merge_query(request: Request, **declared: Any) -> dict:
  37. params = dict(declared)
  38. for key, value in request.query_params.multi_items():
  39. if key not in params or params[key] in (None, ""):
  40. params[key] = value
  41. return params
  42. def _apply_list_kwargs(params: dict) -> dict:
  43. return dict(
  44. q=_param_get(params, "q"),
  45. apply_no=_param_get(params, "applyNo", "apply_no"),
  46. license_plate=_param_get(params, "licensePlate", "license_plate"),
  47. status=_param_get(params, "status"),
  48. status_in=_param_get(params, "statusIn", "status_in"),
  49. apply_date_from=_param_get(params, "applyDateFrom", "apply_date_from"),
  50. apply_date_to=_param_get(params, "applyDateTo", "apply_date_to"),
  51. warehouse_in_date_from=_param_get(
  52. params, "warehouseInDateFrom", "warehouse_in_date_from"
  53. ),
  54. warehouse_in_date_to=_param_get(
  55. params, "warehouseInDateTo", "warehouse_in_date_to"
  56. ),
  57. applicant_id=_param_get(params, "applicantId", "applicant_id"),
  58. )
  59. def _disburse_list_kwargs(params: dict) -> dict:
  60. return dict(
  61. q=_param_get(params, "q"),
  62. apply_no=_param_get(params, "applyNo", "apply_no"),
  63. license_plate=_param_get(params, "licensePlate", "license_plate"),
  64. apply_status=_param_get(params, "applyStatus", "apply_status"),
  65. apply_status_in=_param_get(params, "applyStatusIn", "apply_status_in"),
  66. apply_date_from=_param_get(params, "applyDateFrom", "apply_date_from"),
  67. apply_date_to=_param_get(params, "applyDateTo", "apply_date_to"),
  68. disburse_date_from=_param_get(params, "disburseDateFrom", "disburse_date_from"),
  69. disburse_date_to=_param_get(params, "disburseDateTo", "disburse_date_to"),
  70. )
  71. @apply_router.get("/", response_model=VehicleUreaApplyListResponse)
  72. def list_applies(
  73. request: Request,
  74. q: Optional[str] = Query(default=None),
  75. status: Optional[str] = Query(default=None),
  76. apply_date_from: Optional[date] = Query(default=None, alias="applyDateFrom"),
  77. apply_date_to: Optional[date] = Query(default=None, alias="applyDateTo"),
  78. applicant_id: Optional[str] = Query(default=None, alias="applicantId"),
  79. page: int = Query(default=1, ge=1),
  80. page_size: int = Query(default=20, ge=1, le=500, alias="pageSize"),
  81. db: Session = Depends(get_db),
  82. user: DbUserContext = Depends(get_db_current_user),
  83. ) -> VehicleUreaApplyListResponse:
  84. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  85. params = _merge_query(
  86. request,
  87. q=q,
  88. status=status,
  89. applyDateFrom=apply_date_from,
  90. applyDateTo=apply_date_to,
  91. applicantId=applicant_id,
  92. )
  93. items, total = svc.list_applies(
  94. db,
  95. tenant_no,
  96. user,
  97. **_apply_list_kwargs(params),
  98. page=page,
  99. page_size=page_size,
  100. )
  101. return VehicleUreaApplyListResponse(
  102. items=items, total=total, page=page, pageSize=page_size
  103. )
  104. UREA_APPLY_EXPORT_COLUMNS = [
  105. ExportColumn(title="申请单号", field="applyNo"),
  106. ExportColumn(title="车牌号", field="licensePlate"),
  107. ExportColumn(title="申请时间", field="applyDate"),
  108. ExportColumn(title="入库时间", field="warehouseInDate"),
  109. ExportColumn(title="单位", field="unit"),
  110. ExportColumn(title="入库数量", field="quantity"),
  111. ExportColumn(title="申请人", field="applicantName"),
  112. ExportColumn(title="状态", field="status"),
  113. ]
  114. class UreaApplyExportBody(BaseModel):
  115. q: Optional[str] = None
  116. status: Optional[str] = None
  117. status_in: Optional[str] = Field(default=None, alias="statusIn")
  118. apply_no: Optional[str] = Field(default=None, alias="applyNo")
  119. license_plate: Optional[str] = Field(default=None, alias="licensePlate")
  120. apply_date_from: Optional[date] = Field(default=None, alias="applyDateFrom")
  121. apply_date_to: Optional[date] = Field(default=None, alias="applyDateTo")
  122. warehouse_in_date_from: Optional[date] = Field(
  123. default=None, alias="warehouseInDateFrom"
  124. )
  125. warehouse_in_date_to: Optional[date] = Field(
  126. default=None, alias="warehouseInDateTo"
  127. )
  128. ids: Optional[List[str]] = None
  129. columns: Optional[List[str]] = None
  130. model_config = {"populate_by_name": True, "extra": "allow"}
  131. @apply_router.post("/export")
  132. def export_urea_applies(
  133. body: UreaApplyExportBody,
  134. db: Session = Depends(get_db),
  135. user: DbUserContext = Depends(get_db_current_user),
  136. ):
  137. try:
  138. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  139. params = body.model_dump(by_alias=True, exclude_none=True)
  140. items, _ = svc.list_applies(
  141. db,
  142. tenant_no,
  143. user,
  144. **_apply_list_kwargs(params),
  145. ids=body.ids,
  146. page=1,
  147. page_size=EXPORT_MAX_ROWS,
  148. max_page_size=EXPORT_MAX_ROWS,
  149. )
  150. rows = []
  151. for item in items:
  152. data = item.model_dump(by_alias=True)
  153. for k, v in list(data.items()):
  154. if hasattr(v, 'isoformat'):
  155. data[k] = str(v)[:19]
  156. rows.append(data)
  157. cols = filter_export_columns(UREA_APPLY_EXPORT_COLUMNS, body.columns)
  158. date_str = datetime.now().strftime("%Y%m%d")
  159. return export_rows_to_xlsx_response(
  160. db, tenant_no=tenant_no, sheet_title="尿素申请", columns=cols, rows=rows,
  161. filename_stem=f"尿素申请_{date_str}",
  162. )
  163. except ExportValidationError as e:
  164. raise HTTPException(status_code=400, detail=str(e)) from e
  165. @apply_router.get("/{id}", response_model=VehicleUreaApplyRead)
  166. def get_apply(
  167. id: str,
  168. db: Session = Depends(get_db),
  169. user: DbUserContext = Depends(get_db_current_user),
  170. ) -> VehicleUreaApplyRead:
  171. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  172. return svc.get_apply(db, tenant_no, user, id)
  173. @apply_router.post("/", response_model=VehicleUreaApplyRead, status_code=status.HTTP_201_CREATED)
  174. def create_apply(
  175. payload: VehicleUreaApplyCreate,
  176. db: Session = Depends(get_db),
  177. user: DbUserContext = Depends(get_db_current_user),
  178. ) -> VehicleUreaApplyRead:
  179. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  180. return svc.create_apply(db, tenant_no, user, payload)
  181. @apply_router.put("/{id}", response_model=VehicleUreaApplyRead)
  182. def update_apply(
  183. id: str,
  184. payload: VehicleUreaApplyUpdate,
  185. db: Session = Depends(get_db),
  186. user: DbUserContext = Depends(get_db_current_user),
  187. ) -> VehicleUreaApplyRead:
  188. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  189. return svc.update_apply(db, tenant_no, user, id, payload)
  190. @apply_router.post("/{id}/cancel", response_model=VehicleUreaApplyRead)
  191. def cancel_apply(
  192. id: str,
  193. body: Optional[CancelReasonBody] = Body(default=None),
  194. db: Session = Depends(get_db),
  195. user: DbUserContext = Depends(get_db_current_user),
  196. ) -> VehicleUreaApplyRead:
  197. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  198. return svc.cancel_apply(
  199. db, tenant_no, user, id, cancel_reason=body.cancel_reason if body else None
  200. )
  201. @apply_router.delete("/{ids}", status_code=status.HTTP_204_NO_CONTENT)
  202. def delete_applies(
  203. ids: str,
  204. db: Session = Depends(get_db),
  205. user: DbUserContext = Depends(get_db_current_user),
  206. ) -> None:
  207. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  208. apply_ids = [item for item in ids.split(",") if item]
  209. svc.delete_applies(db, tenant_no, user, apply_ids)
  210. @disburse_router.get("/disburse-board", response_model=VehicleUreaDisburseBoardListResponse)
  211. def list_disburse_board(
  212. request: Request,
  213. q: Optional[str] = Query(default=None),
  214. apply_status: Optional[str] = Query(default=None, alias="applyStatus"),
  215. apply_date_from: Optional[date] = Query(default=None, alias="applyDateFrom"),
  216. apply_date_to: Optional[date] = Query(default=None, alias="applyDateTo"),
  217. disburse_date_from: Optional[date] = Query(default=None, alias="disburseDateFrom"),
  218. disburse_date_to: Optional[date] = Query(default=None, alias="disburseDateTo"),
  219. page: int = Query(default=1, ge=1),
  220. page_size: int = Query(default=20, ge=1, le=500, alias="pageSize"),
  221. db: Session = Depends(get_db),
  222. user: DbUserContext = Depends(get_db_current_user),
  223. ) -> VehicleUreaDisburseBoardListResponse:
  224. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  225. params = _merge_query(
  226. request,
  227. q=q,
  228. applyStatus=apply_status,
  229. applyDateFrom=apply_date_from,
  230. applyDateTo=apply_date_to,
  231. disburseDateFrom=disburse_date_from,
  232. disburseDateTo=disburse_date_to,
  233. )
  234. items, total = svc.list_disburse_board(
  235. db,
  236. tenant_no,
  237. user,
  238. **_disburse_list_kwargs(params),
  239. page=page,
  240. page_size=page_size,
  241. )
  242. return VehicleUreaDisburseBoardListResponse(
  243. items=items, total=total, page=page, pageSize=page_size
  244. )
  245. UREA_DISBURSE_EXPORT_COLUMNS = [
  246. ExportColumn(title="申请单号", field="applyNo"),
  247. ExportColumn(title="车牌号", field="licensePlate"),
  248. ExportColumn(title="申请时间", field="applyDate"),
  249. ExportColumn(title="入库时间", field="warehouseInDate"),
  250. ExportColumn(title="申请人", field="applicantName"),
  251. ExportColumn(title="单位", field="unit"),
  252. ExportColumn(title="入库数量", field="quantity"),
  253. ExportColumn(title="申请状态", field="applyStatus"),
  254. ExportColumn(title="发放日期", field="disburseDate"),
  255. ExportColumn(title="发放金额", field="amount"),
  256. ]
  257. class UreaDisburseExportBody(BaseModel):
  258. q: Optional[str] = None
  259. apply_status: Optional[str] = Field(default=None, alias="applyStatus")
  260. apply_status_in: Optional[str] = Field(default=None, alias="applyStatusIn")
  261. apply_no: Optional[str] = Field(default=None, alias="applyNo")
  262. license_plate: Optional[str] = Field(default=None, alias="licensePlate")
  263. apply_date_from: Optional[date] = Field(default=None, alias="applyDateFrom")
  264. apply_date_to: Optional[date] = Field(default=None, alias="applyDateTo")
  265. disburse_date_from: Optional[date] = Field(default=None, alias="disburseDateFrom")
  266. disburse_date_to: Optional[date] = Field(default=None, alias="disburseDateTo")
  267. ids: Optional[List[str]] = None
  268. columns: Optional[List[str]] = None
  269. model_config = {"populate_by_name": True, "extra": "allow"}
  270. @disburse_router.post("/disburse-board/export")
  271. def export_urea_disburse_board(
  272. body: UreaDisburseExportBody,
  273. db: Session = Depends(get_db),
  274. user: DbUserContext = Depends(get_db_current_user),
  275. ):
  276. try:
  277. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  278. params = body.model_dump(by_alias=True, exclude_none=True)
  279. items, _ = svc.list_disburse_board(
  280. db,
  281. tenant_no,
  282. user,
  283. **_disburse_list_kwargs(params),
  284. ids=body.ids,
  285. page=1,
  286. page_size=EXPORT_MAX_ROWS,
  287. max_page_size=EXPORT_MAX_ROWS,
  288. )
  289. rows = []
  290. for item in items:
  291. data = item.model_dump(by_alias=True)
  292. for k, v in list(data.items()):
  293. if hasattr(v, 'isoformat'):
  294. data[k] = str(v)[:19]
  295. rows.append(data)
  296. cols = filter_export_columns(UREA_DISBURSE_EXPORT_COLUMNS, body.columns)
  297. date_str = datetime.now().strftime("%Y%m%d")
  298. return export_rows_to_xlsx_response(
  299. db, tenant_no=tenant_no, sheet_title="尿素发放", columns=cols, rows=rows,
  300. filename_stem=f"尿素发放_{date_str}",
  301. )
  302. except ExportValidationError as e:
  303. raise HTTPException(status_code=400, detail=str(e)) from e
  304. @disburse_router.get("/{id}", response_model=VehicleUreaDisburseRead)
  305. def get_disburse(
  306. id: str,
  307. db: Session = Depends(get_db),
  308. user: DbUserContext = Depends(get_db_current_user),
  309. ) -> VehicleUreaDisburseRead:
  310. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  311. return svc.get_disburse(db, tenant_no, user, id)
  312. @disburse_router.post("/", response_model=VehicleUreaDisburseRead, status_code=status.HTTP_201_CREATED)
  313. def create_disburse(
  314. payload: VehicleUreaDisburseCreate,
  315. db: Session = Depends(get_db),
  316. user: DbUserContext = Depends(get_db_current_user),
  317. ) -> VehicleUreaDisburseRead:
  318. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  319. return svc.create_disburse(db, tenant_no, user, payload)
  320. @disburse_router.put("/{id}", response_model=VehicleUreaDisburseRead)
  321. def update_disburse(
  322. id: str,
  323. payload: VehicleUreaDisburseUpdate,
  324. db: Session = Depends(get_db),
  325. user: DbUserContext = Depends(get_db_current_user),
  326. ) -> VehicleUreaDisburseRead:
  327. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  328. return svc.update_disburse(db, tenant_no, user, id, payload)
  329. @disburse_router.delete("/{ids}", status_code=status.HTTP_204_NO_CONTENT)
  330. def delete_disburses(
  331. ids: str,
  332. db: Session = Depends(get_db),
  333. user: DbUserContext = Depends(get_db_current_user),
  334. ) -> None:
  335. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  336. disburse_ids = [item for item in ids.split(",") if item]
  337. svc.delete_disburses(db, tenant_no, user, disburse_ids)