| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106 |
- """运营支撑 API(洁净室、进出记录、仓储设施、储位、展厅预约)"""
- from datetime import datetime
- from typing import Any, Optional
- from fastapi import APIRouter, Depends, Query
- from pydantic import BaseModel, ConfigDict, Field
- from sqlalchemy import or_
- from sqlalchemy.orm import Session
- from app.core.data_scope import apply_data_scope
- from app.core.database import get_db
- from app.core.menu_guard import require_admin_or_super_admin
- from app.core.response import error_response, success_response
- from app.crud import clean_room as clean_room_svc
- from app.crud import exhibition_appointment as exhibition_svc
- from app.crud import storage_facility as storage_facility_svc
- from app.crud import storage_occupancy as occupancy_svc
- from app.core.utils import generate_uuid
- from app.models.models import BizCleanRoom, BizCleanRoomAccessLog, BizCleanRoomUvDisinfectLog, BizExhibitionAppointment, BizStorageFacility, BizStorageFacilitySlot, SysUser
- router = APIRouter(prefix="/web/admin", tags=["运营支撑"])
- def _get_tenant_no(db: Session, user: dict) -> str:
- user_id = user.get("user_id")
- if not user_id:
- raise PermissionError("用户身份信息缺失,无法确定租户")
- sys_user = db.query(SysUser).filter(SysUser.id == user_id).first()
- if not sys_user or not sys_user.tenant_no:
- raise PermissionError("用户租户信息缺失")
- return sys_user.tenant_no
- # ---------------------------------------------------------------------------
- # 洁净室
- # ---------------------------------------------------------------------------
- class CleanRoomCreateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- room_code: str = Field(..., alias="roomCode")
- room_name: str = Field(..., alias="roomName")
- room_grade: Optional[str] = Field(None, alias="roomGrade")
- room_status: Optional[str] = Field("zc", alias="roomStatus")
- area_sqm: Optional[float] = Field(None, alias="areaSqm")
- room_image: Optional[str] = Field(None, alias="roomImage")
- order_num: Optional[int] = Field(0, alias="orderNum")
- remark: Optional[str] = None
- class CleanRoomUpdateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- room_code: Optional[str] = Field(None, alias="roomCode")
- room_name: Optional[str] = Field(None, alias="roomName")
- room_grade: Optional[str] = Field(None, alias="roomGrade")
- room_status: Optional[str] = Field(None, alias="roomStatus")
- area_sqm: Optional[float] = Field(None, alias="areaSqm")
- room_image: Optional[str] = Field(None, alias="roomImage")
- order_num: Optional[int] = Field(None, alias="orderNum")
- remark: Optional[str] = None
- @router.get("/clean-rooms")
- async def list_clean_rooms(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
- keyword: Optional[str] = None,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- query = db.query(BizCleanRoom).filter(
- BizCleanRoom.del_flag == 0,
- BizCleanRoom.tenant_no == tenant_no,
- )
- kw = (keyword or "").strip()
- if kw:
- like = f"%{kw}%"
- query = query.filter(or_(BizCleanRoom.room_code.like(like), BizCleanRoom.room_name.like(like)))
- query = apply_data_scope(query, BizCleanRoom, user, db, tenant_no)
- total = query.count()
- items = query.order_by(BizCleanRoom.create_time.desc()).offset((page - 1) * pageSize).limit(pageSize).all()
- result_list = [clean_room_svc._row_to_dict(r) for r in items]
- tp = clean_room_svc.total_pages(total, pageSize)
- return success_response({"list": result_list, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
- @router.post("/clean-rooms")
- async def create_clean_room(
- body: CleanRoomCreateBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- obj = clean_room_svc.create_clean_room(
- db,
- room_code=body.room_code,
- room_name=body.room_name,
- room_grade=body.room_grade,
- room_status=body.room_status or "zc",
- area_sqm=body.area_sqm,
- room_image=body.room_image,
- tenant_no=tenant_no,
- order_num=body.order_num,
- remark=body.remark,
- user_id=user.get("user_id"),
- )
- return success_response(clean_room_svc._row_to_dict(obj), message="创建成功")
- except ValueError as e:
- return error_response(400, str(e))
- @router.put("/clean-rooms/{room_id}")
- async def update_clean_room(
- room_id: str,
- body: CleanRoomUpdateBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
- obj = clean_room_svc.update_clean_room(
- db,
- room_id,
- room_code=payload.get("room_code"),
- room_name=payload.get("room_name"),
- room_grade=payload.get("room_grade"),
- room_status=payload.get("room_status"),
- area_sqm=payload.get("area_sqm"),
- room_image=payload.get("room_image"),
- order_num=payload.get("order_num"),
- remark=payload.get("remark"),
- user_id=user.get("user_id"),
- tenant_no=tenant_no,
- )
- return success_response(clean_room_svc._row_to_dict(obj), message="更新成功")
- except ValueError as e:
- return error_response(400, str(e))
- @router.delete("/clean-rooms/{room_id}")
- async def delete_clean_room(
- room_id: str,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- clean_room_svc.soft_delete_clean_room(db, room_id, tenant_no=tenant_no)
- return success_response(None, message="删除成功")
- except ValueError as e:
- return error_response(400, str(e))
- # ---------------------------------------------------------------------------
- # 进出记录
- # ---------------------------------------------------------------------------
- class AccessLogCreateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- clean_room_id: str = Field(..., alias="cleanRoomId")
- person_name: Optional[str] = Field(None, alias="personName")
- dept_name: Optional[str] = Field(None, alias="deptName")
- enter_time: Optional[str] = Field(None, alias="enterTime")
- purpose: Optional[str] = None
- leave_time: Optional[str] = Field(None, alias="leaveTime")
- body_temp: Optional[str] = Field(None, alias="bodyTemp")
- order_num: Optional[int] = Field(0, alias="orderNum")
- remark: Optional[str] = None
- class AccessLogUpdateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- person_name: Optional[str] = Field(None, alias="personName")
- dept_name: Optional[str] = Field(None, alias="deptName")
- enter_time: Optional[str] = Field(None, alias="enterTime")
- purpose: Optional[str] = None
- leave_time: Optional[str] = Field(None, alias="leaveTime")
- body_temp: Optional[str] = Field(None, alias="bodyTemp")
- order_num: Optional[int] = Field(None, alias="orderNum")
- remark: Optional[str] = None
- def _parse_optional_dt(value: Optional[str]) -> Optional[datetime]:
- if not value:
- return None
- return datetime.fromisoformat(value)
- @router.get("/clean-room-access-logs")
- async def list_access_logs(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
- cleanRoomId: str = Query(..., alias="cleanRoomId"),
- keyword: Optional[str] = None,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- query = db.query(BizCleanRoomAccessLog).filter(
- BizCleanRoomAccessLog.del_flag == 0,
- BizCleanRoomAccessLog.tenant_no == tenant_no,
- BizCleanRoomAccessLog.clean_room_id == cleanRoomId,
- )
- if keyword:
- kw = f"%{keyword}%"
- query = query.filter(
- BizCleanRoomAccessLog.person_name.like(kw)
- | BizCleanRoomAccessLog.dept_name.like(kw)
- )
- query = apply_data_scope(query, BizCleanRoomAccessLog, user, db, tenant_no)
- total = query.count()
- items = query.order_by(BizCleanRoomAccessLog.create_time.desc()).offset((page - 1) * pageSize).limit(pageSize).all()
- result_list = [clean_room_svc._access_log_to_dict(r) for r in items]
- tp = clean_room_svc.total_pages(total, pageSize)
- return success_response({"list": result_list, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
- @router.post("/clean-room-access-logs")
- async def create_access_log(
- body: AccessLogCreateBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- obj = clean_room_svc.create_access_log(
- db,
- clean_room_id=body.clean_room_id,
- person_name=body.person_name,
- dept_name=body.dept_name,
- enter_time=_parse_optional_dt(body.enter_time),
- purpose=body.purpose,
- leave_time=_parse_optional_dt(body.leave_time),
- body_temp=body.body_temp,
- tenant_no=tenant_no,
- order_num=body.order_num,
- remark=body.remark,
- user_id_param=user.get("user_id"),
- )
- return success_response(clean_room_svc._access_log_to_dict(obj), message="创建成功")
- except ValueError as e:
- return error_response(400, str(e))
- @router.put("/clean-room-access-logs/{log_id}")
- async def update_access_log(
- log_id: str,
- body: AccessLogUpdateBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
- obj = clean_room_svc.update_access_log(
- db,
- log_id,
- person_name=payload.get("person_name"),
- dept_name=payload.get("dept_name"),
- enter_time=_parse_optional_dt(payload.get("enter_time")) if "enter_time" in payload else None,
- purpose=payload.get("purpose"),
- leave_time=_parse_optional_dt(payload.get("leave_time")) if "leave_time" in payload else None,
- body_temp=payload.get("body_temp"),
- order_num=payload.get("order_num"),
- remark=payload.get("remark"),
- user_id_param=user.get("user_id"),
- tenant_no=tenant_no,
- )
- return success_response(clean_room_svc._access_log_to_dict(obj), message="更新成功")
- except ValueError as e:
- return error_response(400, str(e))
- @router.delete("/clean-room-access-logs/{log_id}")
- async def delete_access_log(
- log_id: str,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- clean_room_svc.soft_delete_access_log(db, log_id, tenant_no=tenant_no)
- return success_response(None, message="删除成功")
- except ValueError as e:
- return error_response(400, str(e))
- # ---------------------------------------------------------------------------
- # 紫外线消毒记录
- # ---------------------------------------------------------------------------
- class UvDisinfectLogCreateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- clean_room_id: str = Field(..., alias="cleanRoomId")
- operator_name: Optional[str] = Field(None, alias="operatorName")
- start_time: Optional[str] = Field(None, alias="startTime")
- end_time: Optional[str] = Field(None, alias="endTime")
- duration_minutes: Optional[int] = Field(None, alias="durationMinutes")
- order_num: Optional[int] = Field(0, alias="orderNum")
- remark: Optional[str] = None
- class UvDisinfectLogUpdateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- operator_name: Optional[str] = Field(None, alias="operatorName")
- start_time: Optional[str] = Field(None, alias="startTime")
- end_time: Optional[str] = Field(None, alias="endTime")
- duration_minutes: Optional[int] = Field(None, alias="durationMinutes")
- order_num: Optional[int] = Field(None, alias="orderNum")
- remark: Optional[str] = None
- @router.get("/clean-room-uv-disinfect-logs")
- async def list_uv_disinfect_logs(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
- cleanRoomId: str = Query(..., alias="cleanRoomId"),
- keyword: Optional[str] = None,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- query = db.query(BizCleanRoomUvDisinfectLog).filter(
- BizCleanRoomUvDisinfectLog.del_flag == 0,
- BizCleanRoomUvDisinfectLog.tenant_no == tenant_no,
- BizCleanRoomUvDisinfectLog.clean_room_id == cleanRoomId,
- )
- if keyword:
- kw = f"%{keyword}%"
- query = query.filter(BizCleanRoomUvDisinfectLog.operator_name.like(kw))
- query = apply_data_scope(query, BizCleanRoomUvDisinfectLog, user, db, tenant_no)
- total = query.count()
- items = (
- query.order_by(BizCleanRoomUvDisinfectLog.create_time.desc())
- .offset((page - 1) * pageSize)
- .limit(pageSize)
- .all()
- )
- result_list = [clean_room_svc._uv_disinfect_log_to_dict(r) for r in items]
- tp = clean_room_svc.total_pages(total, pageSize)
- return success_response({"list": result_list, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
- @router.post("/clean-room-uv-disinfect-logs")
- async def create_uv_disinfect_log(
- body: UvDisinfectLogCreateBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- obj = clean_room_svc.create_uv_disinfect_log(
- db,
- clean_room_id=body.clean_room_id,
- operator_name=body.operator_name,
- start_time=_parse_optional_dt(body.start_time),
- end_time=_parse_optional_dt(body.end_time),
- duration_minutes=body.duration_minutes,
- tenant_no=tenant_no,
- order_num=body.order_num,
- remark=body.remark,
- user_id_param=user.get("user_id"),
- )
- return success_response(clean_room_svc._uv_disinfect_log_to_dict(obj), message="创建成功")
- except ValueError as e:
- return error_response(400, str(e))
- @router.put("/clean-room-uv-disinfect-logs/{log_id}")
- async def update_uv_disinfect_log(
- log_id: str,
- body: UvDisinfectLogUpdateBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
- obj = clean_room_svc.update_uv_disinfect_log(
- db,
- log_id,
- operator_name=payload.get("operator_name"),
- start_time=_parse_optional_dt(payload.get("start_time")) if "start_time" in payload else None,
- end_time=_parse_optional_dt(payload.get("end_time")) if "end_time" in payload else None,
- duration_minutes=payload.get("duration_minutes"),
- order_num=payload.get("order_num"),
- remark=payload.get("remark"),
- user_id_param=user.get("user_id"),
- tenant_no=tenant_no,
- clear_end_time="end_time" in payload and payload.get("end_time") is None,
- )
- return success_response(clean_room_svc._uv_disinfect_log_to_dict(obj), message="更新成功")
- except ValueError as e:
- return error_response(400, str(e))
- @router.delete("/clean-room-uv-disinfect-logs/{log_id}")
- async def delete_uv_disinfect_log(
- log_id: str,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- clean_room_svc.soft_delete_uv_disinfect_log(db, log_id, tenant_no=tenant_no)
- return success_response(None, message="删除成功")
- except ValueError as e:
- return error_response(400, str(e))
- # ---------------------------------------------------------------------------
- # 仓储设施
- # ---------------------------------------------------------------------------
- class StorageFacilityCreateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- parent_id: str = Field("0", alias="parentId")
- node_name: str = Field(..., alias="nodeName")
- node_code: str = Field("", alias="nodeCode", max_length=32)
- node_type: Optional[str] = Field(None, alias="nodeType")
- is_leaf: Optional[int] = Field(0, alias="isLeaf")
- order_num: Optional[int] = Field(0, alias="orderNum")
- remark: Optional[str] = None
- class StorageFacilityUpdateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- node_name: Optional[str] = Field(None, alias="nodeName")
- node_code: Optional[str] = Field(None, alias="nodeCode", max_length=32)
- node_type: Optional[str] = Field(None, alias="nodeType")
- is_leaf: Optional[int] = Field(None, alias="isLeaf")
- order_num: Optional[int] = Field(None, alias="orderNum")
- remark: Optional[str] = None
- class StorageFacilityBatchCreateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- parent_id: str = Field("0", alias="parentId")
- name_prefix: str = Field("", alias="namePrefix")
- name_suffix: str = Field("", alias="nameSuffix")
- start_num: int = Field(..., alias="startNum", ge=0)
- end_num: int = Field(..., alias="endNum", ge=0)
- pad_width: int = Field(0, alias="padWidth", ge=0, le=8)
- code_prefix: str = Field("", alias="codePrefix")
- code_suffix: str = Field("", alias="codeSuffix")
- node_type: Optional[str] = Field(None, alias="nodeType")
- is_leaf: Optional[int] = Field(0, alias="isLeaf")
- remark: Optional[str] = None
- @router.get("/storage-facilities")
- async def list_storage_facilities(
- keyword: Optional[str] = None,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- query = db.query(BizStorageFacility).filter(
- BizStorageFacility.del_flag == 0,
- BizStorageFacility.tenant_no == tenant_no,
- )
- if keyword:
- like = f"%{keyword}%"
- query = query.filter(
- or_(
- BizStorageFacility.node_name.like(like),
- BizStorageFacility.node_code.like(like),
- )
- )
- query = apply_data_scope(query, BizStorageFacility, user, db, tenant_no)
- items = query.order_by(
- BizStorageFacility.order_num.asc(),
- BizStorageFacility.create_time.asc(),
- ).all()
- result_list = [storage_facility_svc._row_to_dict(r) for r in items]
- return success_response(result_list)
- @router.post("/storage-facilities")
- async def create_storage_facility(
- body: StorageFacilityCreateBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- obj = storage_facility_svc.create_facility(
- db,
- parent_id=body.parent_id,
- node_name=body.node_name,
- node_code=body.node_code,
- node_type=body.node_type,
- is_leaf=body.is_leaf,
- tenant_no=tenant_no,
- order_num=body.order_num,
- remark=body.remark,
- user_id=user.get("user_id"),
- )
- return success_response(storage_facility_svc._row_to_dict(obj), message="创建成功")
- except ValueError as e:
- return error_response(400, str(e))
- @router.post("/storage-facilities/batch")
- async def batch_create_storage_facilities(
- body: StorageFacilityBatchCreateBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- items = storage_facility_svc.batch_create_facilities(
- db,
- parent_id=body.parent_id,
- name_prefix=body.name_prefix,
- name_suffix=body.name_suffix,
- start_num=body.start_num,
- end_num=body.end_num,
- pad_width=body.pad_width,
- code_prefix=body.code_prefix,
- code_suffix=body.code_suffix,
- node_type=body.node_type,
- is_leaf=body.is_leaf or 0,
- tenant_no=tenant_no,
- remark=body.remark,
- user_id=user.get("user_id"),
- )
- return success_response(items, message=f"批量创建 {len(items)} 个子节点成功")
- except ValueError as e:
- return error_response(400, str(e))
- @router.put("/storage-facilities/{node_id}")
- async def update_storage_facility(
- node_id: str,
- body: StorageFacilityUpdateBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
- obj = storage_facility_svc.update_facility(
- db,
- node_id,
- node_name=payload.get("node_name"),
- node_code=payload.get("node_code"),
- node_type=payload.get("node_type"),
- is_leaf=payload.get("is_leaf"),
- order_num=payload.get("order_num"),
- remark=payload.get("remark"),
- user_id=user.get("user_id"),
- tenant_no=tenant_no,
- )
- return success_response(storage_facility_svc._row_to_dict(obj), message="更新成功")
- except ValueError as e:
- return error_response(400, str(e))
- @router.delete("/storage-facilities/{node_id}")
- async def delete_storage_facility(
- node_id: str,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- storage_facility_svc.soft_delete_facility(db, node_id, tenant_no=tenant_no)
- return success_response(None, message="删除成功")
- except ValueError as e:
- return error_response(400, str(e))
- # ---------------------------------------------------------------------------
- # 储位
- # ---------------------------------------------------------------------------
- class StorageSlotCreateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- facility_node_id: str = Field(..., alias="facilityNodeId")
- slot_code: Optional[str] = Field(None, alias="slotCode")
- slot_status: Optional[str] = Field(None, alias="slotStatus")
- order_num: Optional[int] = Field(0, alias="orderNum")
- at_row_num: Optional[int] = Field(None, alias="atRowNum", ge=1)
- remark: Optional[str] = None
- class StorageSlotBatchCreateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- facility_node_id: str = Field(..., alias="facilityNodeId")
- rows: int = Field(..., ge=1, le=50)
- cols: int = Field(..., ge=1, le=50)
- class StorageSlotUpdateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- slot_code: Optional[str] = Field(None, alias="slotCode")
- slot_status: Optional[str] = Field(None, alias="slotStatus")
- order_num: Optional[int] = Field(None, alias="orderNum")
- remark: Optional[str] = None
- class StorageSlotClearBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- facility_node_id: str = Field(..., alias="facilityNodeId")
- class StorageSlotInsertRowBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- facility_node_id: str = Field(..., alias="facilityNodeId")
- after_row_num: int = Field(..., ge=0, alias="afterRowNum")
- cols: int = Field(..., ge=1)
- @router.get("/storage-facility-slots")
- async def list_storage_slots(
- facilityNodeId: str = Query(..., alias="facilityNodeId"),
- availableOnly: Optional[bool] = Query(None, alias="availableOnly"),
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- query = db.query(BizStorageFacilitySlot).filter(
- BizStorageFacilitySlot.del_flag == 0,
- BizStorageFacilitySlot.tenant_no == tenant_no,
- BizStorageFacilitySlot.facility_node_id == facilityNodeId,
- )
- query = apply_data_scope(query, BizStorageFacilitySlot, user, db, tenant_no)
- items = query.order_by(
- BizStorageFacilitySlot.order_num.asc(),
- BizStorageFacilitySlot.create_time.asc(),
- ).all()
- facility_by_id = storage_facility_svc.load_facility_ancestor_map(
- db, tenant_no, facilityNodeId
- )
- result_list = [storage_facility_svc._slot_to_dict(r, facility_by_id) for r in items]
- if availableOnly:
- # 仅空格场景无需 enrich 在库产品
- result_list = [
- row for row in result_list
- if (row.get("slotStatus") or "") == "空" or int(row.get("occupiedCount") or 0) == 0
- ]
- else:
- # 占用格附带名称/类型(轻量批量,供库位管理货架展示)
- result_list = occupancy_svc.enrich_slots_with_stored_cell_products(
- db, tenant_no, result_list
- )
- return success_response(result_list)
- @router.get("/storage-facility-slots/stats")
- async def list_storage_slot_stats(
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- """各叶子节点格子占用/总数统计(单次聚合,替代逐节点拉全量格子列表)。"""
- tenant_no = _get_tenant_no(db, user)
- query = db.query(BizStorageFacilitySlot).filter(
- BizStorageFacilitySlot.del_flag == 0,
- BizStorageFacilitySlot.tenant_no == tenant_no,
- )
- query = apply_data_scope(query, BizStorageFacilitySlot, user, db, tenant_no)
- items = storage_facility_svc.aggregate_slot_stats_by_facility(query=query)
- return success_response(items)
- @router.get("/storage-facility-slots/select-options")
- async def list_storage_slot_select_options(
- keyword: Optional[str] = None,
- availableOnly: Optional[bool] = Query(None, alias="availableOnly"),
- includeSlotIds: Optional[str] = Query(None, alias="includeSlotIds"),
- page: int = Query(1, ge=1),
- pageSize: int = Query(20, ge=1, le=50, alias="pageSize"),
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- query = db.query(BizStorageFacilitySlot).filter(
- BizStorageFacilitySlot.del_flag == 0,
- BizStorageFacilitySlot.tenant_no == tenant_no,
- )
- query = apply_data_scope(query, BizStorageFacilitySlot, user, db, tenant_no)
- include_ids = [
- x.strip()
- for x in (includeSlotIds or "").split(",")
- if x.strip()
- ]
- items, total = storage_facility_svc.list_slot_select_options(
- db,
- query=query,
- tenant_no=tenant_no,
- keyword=keyword,
- available_only=bool(availableOnly),
- include_slot_ids=include_ids or None,
- page=page,
- page_size=pageSize,
- )
- return success_response(
- {
- "list": items,
- "total": total,
- "page": page,
- "pageSize": pageSize,
- "totalPages": (total + pageSize - 1) // pageSize if pageSize else 0,
- }
- )
- @router.post("/storage-facility-slots/batch")
- async def batch_create_storage_slots(
- body: StorageSlotBatchCreateBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- items = storage_facility_svc.batch_create_slots(
- db,
- facility_node_id=body.facility_node_id,
- rows=body.rows,
- cols=body.cols,
- tenant_no=tenant_no,
- user_id=user.get("user_id"),
- )
- return success_response(items, message=f"批量创建 {len(items)} 个格子成功")
- except ValueError as e:
- return error_response(400, str(e))
- @router.post("/storage-facility-slots")
- async def create_storage_slot(
- body: StorageSlotCreateBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- obj = storage_facility_svc.create_slot(
- db,
- facility_node_id=body.facility_node_id,
- slot_code=body.slot_code,
- slot_status=body.slot_status,
- tenant_no=tenant_no,
- order_num=body.order_num,
- remark=body.remark,
- user_id=user.get("user_id"),
- at_row_num=body.at_row_num,
- )
- return success_response(storage_facility_svc._slot_to_dict_for_row(db, obj), message="创建成功")
- except ValueError as e:
- return error_response(400, str(e))
- @router.put("/storage-facility-slots/{slot_id}")
- async def update_storage_slot(
- slot_id: str,
- body: StorageSlotUpdateBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
- obj = storage_facility_svc.update_slot(
- db,
- slot_id,
- slot_code=payload.get("slot_code"),
- slot_status=payload.get("slot_status"),
- order_num=payload.get("order_num"),
- remark=payload.get("remark"),
- user_id=user.get("user_id"),
- tenant_no=tenant_no,
- )
- return success_response(storage_facility_svc._slot_to_dict_for_row(db, obj), message="更新成功")
- except ValueError as e:
- return error_response(400, str(e))
- @router.post("/storage-facility-slots/insert-row")
- async def insert_storage_slot_row(
- body: StorageSlotInsertRowBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- result = storage_facility_svc.insert_row_and_shift_slots(
- db,
- facility_node_id=body.facility_node_id,
- after_row_num=body.after_row_num,
- cols=body.cols,
- tenant_no=tenant_no,
- user_id=user.get("user_id"),
- )
- return success_response(result, message="新增行成功")
- except ValueError as e:
- return error_response(400, str(e))
- @router.post("/storage-facility-slots/clear")
- async def clear_storage_slots(
- body: StorageSlotClearBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- cleared_count = storage_facility_svc.clear_slots_by_facility_node(
- db,
- facility_node_id=body.facility_node_id,
- tenant_no=tenant_no,
- user_id=user.get("user_id"),
- )
- return success_response(
- {"clearedCount": cleared_count},
- message=f"已清空 {cleared_count} 个格子",
- )
- except ValueError as e:
- return error_response(400, str(e))
- @router.delete("/storage-facility-slots/{slot_id}")
- async def delete_storage_slot(
- slot_id: str,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- storage_facility_svc.soft_delete_slot(
- db, slot_id, tenant_no=tenant_no, user_id=user.get("user_id")
- )
- return success_response(None, message="删除成功")
- except ValueError as e:
- return error_response(400, str(e))
- # ---------------------------------------------------------------------------
- # 库位占用记录
- # ---------------------------------------------------------------------------
- class OccupancyCreateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- slot_id: str = Field(..., alias="slotId")
- item_type: str = Field("other", alias="itemType")
- item_code: str = Field(..., alias="itemCode")
- item_name: str = Field(..., alias="itemName")
- item_source: str = Field("manual", alias="itemSource")
- remark: Optional[str] = None
- @router.get("/storage-slot-occupancies")
- async def list_occupancies(
- slotId: str = Query(..., alias="slotId"),
- occupancyStatus: Optional[str] = Query("stored", alias="occupancyStatus"),
- itemType: Optional[str] = Query(None, alias="itemType"),
- page: int = Query(1),
- pageSize: int = Query(20, alias="pageSize"),
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- result = occupancy_svc.list_occupancies(
- db,
- slot_id=slotId,
- occupancy_status=occupancyStatus,
- item_type=itemType,
- tenant_no=tenant_no,
- page=page,
- page_size=pageSize,
- )
- return success_response(result)
- @router.get("/storage-slot-occupancies/history")
- async def list_occupancy_history(
- slotId: Optional[str] = Query(None, alias="slotId"),
- facilityNodeId: Optional[str] = Query(None, alias="facilityNodeId"),
- itemType: Optional[str] = Query(None, alias="itemType"),
- startDate: Optional[str] = Query(None, alias="startDate"),
- endDate: Optional[str] = Query(None, alias="endDate"),
- page: int = Query(1),
- pageSize: int = Query(20, alias="pageSize"),
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- result = occupancy_svc.list_occupancy_history(
- db,
- slot_id=slotId,
- facility_node_id=facilityNodeId,
- item_type=itemType,
- start_date=startDate,
- end_date=endDate,
- tenant_no=tenant_no,
- page=page,
- page_size=pageSize,
- )
- return success_response(result)
- @router.post("/storage-slot-occupancies")
- async def create_occupancy(
- body: OccupancyCreateBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- item_id = generate_uuid()
- obj = occupancy_svc.create_occupancy(
- db,
- slot_id=body.slot_id,
- item_type=body.item_type,
- item_id=item_id,
- item_code=body.item_code,
- item_name=body.item_name,
- item_source=body.item_source,
- tenant_no=tenant_no,
- user_id=user.get("user_id"),
- remark=body.remark,
- )
- return success_response(occupancy_svc._row_to_dict(obj), message="入库成功")
- except ValueError as e:
- return error_response(400, str(e))
- @router.put("/storage-slot-occupancies/{occupancy_id}/remove")
- async def remove_occupancy(
- occupancy_id: str,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- obj = occupancy_svc.remove_occupancy(
- db,
- occupancy_id,
- user_id=user.get("user_id"),
- )
- return success_response(occupancy_svc._row_to_dict(obj), message="出库成功")
- except ValueError as e:
- return error_response(400, str(e))
- # ---------------------------------------------------------------------------
- # 展厅预约
- # ---------------------------------------------------------------------------
- class ExhibitionAppointmentCreateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- appointment_no: Optional[str] = Field(None, alias="appointmentNo")
- visit_org: Optional[str] = Field(None, alias="visitOrg")
- contact_name: Optional[str] = Field(None, alias="contactName")
- visit_date: Optional[str] = Field(None, alias="visitDate")
- visitor_count: Optional[int] = Field(None, alias="visitorCount")
- reception_user_id: Optional[str] = Field(None, alias="receptionUserId")
- order_num: Optional[int] = Field(0, alias="orderNum")
- remark: Optional[str] = None
- class ExhibitionAppointmentUpdateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- visit_org: Optional[str] = Field(None, alias="visitOrg")
- contact_name: Optional[str] = Field(None, alias="contactName")
- visit_date: Optional[str] = Field(None, alias="visitDate")
- visitor_count: Optional[int] = Field(None, alias="visitorCount")
- reception_user_id: Optional[str] = Field(None, alias="receptionUserId")
- order_num: Optional[int] = Field(None, alias="orderNum")
- remark: Optional[str] = None
- def _normalize_visit_date_bound(value: str, *, end_of_day: bool = False) -> str:
- val = (value or "").strip()
- if len(val) == 10:
- return f"{val} 23:59:59" if end_of_day else f"{val} 00:00:00"
- return val
- @router.get("/exhibition-appointments")
- async def list_exhibition_appointments(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
- keyword: Optional[str] = None,
- visitDateStart: Optional[str] = Query(None, alias="visitDateStart"),
- visitDateEnd: Optional[str] = Query(None, alias="visitDateEnd"),
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- query = db.query(BizExhibitionAppointment).filter(
- BizExhibitionAppointment.del_flag == 0,
- BizExhibitionAppointment.tenant_no == tenant_no,
- )
- kw = (keyword or "").strip()
- if kw:
- like = f"%{kw}%"
- query = query.filter(
- or_(
- BizExhibitionAppointment.appointment_no.like(like),
- BizExhibitionAppointment.contact_name.like(like),
- BizExhibitionAppointment.visit_org.like(like),
- )
- )
- if visitDateStart:
- query = query.filter(
- BizExhibitionAppointment.visit_date >= _normalize_visit_date_bound(visitDateStart)
- )
- if visitDateEnd:
- query = query.filter(
- BizExhibitionAppointment.visit_date <= _normalize_visit_date_bound(visitDateEnd, end_of_day=True)
- )
- query = apply_data_scope(query, BizExhibitionAppointment, user, db, tenant_no)
- total = query.count()
- items = query.order_by(BizExhibitionAppointment.create_time.desc()).offset((page - 1) * pageSize).limit(pageSize).all()
- result_list = [exhibition_svc._row_to_dict(r) for r in items]
- tp = exhibition_svc.total_pages(total, pageSize)
- return success_response({"list": result_list, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
- @router.post("/exhibition-appointments")
- async def create_exhibition_appointment(
- body: ExhibitionAppointmentCreateBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- obj = exhibition_svc.create_exhibition_appointment(
- db,
- appointment_no=body.appointment_no,
- visit_org=body.visit_org,
- contact_name=body.contact_name,
- visit_date=body.visit_date,
- visitor_count=body.visitor_count,
- reception_user_id=body.reception_user_id,
- tenant_no=tenant_no,
- order_num=body.order_num,
- remark=body.remark,
- user_id=user.get("user_id"),
- )
- return success_response(exhibition_svc._row_to_dict(obj), message="创建成功")
- except ValueError as e:
- return error_response(400, str(e))
- @router.put("/exhibition-appointments/{appointment_id}")
- async def update_exhibition_appointment(
- appointment_id: str,
- body: ExhibitionAppointmentUpdateBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
- obj = exhibition_svc.update_exhibition_appointment(
- db,
- appointment_id,
- visit_org=payload.get("visit_org"),
- contact_name=payload.get("contact_name"),
- visit_date=payload.get("visit_date"),
- visitor_count=payload.get("visitor_count"),
- reception_user_id=payload.get("reception_user_id"),
- order_num=payload.get("order_num"),
- remark=payload.get("remark"),
- user_id=user.get("user_id"),
- )
- return success_response(exhibition_svc._row_to_dict(obj), message="更新成功")
- except ValueError as e:
- return error_response(400, str(e))
- @router.delete("/exhibition-appointments/{appointment_id}")
- async def delete_exhibition_appointment(
- appointment_id: str,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- exhibition_svc.soft_delete_exhibition_appointment(db, appointment_id)
- return success_response(None, message="删除成功")
- except ValueError as e:
- return error_response(400, str(e))
|