operations.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828
  1. """运营支撑 API(洁净室、进出记录、仓储设施、储位、展厅预约)"""
  2. from datetime import datetime
  3. from typing import Any, Optional
  4. from fastapi import APIRouter, Depends, Query
  5. from pydantic import BaseModel, ConfigDict, Field
  6. from sqlalchemy import or_
  7. from sqlalchemy.orm import Session
  8. from app.core.data_scope import apply_data_scope
  9. from app.core.database import get_db
  10. from app.core.menu_guard import require_admin_or_super_admin
  11. from app.core.response import error_response, success_response
  12. from app.crud import clean_room as clean_room_svc
  13. from app.crud import exhibition_appointment as exhibition_svc
  14. from app.crud import storage_facility as storage_facility_svc
  15. from app.crud import storage_occupancy as occupancy_svc
  16. from app.core.utils import generate_uuid
  17. from app.models.models import BizCleanRoom, BizCleanRoomAccessLog, BizExhibitionAppointment, BizStorageFacility, BizStorageFacilitySlot, SysUser
  18. router = APIRouter(prefix="/web/admin", tags=["运营支撑"])
  19. def _get_tenant_no(db: Session, user: dict) -> str:
  20. user_id = user.get("user_id")
  21. if not user_id:
  22. raise PermissionError("用户身份信息缺失,无法确定租户")
  23. sys_user = db.query(SysUser).filter(SysUser.id == user_id).first()
  24. if not sys_user or not sys_user.tenant_no:
  25. raise PermissionError("用户租户信息缺失")
  26. return sys_user.tenant_no
  27. # ---------------------------------------------------------------------------
  28. # 洁净室
  29. # ---------------------------------------------------------------------------
  30. class CleanRoomCreateBody(BaseModel):
  31. model_config = ConfigDict(populate_by_name=True)
  32. room_code: str = Field(..., alias="roomCode")
  33. room_name: str = Field(..., alias="roomName")
  34. room_grade: Optional[str] = Field(None, alias="roomGrade")
  35. room_status: Optional[str] = Field("zc", alias="roomStatus")
  36. area_sqm: Optional[float] = Field(None, alias="areaSqm")
  37. order_num: Optional[int] = Field(0, alias="orderNum")
  38. remark: Optional[str] = None
  39. class CleanRoomUpdateBody(BaseModel):
  40. model_config = ConfigDict(populate_by_name=True)
  41. room_code: Optional[str] = Field(None, alias="roomCode")
  42. room_name: Optional[str] = Field(None, alias="roomName")
  43. room_grade: Optional[str] = Field(None, alias="roomGrade")
  44. room_status: Optional[str] = Field(None, alias="roomStatus")
  45. area_sqm: Optional[float] = Field(None, alias="areaSqm")
  46. order_num: Optional[int] = Field(None, alias="orderNum")
  47. remark: Optional[str] = None
  48. @router.get("/clean-rooms")
  49. async def list_clean_rooms(
  50. page: int = Query(1, ge=1),
  51. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  52. keyword: Optional[str] = None,
  53. user: dict = Depends(require_admin_or_super_admin),
  54. db: Session = Depends(get_db),
  55. ):
  56. tenant_no = _get_tenant_no(db, user)
  57. query = db.query(BizCleanRoom).filter(
  58. BizCleanRoom.del_flag == 0,
  59. BizCleanRoom.tenant_no == tenant_no,
  60. )
  61. kw = (keyword or "").strip()
  62. if kw:
  63. like = f"%{kw}%"
  64. query = query.filter(or_(BizCleanRoom.room_code.like(like), BizCleanRoom.room_name.like(like)))
  65. query = apply_data_scope(query, BizCleanRoom, user, db, tenant_no)
  66. total = query.count()
  67. items = query.order_by(BizCleanRoom.create_time.desc()).offset((page - 1) * pageSize).limit(pageSize).all()
  68. result_list = [clean_room_svc._row_to_dict(r) for r in items]
  69. tp = clean_room_svc.total_pages(total, pageSize)
  70. return success_response({"list": result_list, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
  71. @router.post("/clean-rooms")
  72. async def create_clean_room(
  73. body: CleanRoomCreateBody,
  74. user: dict = Depends(require_admin_or_super_admin),
  75. db: Session = Depends(get_db),
  76. ):
  77. try:
  78. tenant_no = _get_tenant_no(db, user)
  79. obj = clean_room_svc.create_clean_room(
  80. db,
  81. room_code=body.room_code,
  82. room_name=body.room_name,
  83. room_grade=body.room_grade,
  84. room_status=body.room_status or "zc",
  85. area_sqm=body.area_sqm,
  86. tenant_no=tenant_no,
  87. order_num=body.order_num,
  88. remark=body.remark,
  89. user_id=user.get("user_id"),
  90. )
  91. return success_response(clean_room_svc._row_to_dict(obj), message="创建成功")
  92. except ValueError as e:
  93. return error_response(400, str(e))
  94. @router.put("/clean-rooms/{room_id}")
  95. async def update_clean_room(
  96. room_id: str,
  97. body: CleanRoomUpdateBody,
  98. user: dict = Depends(require_admin_or_super_admin),
  99. db: Session = Depends(get_db),
  100. ):
  101. try:
  102. tenant_no = _get_tenant_no(db, user)
  103. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  104. obj = clean_room_svc.update_clean_room(
  105. db,
  106. room_id,
  107. room_code=payload.get("room_code"),
  108. room_name=payload.get("room_name"),
  109. room_grade=payload.get("room_grade"),
  110. room_status=payload.get("room_status"),
  111. area_sqm=payload.get("area_sqm"),
  112. order_num=payload.get("order_num"),
  113. remark=payload.get("remark"),
  114. user_id=user.get("user_id"),
  115. tenant_no=tenant_no,
  116. )
  117. return success_response(clean_room_svc._row_to_dict(obj), message="更新成功")
  118. except ValueError as e:
  119. return error_response(400, str(e))
  120. @router.delete("/clean-rooms/{room_id}")
  121. async def delete_clean_room(
  122. room_id: str,
  123. user: dict = Depends(require_admin_or_super_admin),
  124. db: Session = Depends(get_db),
  125. ):
  126. try:
  127. tenant_no = _get_tenant_no(db, user)
  128. clean_room_svc.soft_delete_clean_room(db, room_id, tenant_no=tenant_no)
  129. return success_response(None, message="删除成功")
  130. except ValueError as e:
  131. return error_response(400, str(e))
  132. # ---------------------------------------------------------------------------
  133. # 进出记录
  134. # ---------------------------------------------------------------------------
  135. class AccessLogCreateBody(BaseModel):
  136. model_config = ConfigDict(populate_by_name=True)
  137. clean_room_id: str = Field(..., alias="cleanRoomId")
  138. person_name: Optional[str] = Field(None, alias="personName")
  139. dept_name: Optional[str] = Field(None, alias="deptName")
  140. enter_time: Optional[str] = Field(None, alias="enterTime")
  141. purpose: Optional[str] = None
  142. leave_time: Optional[str] = Field(None, alias="leaveTime")
  143. body_temp: Optional[str] = Field(None, alias="bodyTemp")
  144. order_num: Optional[int] = Field(0, alias="orderNum")
  145. remark: Optional[str] = None
  146. class AccessLogUpdateBody(BaseModel):
  147. model_config = ConfigDict(populate_by_name=True)
  148. person_name: Optional[str] = Field(None, alias="personName")
  149. dept_name: Optional[str] = Field(None, alias="deptName")
  150. enter_time: Optional[str] = Field(None, alias="enterTime")
  151. purpose: Optional[str] = None
  152. leave_time: Optional[str] = Field(None, alias="leaveTime")
  153. body_temp: Optional[str] = Field(None, alias="bodyTemp")
  154. order_num: Optional[int] = Field(None, alias="orderNum")
  155. remark: Optional[str] = None
  156. def _parse_optional_dt(value: Optional[str]) -> Optional[datetime]:
  157. if not value:
  158. return None
  159. return datetime.fromisoformat(value)
  160. @router.get("/clean-room-access-logs")
  161. async def list_access_logs(
  162. page: int = Query(1, ge=1),
  163. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  164. cleanRoomId: str = Query(..., alias="cleanRoomId"),
  165. keyword: Optional[str] = None,
  166. user: dict = Depends(require_admin_or_super_admin),
  167. db: Session = Depends(get_db),
  168. ):
  169. tenant_no = _get_tenant_no(db, user)
  170. query = db.query(BizCleanRoomAccessLog).filter(
  171. BizCleanRoomAccessLog.del_flag == 0,
  172. BizCleanRoomAccessLog.tenant_no == tenant_no,
  173. BizCleanRoomAccessLog.clean_room_id == cleanRoomId,
  174. )
  175. if keyword:
  176. kw = f"%{keyword}%"
  177. query = query.filter(
  178. BizCleanRoomAccessLog.person_name.like(kw)
  179. | BizCleanRoomAccessLog.dept_name.like(kw)
  180. )
  181. query = apply_data_scope(query, BizCleanRoomAccessLog, user, db, tenant_no)
  182. total = query.count()
  183. items = query.order_by(BizCleanRoomAccessLog.create_time.desc()).offset((page - 1) * pageSize).limit(pageSize).all()
  184. result_list = [clean_room_svc._access_log_to_dict(r) for r in items]
  185. tp = clean_room_svc.total_pages(total, pageSize)
  186. return success_response({"list": result_list, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
  187. @router.post("/clean-room-access-logs")
  188. async def create_access_log(
  189. body: AccessLogCreateBody,
  190. user: dict = Depends(require_admin_or_super_admin),
  191. db: Session = Depends(get_db),
  192. ):
  193. try:
  194. tenant_no = _get_tenant_no(db, user)
  195. obj = clean_room_svc.create_access_log(
  196. db,
  197. clean_room_id=body.clean_room_id,
  198. person_name=body.person_name,
  199. dept_name=body.dept_name,
  200. enter_time=_parse_optional_dt(body.enter_time),
  201. purpose=body.purpose,
  202. leave_time=_parse_optional_dt(body.leave_time),
  203. body_temp=body.body_temp,
  204. tenant_no=tenant_no,
  205. order_num=body.order_num,
  206. remark=body.remark,
  207. user_id_param=user.get("user_id"),
  208. )
  209. return success_response(clean_room_svc._access_log_to_dict(obj), message="创建成功")
  210. except ValueError as e:
  211. return error_response(400, str(e))
  212. @router.put("/clean-room-access-logs/{log_id}")
  213. async def update_access_log(
  214. log_id: str,
  215. body: AccessLogUpdateBody,
  216. user: dict = Depends(require_admin_or_super_admin),
  217. db: Session = Depends(get_db),
  218. ):
  219. try:
  220. tenant_no = _get_tenant_no(db, user)
  221. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  222. obj = clean_room_svc.update_access_log(
  223. db,
  224. log_id,
  225. person_name=payload.get("person_name"),
  226. dept_name=payload.get("dept_name"),
  227. enter_time=_parse_optional_dt(payload.get("enter_time")) if "enter_time" in payload else None,
  228. purpose=payload.get("purpose"),
  229. leave_time=_parse_optional_dt(payload.get("leave_time")) if "leave_time" in payload else None,
  230. body_temp=payload.get("body_temp"),
  231. order_num=payload.get("order_num"),
  232. remark=payload.get("remark"),
  233. user_id_param=user.get("user_id"),
  234. tenant_no=tenant_no,
  235. )
  236. return success_response(clean_room_svc._access_log_to_dict(obj), message="更新成功")
  237. except ValueError as e:
  238. return error_response(400, str(e))
  239. @router.delete("/clean-room-access-logs/{log_id}")
  240. async def delete_access_log(
  241. log_id: str,
  242. user: dict = Depends(require_admin_or_super_admin),
  243. db: Session = Depends(get_db),
  244. ):
  245. try:
  246. tenant_no = _get_tenant_no(db, user)
  247. clean_room_svc.soft_delete_access_log(db, log_id, tenant_no=tenant_no)
  248. return success_response(None, message="删除成功")
  249. except ValueError as e:
  250. return error_response(400, str(e))
  251. # ---------------------------------------------------------------------------
  252. # 仓储设施
  253. # ---------------------------------------------------------------------------
  254. class StorageFacilityCreateBody(BaseModel):
  255. model_config = ConfigDict(populate_by_name=True)
  256. parent_id: str = Field("0", alias="parentId")
  257. node_name: str = Field(..., alias="nodeName")
  258. node_type: Optional[str] = Field(None, alias="nodeType")
  259. is_leaf: Optional[int] = Field(0, alias="isLeaf")
  260. order_num: Optional[int] = Field(0, alias="orderNum")
  261. remark: Optional[str] = None
  262. class StorageFacilityUpdateBody(BaseModel):
  263. model_config = ConfigDict(populate_by_name=True)
  264. node_name: Optional[str] = Field(None, alias="nodeName")
  265. node_type: Optional[str] = Field(None, alias="nodeType")
  266. is_leaf: Optional[int] = Field(None, alias="isLeaf")
  267. order_num: Optional[int] = Field(None, alias="orderNum")
  268. remark: Optional[str] = None
  269. @router.get("/storage-facilities")
  270. async def list_storage_facilities(
  271. keyword: Optional[str] = None,
  272. user: dict = Depends(require_admin_or_super_admin),
  273. db: Session = Depends(get_db),
  274. ):
  275. tenant_no = _get_tenant_no(db, user)
  276. query = db.query(BizStorageFacility).filter(
  277. BizStorageFacility.del_flag == 0,
  278. BizStorageFacility.tenant_no == tenant_no,
  279. )
  280. if keyword:
  281. query = query.filter(BizStorageFacility.node_name.like(f"%{keyword}%"))
  282. query = apply_data_scope(query, BizStorageFacility, user, db, tenant_no)
  283. items = query.order_by(
  284. BizStorageFacility.order_num.asc(),
  285. BizStorageFacility.create_time.asc(),
  286. ).all()
  287. result_list = [storage_facility_svc._row_to_dict(r) for r in items]
  288. return success_response(result_list)
  289. @router.post("/storage-facilities")
  290. async def create_storage_facility(
  291. body: StorageFacilityCreateBody,
  292. user: dict = Depends(require_admin_or_super_admin),
  293. db: Session = Depends(get_db),
  294. ):
  295. try:
  296. tenant_no = _get_tenant_no(db, user)
  297. obj = storage_facility_svc.create_facility(
  298. db,
  299. parent_id=body.parent_id,
  300. node_name=body.node_name,
  301. node_type=body.node_type,
  302. is_leaf=body.is_leaf,
  303. tenant_no=tenant_no,
  304. order_num=body.order_num,
  305. remark=body.remark,
  306. user_id=user.get("user_id"),
  307. )
  308. return success_response(storage_facility_svc._row_to_dict(obj), message="创建成功")
  309. except ValueError as e:
  310. return error_response(400, str(e))
  311. @router.put("/storage-facilities/{node_id}")
  312. async def update_storage_facility(
  313. node_id: str,
  314. body: StorageFacilityUpdateBody,
  315. user: dict = Depends(require_admin_or_super_admin),
  316. db: Session = Depends(get_db),
  317. ):
  318. try:
  319. tenant_no = _get_tenant_no(db, user)
  320. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  321. obj = storage_facility_svc.update_facility(
  322. db,
  323. node_id,
  324. node_name=payload.get("node_name"),
  325. node_type=payload.get("node_type"),
  326. is_leaf=payload.get("is_leaf"),
  327. order_num=payload.get("order_num"),
  328. remark=payload.get("remark"),
  329. user_id=user.get("user_id"),
  330. tenant_no=tenant_no,
  331. )
  332. return success_response(storage_facility_svc._row_to_dict(obj), message="更新成功")
  333. except ValueError as e:
  334. return error_response(400, str(e))
  335. @router.delete("/storage-facilities/{node_id}")
  336. async def delete_storage_facility(
  337. node_id: str,
  338. user: dict = Depends(require_admin_or_super_admin),
  339. db: Session = Depends(get_db),
  340. ):
  341. try:
  342. tenant_no = _get_tenant_no(db, user)
  343. storage_facility_svc.soft_delete_facility(db, node_id, tenant_no=tenant_no)
  344. return success_response(None, message="删除成功")
  345. except ValueError as e:
  346. return error_response(400, str(e))
  347. # ---------------------------------------------------------------------------
  348. # 储位
  349. # ---------------------------------------------------------------------------
  350. class StorageSlotCreateBody(BaseModel):
  351. model_config = ConfigDict(populate_by_name=True)
  352. facility_node_id: str = Field(..., alias="facilityNodeId")
  353. slot_code: Optional[str] = Field(None, alias="slotCode")
  354. slot_status: Optional[str] = Field(None, alias="slotStatus")
  355. order_num: Optional[int] = Field(0, alias="orderNum")
  356. remark: Optional[str] = None
  357. class StorageSlotBatchCreateBody(BaseModel):
  358. model_config = ConfigDict(populate_by_name=True)
  359. facility_node_id: str = Field(..., alias="facilityNodeId")
  360. rows: int = Field(..., ge=1, le=50)
  361. cols: int = Field(..., ge=1, le=50)
  362. code_prefix: Optional[str] = Field("", alias="codePrefix")
  363. class StorageSlotUpdateBody(BaseModel):
  364. model_config = ConfigDict(populate_by_name=True)
  365. slot_code: Optional[str] = Field(None, alias="slotCode")
  366. slot_status: Optional[str] = Field(None, alias="slotStatus")
  367. order_num: Optional[int] = Field(None, alias="orderNum")
  368. remark: Optional[str] = None
  369. class StorageSlotClearBody(BaseModel):
  370. model_config = ConfigDict(populate_by_name=True)
  371. facility_node_id: str = Field(..., alias="facilityNodeId")
  372. class StorageSlotInsertRowBody(BaseModel):
  373. model_config = ConfigDict(populate_by_name=True)
  374. facility_node_id: str = Field(..., alias="facilityNodeId")
  375. after_row_num: int = Field(..., ge=0, alias="afterRowNum")
  376. cols: int = Field(..., ge=1)
  377. code_prefix: Optional[str] = Field("", alias="codePrefix")
  378. @router.get("/storage-facility-slots")
  379. async def list_storage_slots(
  380. facilityNodeId: str = Query(..., alias="facilityNodeId"),
  381. user: dict = Depends(require_admin_or_super_admin),
  382. db: Session = Depends(get_db),
  383. ):
  384. tenant_no = _get_tenant_no(db, user)
  385. query = db.query(BizStorageFacilitySlot).filter(
  386. BizStorageFacilitySlot.del_flag == 0,
  387. BizStorageFacilitySlot.tenant_no == tenant_no,
  388. BizStorageFacilitySlot.facility_node_id == facilityNodeId,
  389. )
  390. query = apply_data_scope(query, BizStorageFacilitySlot, user, db, tenant_no)
  391. items = query.order_by(BizStorageFacilitySlot.order_num.asc(), BizStorageFacilitySlot.create_time.asc()).all()
  392. result_list = [storage_facility_svc._slot_to_dict(r) for r in items]
  393. return success_response(result_list)
  394. @router.post("/storage-facility-slots/batch")
  395. async def batch_create_storage_slots(
  396. body: StorageSlotBatchCreateBody,
  397. user: dict = Depends(require_admin_or_super_admin),
  398. db: Session = Depends(get_db),
  399. ):
  400. try:
  401. tenant_no = _get_tenant_no(db, user)
  402. items = storage_facility_svc.batch_create_slots(
  403. db,
  404. facility_node_id=body.facility_node_id,
  405. rows=body.rows,
  406. cols=body.cols,
  407. code_prefix=body.code_prefix,
  408. tenant_no=tenant_no,
  409. user_id=user.get("user_id"),
  410. )
  411. return success_response(items, message=f"批量创建 {len(items)} 个格子成功")
  412. except ValueError as e:
  413. return error_response(400, str(e))
  414. @router.post("/storage-facility-slots")
  415. async def create_storage_slot(
  416. body: StorageSlotCreateBody,
  417. user: dict = Depends(require_admin_or_super_admin),
  418. db: Session = Depends(get_db),
  419. ):
  420. try:
  421. tenant_no = _get_tenant_no(db, user)
  422. obj = storage_facility_svc.create_slot(
  423. db,
  424. facility_node_id=body.facility_node_id,
  425. slot_code=body.slot_code,
  426. slot_status=body.slot_status,
  427. tenant_no=tenant_no,
  428. order_num=body.order_num,
  429. remark=body.remark,
  430. user_id=user.get("user_id"),
  431. )
  432. return success_response(storage_facility_svc._slot_to_dict(obj), message="创建成功")
  433. except ValueError as e:
  434. return error_response(400, str(e))
  435. @router.put("/storage-facility-slots/{slot_id}")
  436. async def update_storage_slot(
  437. slot_id: str,
  438. body: StorageSlotUpdateBody,
  439. user: dict = Depends(require_admin_or_super_admin),
  440. db: Session = Depends(get_db),
  441. ):
  442. try:
  443. tenant_no = _get_tenant_no(db, user)
  444. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  445. obj = storage_facility_svc.update_slot(
  446. db,
  447. slot_id,
  448. slot_code=payload.get("slot_code"),
  449. slot_status=payload.get("slot_status"),
  450. order_num=payload.get("order_num"),
  451. remark=payload.get("remark"),
  452. user_id=user.get("user_id"),
  453. tenant_no=tenant_no,
  454. )
  455. return success_response(storage_facility_svc._slot_to_dict(obj), message="更新成功")
  456. except ValueError as e:
  457. return error_response(400, str(e))
  458. @router.post("/storage-facility-slots/insert-row")
  459. async def insert_storage_slot_row(
  460. body: StorageSlotInsertRowBody,
  461. user: dict = Depends(require_admin_or_super_admin),
  462. db: Session = Depends(get_db),
  463. ):
  464. try:
  465. tenant_no = _get_tenant_no(db, user)
  466. result = storage_facility_svc.insert_row_and_shift_slots(
  467. db,
  468. facility_node_id=body.facility_node_id,
  469. after_row_num=body.after_row_num,
  470. cols=body.cols,
  471. code_prefix=body.code_prefix or "",
  472. tenant_no=tenant_no,
  473. user_id=user.get("user_id"),
  474. )
  475. return success_response(result, message="新增行成功")
  476. except ValueError as e:
  477. return error_response(400, str(e))
  478. @router.post("/storage-facility-slots/clear")
  479. async def clear_storage_slots(
  480. body: StorageSlotClearBody,
  481. user: dict = Depends(require_admin_or_super_admin),
  482. db: Session = Depends(get_db),
  483. ):
  484. try:
  485. tenant_no = _get_tenant_no(db, user)
  486. cleared_count = storage_facility_svc.clear_slots_by_facility_node(
  487. db,
  488. facility_node_id=body.facility_node_id,
  489. tenant_no=tenant_no,
  490. user_id=user.get("user_id"),
  491. )
  492. return success_response(
  493. {"clearedCount": cleared_count},
  494. message=f"已清空 {cleared_count} 个格子",
  495. )
  496. except ValueError as e:
  497. return error_response(400, str(e))
  498. @router.delete("/storage-facility-slots/{slot_id}")
  499. async def delete_storage_slot(
  500. slot_id: str,
  501. user: dict = Depends(require_admin_or_super_admin),
  502. db: Session = Depends(get_db),
  503. ):
  504. try:
  505. tenant_no = _get_tenant_no(db, user)
  506. storage_facility_svc.soft_delete_slot(db, slot_id, tenant_no=tenant_no)
  507. return success_response(None, message="删除成功")
  508. except ValueError as e:
  509. return error_response(400, str(e))
  510. # ---------------------------------------------------------------------------
  511. # 库位占用记录
  512. # ---------------------------------------------------------------------------
  513. class OccupancyCreateBody(BaseModel):
  514. model_config = ConfigDict(populate_by_name=True)
  515. slot_id: str = Field(..., alias="slotId")
  516. item_type: str = Field("other", alias="itemType")
  517. item_code: str = Field(..., alias="itemCode")
  518. item_name: str = Field(..., alias="itemName")
  519. item_source: str = Field("manual", alias="itemSource")
  520. remark: Optional[str] = None
  521. @router.get("/storage-slot-occupancies")
  522. async def list_occupancies(
  523. slotId: str = Query(..., alias="slotId"),
  524. occupancyStatus: Optional[str] = Query("stored", alias="occupancyStatus"),
  525. itemType: Optional[str] = Query(None, alias="itemType"),
  526. page: int = Query(1),
  527. pageSize: int = Query(20, alias="pageSize"),
  528. user: dict = Depends(require_admin_or_super_admin),
  529. db: Session = Depends(get_db),
  530. ):
  531. tenant_no = _get_tenant_no(db, user)
  532. result = occupancy_svc.list_occupancies(
  533. db,
  534. slot_id=slotId,
  535. occupancy_status=occupancyStatus,
  536. item_type=itemType,
  537. tenant_no=tenant_no,
  538. page=page,
  539. page_size=pageSize,
  540. )
  541. return success_response(result)
  542. @router.get("/storage-slot-occupancies/history")
  543. async def list_occupancy_history(
  544. slotId: Optional[str] = Query(None, alias="slotId"),
  545. facilityNodeId: Optional[str] = Query(None, alias="facilityNodeId"),
  546. itemType: Optional[str] = Query(None, alias="itemType"),
  547. startDate: Optional[str] = Query(None, alias="startDate"),
  548. endDate: Optional[str] = Query(None, alias="endDate"),
  549. page: int = Query(1),
  550. pageSize: int = Query(20, alias="pageSize"),
  551. user: dict = Depends(require_admin_or_super_admin),
  552. db: Session = Depends(get_db),
  553. ):
  554. tenant_no = _get_tenant_no(db, user)
  555. result = occupancy_svc.list_occupancy_history(
  556. db,
  557. slot_id=slotId,
  558. facility_node_id=facilityNodeId,
  559. item_type=itemType,
  560. start_date=startDate,
  561. end_date=endDate,
  562. tenant_no=tenant_no,
  563. page=page,
  564. page_size=pageSize,
  565. )
  566. return success_response(result)
  567. @router.post("/storage-slot-occupancies")
  568. async def create_occupancy(
  569. body: OccupancyCreateBody,
  570. user: dict = Depends(require_admin_or_super_admin),
  571. db: Session = Depends(get_db),
  572. ):
  573. try:
  574. tenant_no = _get_tenant_no(db, user)
  575. item_id = generate_uuid()
  576. obj = occupancy_svc.create_occupancy(
  577. db,
  578. slot_id=body.slot_id,
  579. item_type=body.item_type,
  580. item_id=item_id,
  581. item_code=body.item_code,
  582. item_name=body.item_name,
  583. item_source=body.item_source,
  584. tenant_no=tenant_no,
  585. user_id=user.get("user_id"),
  586. remark=body.remark,
  587. )
  588. return success_response(occupancy_svc._row_to_dict(obj), message="入库成功")
  589. except ValueError as e:
  590. return error_response(400, str(e))
  591. @router.put("/storage-slot-occupancies/{occupancy_id}/remove")
  592. async def remove_occupancy(
  593. occupancy_id: str,
  594. user: dict = Depends(require_admin_or_super_admin),
  595. db: Session = Depends(get_db),
  596. ):
  597. try:
  598. obj = occupancy_svc.remove_occupancy(
  599. db,
  600. occupancy_id,
  601. user_id=user.get("user_id"),
  602. )
  603. return success_response(occupancy_svc._row_to_dict(obj), message="出库成功")
  604. except ValueError as e:
  605. return error_response(400, str(e))
  606. # ---------------------------------------------------------------------------
  607. # 展厅预约
  608. # ---------------------------------------------------------------------------
  609. class ExhibitionAppointmentCreateBody(BaseModel):
  610. model_config = ConfigDict(populate_by_name=True)
  611. appointment_no: Optional[str] = Field(None, alias="appointmentNo")
  612. visit_org: Optional[str] = Field(None, alias="visitOrg")
  613. contact_name: Optional[str] = Field(None, alias="contactName")
  614. visit_date: Optional[str] = Field(None, alias="visitDate")
  615. visitor_count: Optional[int] = Field(None, alias="visitorCount")
  616. reception_user_id: Optional[str] = Field(None, alias="receptionUserId")
  617. order_num: Optional[int] = Field(0, alias="orderNum")
  618. remark: Optional[str] = None
  619. class ExhibitionAppointmentUpdateBody(BaseModel):
  620. model_config = ConfigDict(populate_by_name=True)
  621. visit_org: Optional[str] = Field(None, alias="visitOrg")
  622. contact_name: Optional[str] = Field(None, alias="contactName")
  623. visit_date: Optional[str] = Field(None, alias="visitDate")
  624. visitor_count: Optional[int] = Field(None, alias="visitorCount")
  625. reception_user_id: Optional[str] = Field(None, alias="receptionUserId")
  626. order_num: Optional[int] = Field(None, alias="orderNum")
  627. remark: Optional[str] = None
  628. @router.get("/exhibition-appointments")
  629. async def list_exhibition_appointments(
  630. page: int = Query(1, ge=1),
  631. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  632. keyword: Optional[str] = None,
  633. user: dict = Depends(require_admin_or_super_admin),
  634. db: Session = Depends(get_db),
  635. ):
  636. tenant_no = _get_tenant_no(db, user)
  637. query = db.query(BizExhibitionAppointment).filter(
  638. BizExhibitionAppointment.del_flag == 0,
  639. BizExhibitionAppointment.tenant_no == tenant_no,
  640. )
  641. kw = (keyword or "").strip()
  642. if kw:
  643. like = f"%{kw}%"
  644. query = query.filter(
  645. or_(
  646. BizExhibitionAppointment.appointment_no.like(like),
  647. BizExhibitionAppointment.contact_name.like(like),
  648. BizExhibitionAppointment.visit_org.like(like),
  649. )
  650. )
  651. query = apply_data_scope(query, BizExhibitionAppointment, user, db, tenant_no)
  652. total = query.count()
  653. items = query.order_by(BizExhibitionAppointment.create_time.desc()).offset((page - 1) * pageSize).limit(pageSize).all()
  654. result_list = [exhibition_svc._row_to_dict(r) for r in items]
  655. tp = exhibition_svc.total_pages(total, pageSize)
  656. return success_response({"list": result_list, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
  657. @router.post("/exhibition-appointments")
  658. async def create_exhibition_appointment(
  659. body: ExhibitionAppointmentCreateBody,
  660. user: dict = Depends(require_admin_or_super_admin),
  661. db: Session = Depends(get_db),
  662. ):
  663. try:
  664. tenant_no = _get_tenant_no(db, user)
  665. obj = exhibition_svc.create_exhibition_appointment(
  666. db,
  667. appointment_no=body.appointment_no,
  668. visit_org=body.visit_org,
  669. contact_name=body.contact_name,
  670. visit_date=body.visit_date,
  671. visitor_count=body.visitor_count,
  672. reception_user_id=body.reception_user_id,
  673. tenant_no=tenant_no,
  674. order_num=body.order_num,
  675. remark=body.remark,
  676. user_id=user.get("user_id"),
  677. )
  678. return success_response(exhibition_svc._row_to_dict(obj), message="创建成功")
  679. except ValueError as e:
  680. return error_response(400, str(e))
  681. @router.put("/exhibition-appointments/{appointment_id}")
  682. async def update_exhibition_appointment(
  683. appointment_id: str,
  684. body: ExhibitionAppointmentUpdateBody,
  685. user: dict = Depends(require_admin_or_super_admin),
  686. db: Session = Depends(get_db),
  687. ):
  688. try:
  689. tenant_no = _get_tenant_no(db, user)
  690. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  691. obj = exhibition_svc.update_exhibition_appointment(
  692. db,
  693. appointment_id,
  694. visit_org=payload.get("visit_org"),
  695. contact_name=payload.get("contact_name"),
  696. visit_date=payload.get("visit_date"),
  697. visitor_count=payload.get("visitor_count"),
  698. reception_user_id=payload.get("reception_user_id"),
  699. order_num=payload.get("order_num"),
  700. remark=payload.get("remark"),
  701. user_id=user.get("user_id"),
  702. )
  703. return success_response(exhibition_svc._row_to_dict(obj), message="更新成功")
  704. except ValueError as e:
  705. return error_response(400, str(e))
  706. @router.delete("/exhibition-appointments/{appointment_id}")
  707. async def delete_exhibition_appointment(
  708. appointment_id: str,
  709. user: dict = Depends(require_admin_or_super_admin),
  710. db: Session = Depends(get_db),
  711. ):
  712. try:
  713. exhibition_svc.soft_delete_exhibition_appointment(db, appointment_id)
  714. return success_response(None, message="删除成功")
  715. except ValueError as e:
  716. return error_response(400, str(e))