"""样本运费批次 API""" from __future__ import annotations from typing import Any, Dict, List, Optional from fastapi import APIRouter, Depends, Query from pydantic import BaseModel, ConfigDict, Field from sqlalchemy.orm import Session 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 sample_freight as freight_svc from app.crud import sf_express_settlement as sf_crud from app.models.models import 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 def _get_current_user_id(user: dict) -> str: user_id = user.get("user_id") if not user_id: raise PermissionError("用户身份信息缺失") return str(user_id) class SampleFreightItemBody(BaseModel): model_config = ConfigDict(extra="ignore", populate_by_name=True) sample_source: str = Field(..., alias="sampleSource") sample_id: str = Field(..., alias="sampleId") protocol_id: Optional[str] = Field(None, alias="protocolId") sample_code: Optional[str] = Field(None, alias="sampleCode") class SampleFreightCreateBody(BaseModel): model_config = ConfigDict(extra="ignore", populate_by_name=True) transport_mode: str = Field(..., alias="transportMode") freight_amount: Optional[float] = Field(None, alias="freightAmount") waybill_no: Optional[str] = Field(None, alias="waybillNo") items: Optional[List[SampleFreightItemBody]] = None class SampleFreightUpdateBody(BaseModel): model_config = ConfigDict(extra="ignore", populate_by_name=True) transport_mode: Optional[str] = Field(None, alias="transportMode") freight_amount: Optional[float] = Field(None, alias="freightAmount") waybill_no: Optional[str] = Field(None, alias="waybillNo") class SampleFreightItemsBody(BaseModel): model_config = ConfigDict(extra="ignore", populate_by_name=True) items: List[SampleFreightItemBody] = Field(default_factory=list) class SfSettlementSettleBody(BaseModel): model_config = ConfigDict(extra="ignore", populate_by_name=True) ids: List[str] = Field(default_factory=list) freight_amount: Optional[float] = Field(None, alias="freightAmount") class SfSettlementFillBody(BaseModel): model_config = ConfigDict(extra="ignore", populate_by_name=True) freight_amount: float = Field(..., alias="freightAmount") @router.get("/sample-freights") async def list_sample_freights( page: int = Query(1, ge=1), pageSize: int = Query(10, ge=1, le=100, alias="pageSize"), keyword: Optional[str] = None, transportMode: Optional[str] = None, sampleSource: Optional[str] = None, createTimeFrom: Optional[str] = None, createTimeTo: Optional[str] = None, includeItems: bool = Query(False), user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) items, total = freight_svc.list_freights( db, tenant_no=tenant_no, user_info=user, page=page, page_size=pageSize, keyword=keyword, transport_mode=transportMode, sample_source=sampleSource, create_time_from=createTimeFrom, create_time_to=createTimeTo, include_items=includeItems, ) return success_response( {"list": items, "total": total, "page": page, "pageSize": pageSize} ) except PermissionError as e: return error_response(403, str(e)) except ValueError as e: return error_response(400, str(e)) @router.get("/sample-freights/sample-options") async def list_sample_freight_options( page: int = Query(1, ge=1), pageSize: int = Query(20, ge=1, le=100, alias="pageSize"), keyword: Optional[str] = None, sampleSource: Optional[str] = None, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) items, total = freight_svc.list_sample_options( db, tenant_no=tenant_no, user_info=user, page=page, page_size=pageSize, keyword=keyword, sample_source=sampleSource, ) return success_response( {"list": items, "total": total, "page": page, "pageSize": pageSize} ) except PermissionError as e: return error_response(403, str(e)) except ValueError as e: return error_response(400, str(e)) @router.get("/sample-freights/sf-settlements") async def list_sf_express_settlements( page: int = Query(1, ge=1), pageSize: int = Query(10, ge=1, le=100, alias="pageSize"), waybillNo: Optional[str] = None, settleStatus: Optional[str] = None, createTimeFrom: Optional[str] = None, createTimeTo: Optional[str] = None, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) rows, total = sf_crud.list_sf_settlements( db, tenant_no=tenant_no, user_info=user, page=page, page_size=pageSize, waybill_no=waybillNo, settle_status=settleStatus, create_time_from=createTimeFrom, create_time_to=createTimeTo, ) return success_response( {"list": rows, "total": total, "page": page, "pageSize": pageSize} ) except PermissionError as e: return error_response(403, str(e)) except ValueError as e: return error_response(400, str(e)) @router.get("/sample-freights/sf-settlements/export") async def export_sf_express_settlements( waybillNo: Optional[str] = None, settleStatus: Optional[str] = None, createTimeFrom: Optional[str] = None, createTimeTo: Optional[str] = None, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) rows = sf_crud.list_sf_settlements_for_export( db, tenant_no=tenant_no, user_info=user, waybill_no=waybillNo, settle_status=settleStatus, create_time_from=createTimeFrom, create_time_to=createTimeTo, ) return success_response({"list": rows}) except PermissionError as e: return error_response(403, str(e)) except ValueError as e: return error_response(400, str(e)) @router.post("/sample-freights/sf-settlements/settle") async def settle_sf_express_settlements( body: SfSettlementSettleBody, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) user_id = _get_current_user_id(user) data = sf_crud.settle_sf_freights( db, tenant_no=tenant_no, user_id=user_id, user_info=user, ids=body.ids, freight_amount=body.freight_amount, ) return success_response(data, message="结算成功") except PermissionError as e: return error_response(403, str(e)) except ValueError as e: return error_response(400, str(e)) @router.post("/sample-freights/sf-settlements/{freight_id}/fill-amount") async def fill_sf_express_settlement_amount( freight_id: str, body: SfSettlementFillBody, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) user_id = _get_current_user_id(user) data = sf_crud.fill_sf_freight_amount( db, tenant_no=tenant_no, user_id=user_id, user_info=user, freight_id=freight_id, freight_amount=body.freight_amount, ) return success_response(data, message="补填成功") except PermissionError as e: return error_response(403, str(e)) except ValueError as e: return error_response(400, str(e)) @router.get("/sample-freights/{freight_id}") async def get_sample_freight( freight_id: str, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) data = freight_svc.get_freight_detail(db, freight_id, tenant_no, user_info=user) return success_response(data) except PermissionError as e: return error_response(403, str(e)) except ValueError as e: return error_response(404, str(e)) @router.post("/sample-freights") async def create_sample_freight( body: SampleFreightCreateBody, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) user_id = _get_current_user_id(user) items = [it.model_dump(by_alias=True) for it in (body.items or [])] data = freight_svc.create_freight( db, tenant_no=tenant_no, user_info=user, user_id=user_id, transport_mode_raw=body.transport_mode, freight_amount=body.freight_amount, waybill_no=body.waybill_no, waybill_supplied=True, amount_supplied=True, items=items, ) return success_response(data, message="创建成功") except PermissionError as e: return error_response(403, str(e)) except ValueError as e: return error_response(400, str(e)) @router.put("/sample-freights/{freight_id}") async def update_sample_freight( freight_id: str, body: SampleFreightUpdateBody, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) user_id = _get_current_user_id(user) data = freight_svc.update_freight( db, freight_id, tenant_no=tenant_no, user_info=user, user_id=user_id, transport_mode_raw=body.transport_mode, freight_amount=body.freight_amount, waybill_no=body.waybill_no, waybill_supplied="waybill_no" in body.model_fields_set, amount_supplied="freight_amount" in body.model_fields_set, ) return success_response(data, message="更新成功") except PermissionError as e: return error_response(403, str(e)) except ValueError as e: msg = str(e) code = 404 if "不存在" in msg else 400 return error_response(code, msg) @router.post("/sample-freights/{freight_id}/link") async def link_sample_freight( freight_id: str, body: SampleFreightItemsBody, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) user_id = _get_current_user_id(user) items = [it.model_dump(by_alias=True) for it in (body.items or [])] data = freight_svc.link_samples( db, freight_id, tenant_no=tenant_no, user_info=user, user_id=user_id, items=items, ) return success_response(data, message="关联成功") except PermissionError as e: return error_response(403, str(e)) except ValueError as e: msg = str(e) code = 404 if "不存在" in msg else 400 return error_response(code, msg) @router.post("/sample-freights/{freight_id}/unlink") async def unlink_sample_freight( freight_id: str, body: SampleFreightItemsBody, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) user_id = _get_current_user_id(user) items = [it.model_dump(by_alias=True) for it in (body.items or [])] data = freight_svc.unlink_samples( db, freight_id, tenant_no=tenant_no, user_info=user, user_id=user_id, items=items, ) return success_response(data, message="已取消关联") except PermissionError as e: return error_response(403, str(e)) except ValueError as e: msg = str(e) code = 404 if "不存在" in msg else 400 return error_response(code, msg) @router.delete("/sample-freights/{freight_id}") async def delete_sample_freight( freight_id: str, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) user_id = _get_current_user_id(user) data = freight_svc.delete_freight( db, freight_id, tenant_no=tenant_no, user_info=user, user_id=user_id, ) return success_response(data, message="删除成功") except PermissionError as e: return error_response(403, str(e)) except ValueError as e: msg = str(e) code = 404 if "不存在" in msg else 400 return error_response(code, msg)