| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719 |
- """质量检测 — 第三方/实验室主数据、质检工单、质检任务 API"""
- from typing import Any, Dict, Iterable, List, Optional
- from fastapi import APIRouter, Depends, HTTPException, Query
- from pydantic import BaseModel, ConfigDict, Field
- from sqlalchemy.orm import Session
- from datetime import datetime
- from decimal import Decimal
- from app.core.database import get_db
- from app.core.menu_guard import (
- require_admin_or_super_admin,
- require_any_menu_code,
- require_menu_code,
- strip_row_fields,
- user_has_menu_perm,
- )
- MY_QC_MATERIAL_USAGE_VIEW_AMOUNT_PERM = "biz:myQcMaterialUsage:viewAmount"
- QC_MATERIAL_VIEW_AMOUNT_PERM = "biz:qcMaterial:viewAmount"
- QC_THIRD_PARTY_VIEW_AMOUNT_PERM = "biz:qcThirdPartyCompany:viewAmount"
- QC_MATERIAL_AMOUNT_FIELD_NAMES = ("unitPrice", "amount", "remainingAmount", "unit_price")
- QC_THIRD_PARTY_FEE_FIELD_NAMES = ("feeAmount", "thirdPartyFeeTotal")
- from app.core.response import error_response, success_response
- from app.core.utils import parse_datetime
- from app.crud import qc_lab_test_item as lab_svc
- from app.crud import qc_inspection_item_template as iit_svc
- from app.crud import qc_item_param as param_svc
- from app.crud import qc_task_result as result_svc
- from app.crud import qc_task as task_svc
- from app.crud import qc_third_party_company as tpc_svc
- from app.crud import qc_third_party_item as tpi_svc
- from app.crud import qc_material as mat_svc
- from app.crud import qc_material_stock_batch as batch_svc
- from app.crud import qc_material_stock_check as stock_check_svc
- from app.crud import qc_material_usage as mu_svc
- from app.crud import qc_material_amount_auth as amount_auth_svc
- from app.crud import qc_material_month_template as month_tpl_svc
- from app.crud.qc_material_stock_batch import _parse_date
- from app.core.qc_material_amount_auth_constants import QC_MATERIAL_AMOUNT_AUTH_LIST_PERM
- from app.crud import inbound_sample_special as inbound_sample_special_svc
- from app.crud import qc_work_order as wo_svc
- from app.crud.collection_protocol import total_pages
- from app.api.sample_reception import _get_current_user_id, _get_tenant_no, _page_payload
- from app.schemas.qc_work_order import QcWorkOrderCreateBody, QcWorkOrderUpdateBody
- from app.schemas.qc_task import QcTaskCreateBody
- router = APIRouter(prefix="/web/admin", tags=["质量检测"])
- # 协议/捐赠查看等场景只读打开质检任务详情(参数、附件)
- QC_TASK_READONLY_MENU_CODES = (
- "biz:qcTask:list",
- "biz:myQcTask:list",
- "biz:cellStorageProtocol:list",
- "biz:cellStorageProtocolAudit:list",
- "biz:researchDonation:list",
- "biz:researchDonationAudit:list",
- "biz:storageReleaseReview:list",
- "biz:qcWorkOrder:list",
- )
- # ---------- 第三方检测公司 ----------
- class ThirdPartyCompanyBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- company_code: str = Field(..., alias="companyCode")
- company_name: str = Field(..., alias="companyName")
- contact_name: Optional[str] = Field(None, alias="contactName")
- contact_phone: Optional[str] = Field(None, alias="contactPhone")
- company_logo: Optional[str] = Field(None, alias="companyLogo")
- address: Optional[str] = None
- invoice_title: Optional[str] = Field(None, alias="invoiceTitle")
- tax_id: Optional[str] = Field(None, alias="taxId")
- bank_name: Optional[str] = Field(None, alias="bankName")
- bank_account: Optional[str] = Field(None, alias="bankAccount")
- status: str = "1"
- order_num: Optional[int] = Field(None, alias="orderNum")
- remark: Optional[str] = None
- class ThirdPartyCompanyUpdateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- company_code: Optional[str] = Field(None, alias="companyCode")
- company_name: Optional[str] = Field(None, alias="companyName")
- contact_name: Optional[str] = Field(None, alias="contactName")
- contact_phone: Optional[str] = Field(None, alias="contactPhone")
- company_logo: Optional[str] = Field(None, alias="companyLogo")
- address: Optional[str] = None
- invoice_title: Optional[str] = Field(None, alias="invoiceTitle")
- tax_id: Optional[str] = Field(None, alias="taxId")
- bank_name: Optional[str] = Field(None, alias="bankName")
- bank_account: Optional[str] = Field(None, alias="bankAccount")
- status: Optional[str] = None
- order_num: Optional[int] = Field(None, alias="orderNum")
- remark: Optional[str] = None
- @router.get("/qc-third-party-companies")
- async def list_qc_third_party_companies(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100),
- keyword: Optional[str] = None,
- status: Optional[str] = None,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcThirdPartyCompany:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- items, total = tpc_svc.list_companies(
- db, page=page, page_size=pageSize, keyword=keyword, status=status, tenant_no=tenant_no,
- user_info=user
- )
- return success_response(_page_payload(items, total, page, pageSize))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-third-party-companies/options")
- async def list_qc_third_party_company_options(
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcWorkOrder:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- return success_response(tpc_svc.list_company_options(db, tenant_no))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/qc-third-party-companies")
- async def create_qc_third_party_company(
- body: ThirdPartyCompanyBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcThirdPartyCompany:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- obj = tpc_svc.create_company(
- db,
- company_code=body.company_code,
- company_name=body.company_name,
- contact_name=body.contact_name,
- contact_phone=body.contact_phone,
- company_logo=body.company_logo,
- address=body.address,
- invoice_title=body.invoice_title,
- tax_id=body.tax_id,
- bank_name=body.bank_name,
- bank_account=body.bank_account,
- status=body.status,
- tenant_no=tenant_no,
- order_num=body.order_num,
- remark=body.remark,
- user_id=_get_current_user_id(user),
- )
- return success_response(tpc_svc._row_to_dict(obj), message="创建成功")
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.put("/qc-third-party-companies/{company_id}")
- async def update_qc_third_party_company(
- company_id: str,
- body: ThirdPartyCompanyUpdateBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcThirdPartyCompany:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- obj = tpc_svc.update_company(
- db,
- company_id,
- company_code=body.company_code,
- company_name=body.company_name,
- contact_name=body.contact_name,
- contact_phone=body.contact_phone,
- company_logo=body.company_logo,
- address=body.address,
- invoice_title=body.invoice_title,
- tax_id=body.tax_id,
- bank_name=body.bank_name,
- bank_account=body.bank_account,
- status=body.status,
- order_num=body.order_num,
- remark=body.remark,
- user_id=_get_current_user_id(user),
- tenant_no=tenant_no,
- )
- return success_response(tpc_svc._row_to_dict(obj), message="更新成功")
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.delete("/qc-third-party-companies/{company_id}")
- async def delete_qc_third_party_company(
- company_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcThirdPartyCompany:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- tpc_svc.soft_delete_company(db, company_id, tenant_no=tenant_no)
- return success_response(None, message="删除成功")
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- # ---------- 第三方检测项目 ----------
- class ThirdPartyItemBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- company_id: str = Field(..., alias="companyId")
- item_code: str = Field(..., alias="itemCode")
- item_name: str = Field(..., alias="itemName")
- fee_amount: float = Field(..., alias="feeAmount")
- status: str = "1"
- order_num: Optional[int] = Field(None, alias="orderNum")
- remark: Optional[str] = None
- class ThirdPartyItemUpdateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- company_id: Optional[str] = Field(None, alias="companyId")
- item_code: Optional[str] = Field(None, alias="itemCode")
- item_name: Optional[str] = Field(None, alias="itemName")
- fee_amount: Optional[float] = Field(None, alias="feeAmount")
- status: Optional[str] = None
- order_num: Optional[int] = Field(None, alias="orderNum")
- remark: Optional[str] = None
- @router.get("/qc-third-party-items")
- async def list_qc_third_party_items(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100),
- keyword: Optional[str] = None,
- status: Optional[str] = None,
- companyId: Optional[str] = None,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcThirdPartyCompany:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- items, total = tpi_svc.list_items(
- db,
- page=page,
- page_size=pageSize,
- keyword=keyword,
- status=status,
- company_id=companyId,
- tenant_no=tenant_no,
- user_info=user,
- )
- return success_response(_page_payload(items, total, page, pageSize))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-third-party-items/active")
- async def list_qc_third_party_items_active(
- companyId: Optional[str] = None,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcWorkOrder:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- return success_response(tpi_svc.list_active_items(db, tenant_no, company_id=companyId, user_info=user))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/qc-third-party-items")
- async def create_qc_third_party_item(
- body: ThirdPartyItemBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcThirdPartyCompany:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- obj = tpi_svc.create_item(
- db,
- company_id=body.company_id,
- item_code=body.item_code,
- item_name=body.item_name,
- fee_amount=body.fee_amount,
- status=body.status,
- tenant_no=tenant_no,
- order_num=body.order_num,
- remark=body.remark,
- user_id=_get_current_user_id(user),
- )
- return success_response(tpi_svc._row_to_dict(obj, ""), message="创建成功")
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.put("/qc-third-party-items/{item_id}")
- async def update_qc_third_party_item(
- item_id: str,
- body: ThirdPartyItemUpdateBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcThirdPartyCompany:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- obj = tpi_svc.update_item(
- db,
- item_id,
- company_id=body.company_id,
- item_code=body.item_code,
- item_name=body.item_name,
- fee_amount=body.fee_amount,
- status=body.status,
- order_num=body.order_num,
- remark=body.remark,
- user_id=_get_current_user_id(user),
- tenant_no=tenant_no,
- )
- return success_response(tpi_svc._row_to_dict(obj, ""), message="更新成功")
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.delete("/qc-third-party-items/{item_id}")
- async def delete_qc_third_party_item(
- item_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcThirdPartyCompany:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- tpi_svc.soft_delete_item(db, item_id, tenant_no=tenant_no)
- return success_response(None, message="删除成功")
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- # ---------- 实验室自检项目 ----------
- class LabTestItemBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- item_code: str = Field(..., alias="itemCode")
- item_name: str = Field(..., alias="itemName")
- description: Optional[str] = None
- status: str = "1"
- order_num: Optional[int] = Field(None, alias="orderNum")
- remark: Optional[str] = None
- class LabTestItemUpdateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- item_code: Optional[str] = Field(None, alias="itemCode")
- item_name: Optional[str] = Field(None, alias="itemName")
- description: Optional[str] = None
- status: Optional[str] = None
- order_num: Optional[int] = Field(None, alias="orderNum")
- remark: Optional[str] = None
- # ---------- 检测项目模版(项目组合) ----------
- class InspectionItemTemplateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- template_name: str = Field(..., alias="templateName")
- lab_item_ids: List[str] = Field(default_factory=list, alias="labItemIds")
- third_party_item_ids: List[str] = Field(default_factory=list, alias="thirdPartyItemIds")
- status: Optional[str] = "1"
- order_num: Optional[int] = Field(None, alias="orderNum")
- remark: Optional[str] = None
- class InspectionItemTemplateUpdateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- template_name: Optional[str] = Field(None, alias="templateName")
- lab_item_ids: Optional[List[str]] = Field(None, alias="labItemIds")
- third_party_item_ids: Optional[List[str]] = Field(None, alias="thirdPartyItemIds")
- status: Optional[str] = None
- order_num: Optional[int] = Field(None, alias="orderNum")
- remark: Optional[str] = None
- # ---------- 检测项目参数模板 ----------
- class ItemParamBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- params: List[Dict[str, Any]] = Field(default_factory=list, alias="params")
- class TaskResultBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- results: List[Dict[str, Any]] = Field(default_factory=list, alias="results")
- class TaskCompleteWithResultBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- conclusion: Optional[str] = Field(None, alias="conclusion")
- results: List[Dict[str, Any]] = Field(default_factory=list, alias="results")
- remark: Optional[str] = Field(None, alias="remark")
- third_party_attachments: Optional[str] = Field(None, alias="thirdPartyAttachments")
- material_usages: Optional[List[Dict[str, Any]]] = Field(None, alias="materialUsages")
- task_status: Optional[str] = Field(None, alias="taskStatus")
- @router.get("/qc-lab-test-items")
- async def list_qc_lab_test_items(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100),
- keyword: Optional[str] = None,
- status: Optional[str] = None,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcLabTestItem:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- items, total = lab_svc.list_lab_items(
- db, page=page, page_size=pageSize, keyword=keyword, status=status, tenant_no=tenant_no,
- user_info=user
- )
- return success_response(_page_payload(items, total, page, pageSize))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-lab-test-items/active")
- async def list_qc_lab_test_items_active(
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcWorkOrder:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- return success_response(lab_svc.list_active_lab_items(db, tenant_no, user_info=user))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/qc-lab-test-items")
- async def create_qc_lab_test_item(
- body: LabTestItemBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcLabTestItem:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- obj = lab_svc.create_lab_item(
- db,
- item_code=body.item_code,
- item_name=body.item_name,
- description=body.description,
- status=body.status,
- tenant_no=tenant_no,
- order_num=body.order_num,
- remark=body.remark,
- user_id=_get_current_user_id(user),
- )
- return success_response(lab_svc._row_to_dict(obj), message="创建成功")
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.put("/qc-lab-test-items/{item_id}")
- async def update_qc_lab_test_item(
- item_id: str,
- body: LabTestItemUpdateBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcLabTestItem:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- obj = lab_svc.update_lab_item(
- db,
- item_id,
- item_code=body.item_code,
- item_name=body.item_name,
- description=body.description,
- status=body.status,
- order_num=body.order_num,
- remark=body.remark,
- user_id=_get_current_user_id(user),
- tenant_no=tenant_no,
- )
- return success_response(lab_svc._row_to_dict(obj), message="更新成功")
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.delete("/qc-lab-test-items/{item_id}")
- async def delete_qc_lab_test_item(
- item_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcLabTestItem:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- lab_svc.soft_delete_lab_item(db, item_id, tenant_no=tenant_no)
- return success_response(None, message="删除成功")
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-inspection-item-templates")
- async def list_qc_inspection_item_templates(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100),
- keyword: Optional[str] = None,
- status: Optional[str] = None,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcInspectionItemTemplate:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- items, total = iit_svc.list_templates(
- db,
- page=page,
- page_size=pageSize,
- keyword=keyword,
- status=status,
- tenant_no=tenant_no,
- user_info=user,
- )
- return success_response(_page_payload(items, total, page, pageSize))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-inspection-item-templates/active")
- async def list_qc_inspection_item_templates_active(
- db: Session = Depends(get_db),
- user: dict = Depends(
- require_any_menu_code(
- "biz:qcInspectionItemTemplate:list", "biz:qcWorkOrder:list"
- )
- ),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- return success_response(
- iit_svc.list_active_templates(db, tenant_no, user_info=user)
- )
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-inspection-item-templates/{template_id}")
- async def get_qc_inspection_item_template(
- template_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcInspectionItemTemplate:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- obj = iit_svc.get_template(db, template_id, tenant_no=tenant_no)
- if not obj:
- return error_response(404, "模版不存在")
- return success_response(iit_svc._row_to_dict(obj))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/qc-inspection-item-templates")
- async def create_qc_inspection_item_template(
- body: InspectionItemTemplateBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcInspectionItemTemplate:add")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- obj = iit_svc.create_template(
- db,
- template_name=body.template_name,
- lab_item_ids=body.lab_item_ids,
- third_party_item_ids=body.third_party_item_ids,
- status=body.status or "1",
- tenant_no=tenant_no,
- order_num=body.order_num,
- remark=body.remark,
- user_id=_get_current_user_id(user),
- create_dept_id=user.get("dept_id", "") or "",
- )
- return success_response(iit_svc._row_to_dict(obj), message="创建成功")
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.put("/qc-inspection-item-templates/{template_id}")
- async def update_qc_inspection_item_template(
- template_id: str,
- body: InspectionItemTemplateUpdateBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcInspectionItemTemplate:edit")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- obj = iit_svc.update_template(
- db,
- template_id,
- template_name=body.template_name,
- lab_item_ids=body.lab_item_ids,
- third_party_item_ids=body.third_party_item_ids,
- status=body.status,
- order_num=body.order_num,
- remark=body.remark,
- user_id=_get_current_user_id(user),
- tenant_no=tenant_no,
- )
- return success_response(iit_svc._row_to_dict(obj), message="更新成功")
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.delete("/qc-inspection-item-templates/{template_id}")
- async def delete_qc_inspection_item_template(
- template_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcInspectionItemTemplate:remove")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- iit_svc.soft_delete_template(db, template_id, tenant_no=tenant_no)
- return success_response(None, message="删除成功")
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- # ---------- 检测项目参数模板 ----------
- @router.get("/qc-item-params")
- async def list_item_params(
- itemSource: str = Query(...),
- itemId: str = Query(...),
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcLabTestItem:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- return success_response(param_svc.list_params_by_item(db, itemSource, itemId, tenant_no, user_info=user))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.put("/qc-item-params")
- async def save_item_params(
- body: ItemParamBody,
- itemSource: str = Query(...),
- itemId: str = Query(...),
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcLabTestItem:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- return success_response(
- param_svc.save_params_for_item(db, itemSource, itemId, body.params, tenant_no, _get_current_user_id(user)),
- message="保存成功",
- )
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- # ---------- 质检工单 ----------
- class WorkOrderConfigureBatchItem(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- id: Optional[str] = None
- batch_name: Optional[str] = Field(None, alias="batchName")
- assignee_user_ids: List[str] = Field(default_factory=list, alias="assigneeUserIds")
- has_lab: bool = Field(False, alias="hasLab")
- has_third_party: bool = Field(False, alias="hasThirdParty")
- lab_item_ids: List[str] = Field(default_factory=list, alias="labItemIds")
- third_party_item_ids: List[str] = Field(default_factory=list, alias="thirdPartyItemIds")
- class WorkOrderConfigureBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- batches: Optional[List[WorkOrderConfigureBatchItem]] = Field(None, alias="batches")
- has_lab: bool = Field(False, alias="hasLab")
- has_third_party: bool = Field(False, alias="hasThirdParty")
- lab_item_ids: List[str] = Field(default_factory=list, alias="labItemIds")
- third_party_item_ids: List[str] = Field(default_factory=list, alias="thirdPartyItemIds")
- lab_item_assignees: Dict[str, List[str]] = Field(default_factory=dict, alias="labItemAssignees")
- third_party_item_assignees: Dict[str, List[str]] = Field(
- default_factory=dict, alias="thirdPartyItemAssignees"
- )
- @router.post("/qc-work-orders")
- async def create_qc_work_order(
- body: QcWorkOrderCreateBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcWorkOrder:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- wo_type = (body.work_order_type or "inbound").strip()
- if wo_type == "temporary":
- wo = wo_svc.create_temp_qc_work_order(
- db,
- tenant_no=tenant_no,
- user_id=user.get("user_id"),
- create_dept_id=user.get("dept_id", ""),
- cell_product_id=body.cell_product_id or "",
- external_target_code=body.external_target_code or "",
- remark=body.remark or "",
- )
- else:
- wo = wo_svc.create_qc_work_order_manual(
- db,
- tenant_no=tenant_no,
- user_id=user.get("user_id"),
- create_dept_id=user.get("dept_id", ""),
- protocol_no=body.protocol_no or "",
- sample_code=body.sample_code or "",
- reception_source=body.reception_source or "cell_storage",
- qc_biz_type=body.qc_biz_type or "inbound",
- remark=body.remark or "",
- )
- db.commit()
- return success_response(wo_svc._wo_to_dict(wo), message="创建成功")
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- db.rollback()
- return error_response(400, str(e))
- @router.get("/qc-work-orders")
- async def list_qc_work_orders(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100),
- keyword: Optional[str] = None,
- workOrderStatus: Optional[str] = Query(None, description="工单状态,多个用英文逗号分隔"),
- workOrderType: Optional[str] = None,
- bizType: Optional[str] = Query(None, alias="bizType"),
- receptionSource: Optional[str] = Query(None, alias="receptionSource"),
- createTimeStart: Optional[str] = Query(None, alias="createTimeStart"),
- createTimeEnd: Optional[str] = Query(None, alias="createTimeEnd"),
- infusionTimeStart: Optional[str] = Query(None, alias="infusionTimeStart"),
- infusionTimeEnd: Optional[str] = Query(None, alias="infusionTimeEnd"),
- triggerSource: Optional[str] = Query(None, alias="triggerSource"),
- sampleTypeId: Optional[str] = Query(None, alias="sampleTypeId"),
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcWorkOrder:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- items, total = wo_svc.list_work_orders(
- db,
- page=page,
- page_size=pageSize,
- keyword=keyword,
- work_order_status=workOrderStatus,
- work_order_type=workOrderType,
- biz_type=bizType,
- reception_source=receptionSource,
- trigger_source=triggerSource,
- sample_type_id=sampleTypeId,
- create_time_start=createTimeStart,
- create_time_end=createTimeEnd,
- infusion_time_start=infusionTimeStart,
- infusion_time_end=infusionTimeEnd,
- tenant_no=tenant_no,
- user_info=user,
- )
- return success_response(_page_payload(items, total, page, pageSize))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-work-orders/tab-unfinished-counts")
- async def qc_work_order_tab_unfinished_counts(
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcWorkOrder:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- counts = wo_svc.count_unfinished_work_orders_by_tab(
- db, tenant_no=tenant_no, user_info=user
- )
- return success_response(counts)
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-work-orders/mine-tab-unfinished-counts")
- async def qc_work_order_mine_tab_unfinished_counts(
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:myQcTask:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- counts = wo_svc.count_mine_unfinished_work_orders_by_tab(
- db,
- tenant_no=tenant_no,
- assignee_user_id=_get_current_user_id(user),
- )
- return success_response(counts)
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-work-orders/by-sample")
- async def list_qc_work_orders_by_sample(
- sampleCode: str = Query(..., alias="sampleCode"),
- receptionSource: str = Query(..., alias="receptionSource"),
- db: Session = Depends(get_db),
- user: dict = Depends(
- require_any_menu_code("biz:qcWorkOrder:list", "biz:myQcTask:list")
- ),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- items = wo_svc.list_work_orders_by_sample(
- db,
- sample_code=sampleCode,
- reception_source=receptionSource,
- tenant_no=tenant_no,
- user_info=user,
- )
- return success_response({"list": items})
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-work-orders/{work_order_id}")
- async def get_qc_work_order_detail(
- work_order_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcWorkOrder:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- data = wo_svc.get_work_order_detail(db, work_order_id, tenant_no)
- _maybe_strip_qc_third_party_fee_fields(db, user, data)
- return success_response(data)
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-work-orders/{work_order_id}/outbound-detail")
- async def get_outbound_qc_detail(
- work_order_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcWorkOrder:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- data = wo_svc.get_outbound_qc_detail(db, work_order_id, tenant_no)
- _maybe_strip_qc_third_party_fee_fields(db, user, data)
- return success_response(data)
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.put("/qc-work-orders/{work_order_id}")
- async def update_qc_work_order(
- work_order_id: str,
- body: QcWorkOrderUpdateBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcWorkOrder:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- wo = wo_svc.update_qc_work_order(
- db,
- work_order_id,
- tenant_no=tenant_no,
- user_id=_get_current_user_id(user),
- cell_product_id=body.cell_product_id,
- external_target_code=body.external_target_code,
- work_order_status=body.work_order_status,
- qc_conclusion=body.qc_conclusion,
- remark=body.remark,
- )
- db.commit()
- return success_response(wo_svc._wo_to_dict(wo), message="保存成功")
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- db.rollback()
- return error_response(400, str(e))
- except Exception as e:
- db.rollback()
- return error_response(400, str(e))
- @router.delete("/qc-work-orders/{work_order_id}")
- async def delete_qc_work_order_api(
- work_order_id: str,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- wo_svc.delete_qc_work_order(db, work_order_id, tenant_no, user.get("user_id"))
- return success_response(None, message="删除成功")
- except ValueError as e:
- return error_response(400, str(e))
- class InboundSampleSpecialMarkBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- reception_source: str = Field(..., alias="receptionSource")
- protocol_sample_id: str = Field(..., alias="protocolSampleId")
- is_special: bool = Field(..., alias="isSpecial")
- special_remark: Optional[str] = Field(None, alias="specialRemark")
- @router.put("/inbound-samples/special-mark")
- async def update_inbound_sample_special_mark(
- body: InboundSampleSpecialMarkBody,
- db: Session = Depends(get_db),
- user: dict = Depends(
- require_any_menu_code("biz:qcWorkOrder:list", "biz:myQcTask:list")
- ),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- source = (body.reception_source or "").strip()
- if source not in ("cell_storage", "research_donation"):
- return error_response(400, "仅支持细胞存储或科研捐赠样本")
- data = inbound_sample_special_svc.update_inbound_sample_special_mark(
- db,
- tenant_no=tenant_no,
- reception_source=source,
- protocol_sample_id=body.protocol_sample_id,
- is_special=body.is_special,
- special_remark=body.special_remark,
- user_id=user.get("user_id") or "",
- )
- db.commit()
- return success_response(data, message="保存成功")
- except ValueError as e:
- db.rollback()
- return error_response(400, str(e))
- except Exception as e:
- db.rollback()
- return error_response(400, str(e))
- @router.post("/qc-work-orders/{work_order_id}/configure")
- async def configure_qc_work_order(
- work_order_id: str,
- body: WorkOrderConfigureBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcWorkOrder:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- obj = wo_svc.configure_work_order(
- db,
- work_order_id,
- has_lab=body.has_lab,
- has_third_party=body.has_third_party,
- lab_item_ids=body.lab_item_ids,
- third_party_item_ids=body.third_party_item_ids,
- lab_item_assignees=body.lab_item_assignees,
- third_party_item_assignees=body.third_party_item_assignees,
- batches=[b.model_dump(by_alias=True) for b in body.batches] if body.batches is not None else None,
- tenant_no=tenant_no,
- user_id=_get_current_user_id(user),
- create_dept_id=user.get("dept_id", ""),
- )
- return success_response(wo_svc._wo_to_dict(obj), message="配置成功")
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- class WorkOrderInspectionBatchesBatchBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- work_order_ids: List[str] = Field(default_factory=list, alias="workOrderIds")
- @router.post("/qc-work-orders/inspection-batches/batch")
- async def batch_list_qc_work_order_inspection_batches(
- body: WorkOrderInspectionBatchesBatchBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_any_menu_code(*QC_TASK_READONLY_MENU_CODES)),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- from app.crud import qc_inspection_batch as batch_svc
- data = batch_svc.list_inspection_batches_by_work_order_ids(
- db, body.work_order_ids, tenant_no
- )
- return success_response(data)
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-work-orders/{work_order_id}/inspection-batches")
- async def list_qc_work_order_inspection_batches(
- work_order_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(require_any_menu_code(*QC_TASK_READONLY_MENU_CODES)),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- from app.crud import qc_inspection_batch as batch_svc
- wo = wo_svc.get_work_order(db, work_order_id, tenant_no=tenant_no)
- if not wo:
- return error_response(404, "工单不存在")
- return success_response(batch_svc.list_inspection_batches(db, work_order_id, tenant_no))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- # ---------- 质检任务 ----------
- class TaskAttachmentsBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- third_party_attachments: str = Field("", alias="thirdPartyAttachments")
- @router.post("/qc-tasks")
- async def create_qc_task(
- body: QcTaskCreateBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcTask:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- task = task_svc.create_qc_task_manual(
- db,
- tenant_no=tenant_no,
- user_id=user.get("user_id"),
- create_dept_id=user.get("dept_id", ""),
- task_type=body.task_type,
- item_code=body.item_code,
- item_name=body.item_name,
- company_id=body.company_id,
- company_name=body.company_name,
- fee_amount=body.fee_amount,
- sample_code=body.sample_code or "",
- work_order_id=body.work_order_id or "",
- remark=body.remark or "",
- )
- db.commit()
- return success_response(task_svc._task_to_dict(task), message="创建成功")
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- db.rollback()
- return error_response(400, str(e))
- @router.get("/qc-tasks/mine")
- async def list_my_qc_tasks(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100),
- keyword: Optional[str] = None,
- taskType: Optional[str] = None,
- taskStatus: Optional[str] = None,
- qcBizType: Optional[str] = None,
- receptionSource: Optional[str] = Query(None, alias="receptionSource"),
- bizType: Optional[str] = Query(None, alias="bizType"),
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:myQcTask:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- items, total = task_svc.list_mine_tasks(
- db,
- assignee_user_id=_get_current_user_id(user),
- page=page,
- page_size=pageSize,
- keyword=keyword,
- task_type=taskType,
- task_status=taskStatus,
- qc_biz_type=qcBizType,
- reception_source=receptionSource,
- biz_type=bizType,
- tenant_no=tenant_no,
- )
- return success_response(_page_payload(items, total, page, pageSize))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-tasks")
- async def list_qc_tasks(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100),
- keyword: Optional[str] = None,
- taskType: Optional[str] = None,
- taskStatus: Optional[str] = None,
- workOrderId: Optional[str] = None,
- qcBizType: Optional[str] = None,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcTask:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- items, total = task_svc.list_tasks(
- db,
- page=page,
- page_size=pageSize,
- keyword=keyword,
- task_type=taskType,
- task_status=taskStatus,
- work_order_id=workOrderId,
- qc_biz_type=qcBizType,
- tenant_no=tenant_no,
- user_info=user,
- )
- return success_response(_page_payload(items, total, page, pageSize))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-tasks/{task_id}")
- async def get_qc_task_detail(
- task_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(require_any_menu_code(*QC_TASK_READONLY_MENU_CODES)),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- data = task_svc.get_task_detail(db, task_id, tenant_no)
- return success_response(data)
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/qc-tasks/{task_id}/start")
- async def start_qc_task(
- task_id: str,
- operateScope: Optional[str] = Query(None, alias="operateScope"),
- db: Session = Depends(get_db),
- user: dict = Depends(require_any_menu_code("biz:qcTask:list", "biz:myQcTask:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- obj = task_svc.start_task(db, task_id, tenant_no=tenant_no, user_id=_get_current_user_id(user))
- return success_response(task_svc._task_to_dict(obj), message="已开始")
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/qc-tasks/{task_id}/complete")
- async def complete_qc_task(
- task_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcTask:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- obj = task_svc.complete_task(
- db, task_id, tenant_no=tenant_no, user_id=_get_current_user_id(user)
- )
- return success_response(task_svc._task_to_dict(obj), message="已完成")
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-tasks/{task_id}/results")
- async def get_task_results(
- task_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(require_any_menu_code(*QC_TASK_READONLY_MENU_CODES)),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- return success_response(result_svc.list_results_by_task(db, task_id, tenant_no, user_info=user))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-material-requisition-options")
- async def list_qc_material_requisition_options(
- keyword: Optional[str] = None,
- limit: int = Query(50, ge=1, le=100),
- db: Session = Depends(get_db),
- user: dict = Depends(require_any_menu_code("biz:qcTask:list", "biz:myQcTask:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- items = mu_svc.list_requisition_options(
- db,
- tenant_no,
- keyword=keyword,
- limit=limit,
- user_info=user,
- )
- return success_response(items)
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-tasks/{task_id}/material-usages")
- async def get_task_material_usages(
- task_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(require_any_menu_code(*QC_TASK_READONLY_MENU_CODES)),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- t = task_svc.get_task(db, task_id, tenant_no=tenant_no)
- if not t:
- return error_response(404, "任务不存在")
- return success_response(
- mu_svc.list_usages_by_task(db, task_id, tenant_no, user_info=user)
- )
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/qc-tasks/{task_id}/complete-with-result")
- async def complete_task_with_result(
- task_id: str,
- body: TaskCompleteWithResultBody,
- operateScope: Optional[str] = Query(None, alias="operateScope"),
- db: Session = Depends(get_db),
- user: dict = Depends(require_any_menu_code("biz:qcTask:list", "biz:myQcTask:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- t = task_svc.get_task(db, task_id, tenant_no=tenant_no)
- if not t:
- return error_response(404, "任务不存在")
- was_done = (t.task_status or "").strip() == "ywc"
- if body.results:
- result_svc.save_results_for_task(
- db, task_id, body.results, tenant_no, user_id, commit=False,
- )
- if body.third_party_attachments is not None:
- task_svc.update_task_attachments(
- db, task_id, attachments=body.third_party_attachments,
- tenant_no=tenant_no, user_id=user_id, commit=False,
- )
- obj = task_svc.apply_task_result(
- db,
- task_id,
- tenant_no=tenant_no,
- user_id=user_id,
- conclusion=body.conclusion,
- remark=body.remark,
- material_usages=body.material_usages,
- task_status=body.task_status,
- commit=False,
- )
- db.commit()
- db.refresh(obj)
- if (obj.task_status or "").strip() == "ywc":
- wo_svc.try_complete_work_order(db, obj.work_order_id, tenant_no)
- elif was_done:
- wo_svc.try_reopen_work_order(db, obj.work_order_id, tenant_no)
- return success_response(
- task_svc._task_to_dict(obj, work_order_no="", db=db, tenant_no=tenant_no),
- message="已保存",
- )
- except HTTPException as e:
- db.rollback()
- detail = e.detail
- msg = detail if isinstance(detail, str) else str(detail)
- return error_response(e.status_code, msg)
- except PermissionError as e:
- db.rollback()
- return error_response(403, str(e))
- except ValueError as e:
- db.rollback()
- return error_response(400, str(e))
- except Exception as e:
- db.rollback()
- return error_response(400, str(e))
- @router.put("/qc-tasks/{task_id}/attachments")
- async def update_qc_task_attachments(
- task_id: str,
- body: TaskAttachmentsBody,
- operateScope: Optional[str] = Query(None, alias="operateScope"),
- db: Session = Depends(get_db),
- user: dict = Depends(require_any_menu_code("biz:qcTask:list", "biz:myQcTask:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- obj = task_svc.update_task_attachments(
- db,
- task_id,
- attachments=body.third_party_attachments,
- tenant_no=tenant_no,
- user_id=_get_current_user_id(user),
- )
- return success_response(task_svc._task_to_dict(obj), message="已保存")
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.delete("/qc-tasks/{task_id}")
- async def delete_qc_task_api(
- task_id: str,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- task_svc.delete_qc_task(db, task_id, tenant_no, user.get("user_id"))
- return success_response(None, message="删除成功")
- except ValueError as e:
- return error_response(400, str(e))
- # ---------- 质检物料库 ----------
- def _maybe_strip_qc_material_amount_fields(
- db: Session, user: dict, rows: Iterable[Dict[str, Any]]
- ) -> None:
- if user_has_menu_perm(db, user, QC_MATERIAL_VIEW_AMOUNT_PERM):
- return
- strip_row_fields(rows, *QC_MATERIAL_AMOUNT_FIELD_NAMES)
- def _user_can_view_qc_material_amount(db: Session, user: dict) -> bool:
- return user_has_menu_perm(db, user, QC_MATERIAL_VIEW_AMOUNT_PERM)
- def _strip_amount_from_write_data(db: Session, user: dict, data: Dict[str, Any]) -> None:
- if _user_can_view_qc_material_amount(db, user):
- return
- for key in ("unit_price", "unitPrice", "amount"):
- data.pop(key, None)
- def _resolve_stock_in_amounts(
- db: Session, user: dict, unit_price: Any, amount: Any
- ) -> tuple:
- if _user_can_view_qc_material_amount(db, user):
- return unit_price, amount
- return 0, 0
- def _maybe_strip_qc_third_party_fee_fields(
- db: Session, user: dict, data: Dict[str, Any]
- ) -> None:
- if user_has_menu_perm(db, user, QC_THIRD_PARTY_VIEW_AMOUNT_PERM):
- return
- strip_row_fields([data], "thirdPartyFeeTotal")
- third_items = data.get("thirdPartyItems")
- if isinstance(third_items, list):
- strip_row_fields(third_items, "feeAmount")
- tasks = data.get("tasks")
- if isinstance(tasks, list):
- for task in tasks:
- if isinstance(task, dict) and (task.get("taskType") or "").strip() == "dsfjc":
- strip_row_fields([task], "feeAmount")
- work_order = data.get("workOrder")
- if isinstance(work_order, dict):
- strip_row_fields([work_order], "thirdPartyFeeTotal")
- wo_tasks = work_order.get("tasks")
- if isinstance(wo_tasks, list):
- for task in wo_tasks:
- if isinstance(task, dict) and (task.get("taskType") or "").strip() == "dsfjc":
- strip_row_fields([task], "feeAmount")
- def _maybe_strip_qc_material_stock_check_detail(db: Session, user: dict, data: Dict[str, Any]) -> None:
- if user_has_menu_perm(db, user, QC_MATERIAL_VIEW_AMOUNT_PERM):
- return
- for key in ("batchLines", "unusedLines"):
- lines = data.get(key)
- if isinstance(lines, list):
- strip_row_fields(lines, *QC_MATERIAL_AMOUNT_FIELD_NAMES)
- class QcMaterialBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- material_code: str = Field(..., alias="materialCode")
- material_name: str = Field(..., alias="materialName")
- spec_model: Optional[str] = Field(None, alias="specModel")
- unit: Optional[str] = None
- warning_qty: Optional[float] = Field(0, alias="warningQty")
- status: str = "1"
- remark: Optional[str] = None
- class QcMaterialUpdateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- material_code: Optional[str] = Field(None, alias="materialCode")
- material_name: Optional[str] = Field(None, alias="materialName")
- spec_model: Optional[str] = Field(None, alias="specModel")
- unit: Optional[str] = None
- warning_qty: Optional[float] = Field(None, alias="warningQty")
- status: Optional[str] = None
- remark: Optional[str] = None
- class StockInBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- stock_in_no: str = Field(..., alias="stockInNo")
- stock_in_date: str = Field(..., alias="stockInDate")
- expire_date: str = Field(..., alias="expireDate")
- catalog_no: str = Field(..., alias="catalogNo")
- batch_no: str = Field(..., alias="batchNo")
- manufacturer: str
- unit_price: float = Field(..., alias="unitPrice")
- amount: float
- qty: float = Field(..., gt=0)
- remark: Optional[str] = None
- stock_in_time: Optional[str] = Field(None, alias="stockInTime")
- class BatchStockInItem(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- material_id: str = Field(..., alias="materialId")
- stock_in_no: str = Field(..., alias="stockInNo")
- stock_in_date: str = Field(..., alias="stockInDate")
- expire_date: str = Field(..., alias="expireDate")
- catalog_no: str = Field(..., alias="catalogNo")
- batch_no: str = Field(..., alias="batchNo")
- manufacturer: str
- unit_price: float = Field(..., alias="unitPrice")
- amount: float
- qty: float = Field(..., gt=0)
- remark: Optional[str] = None
- class BatchStockInBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- items: List[BatchStockInItem] = Field(..., min_length=1)
- remark: Optional[str] = None
- stock_in_time: Optional[str] = Field(None, alias="stockInTime")
- class QcMonthTemplateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- material_id: Optional[str] = Field(None, alias="materialId")
- material_code: Optional[str] = Field(None, alias="materialCode")
- material_name: Optional[str] = Field(None, alias="materialName")
- spec_model: Optional[str] = Field(None, alias="specModel")
- unit: Optional[str] = None
- catalog_no: Optional[str] = Field(None, alias="catalogNo")
- batch_no: Optional[str] = Field(None, alias="batchNo")
- manufacturer: Optional[str] = None
- unit_price: Optional[float] = Field(None, alias="unitPrice")
- amount: Optional[float] = None
- in_qty: Optional[float] = Field(None, alias="inQty")
- expire_date: Optional[str] = Field(None, alias="expireDate")
- order_num: Optional[int] = Field(None, alias="orderNum")
- remark: Optional[str] = None
- class QcMonthTemplatePreviewBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- stock_in_date: str = Field(..., alias="stockInDate")
- class QcMonthTemplateConfirmItem(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- material_id: Optional[str] = Field(None, alias="materialId")
- material_code: str = Field(..., alias="materialCode")
- material_name: str = Field(..., alias="materialName")
- spec_model: Optional[str] = Field(None, alias="specModel")
- unit: Optional[str] = None
- stock_in_no: str = Field(..., alias="stockInNo")
- expire_date: str = Field(..., alias="expireDate")
- catalog_no: str = Field(..., alias="catalogNo")
- batch_no: str = Field(..., alias="batchNo")
- manufacturer: str
- unit_price: float = Field(..., alias="unitPrice")
- amount: float
- qty: float = Field(..., gt=0)
- remark: Optional[str] = None
- class QcMonthTemplateConfirmBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- stock_in_date: str = Field(..., alias="stockInDate")
- items: List[QcMonthTemplateConfirmItem] = Field(..., min_length=1)
- remark: Optional[str] = None
- stock_in_time: Optional[str] = Field(None, alias="stockInTime")
- class BatchStockOutItem(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- batch_id: str = Field(..., alias="batchId")
- qty: float = Field(..., gt=0)
- remark: Optional[str] = None
- class BatchStockOutBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- items: List[BatchStockOutItem] = Field(..., min_length=1)
- receiver: Optional[str] = None
- purpose: Optional[str] = None
- remark: Optional[str] = None
- stock_out_time: Optional[str] = Field(None, alias="stockOutTime")
- class BatchDiscardItem(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- batch_id: str = Field(..., alias="batchId")
- qty: float = Field(..., gt=0)
- usage_unit: Optional[str] = Field(None, alias="usageUnit")
- remark: Optional[str] = None
- class BatchDiscardBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- items: List[BatchDiscardItem] = Field(..., min_length=1)
- discard_reason: str = Field(..., alias="discardReason")
- remark: Optional[str] = None
- discard_time: Optional[str] = Field(None, alias="discardTime")
- class QcMaterialBatchUpdateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- stock_in_no: Optional[str] = Field(None, alias="stockInNo")
- stock_in_date: Optional[str] = Field(None, alias="stockInDate")
- expire_date: Optional[str] = Field(None, alias="expireDate")
- catalog_no: Optional[str] = Field(None, alias="catalogNo")
- batch_no: Optional[str] = Field(None, alias="batchNo")
- manufacturer: Optional[str] = None
- unit_price: Optional[float] = Field(None, alias="unitPrice")
- amount: Optional[float] = None
- in_qty: Optional[float] = Field(None, alias="inQty")
- remaining_qty: Optional[float] = Field(None, alias="remainingQty")
- remark: Optional[str] = None
- qc_attachment_urls: Optional[str] = Field(None, alias="qcAttachmentUrls")
- material_code: Optional[str] = Field(None, alias="materialCode")
- material_name: Optional[str] = Field(None, alias="materialName")
- spec_model: Optional[str] = Field(None, alias="specModel")
- unit: Optional[str] = None
- warning_qty: Optional[float] = Field(None, alias="warningQty")
- status: Optional[str] = None
- @router.get("/qc-material-batches")
- async def list_qc_material_batches(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100),
- keyword: Optional[str] = None,
- materialId: Optional[str] = None,
- status: Optional[str] = None,
- stockStatus: Optional[str] = None,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- items, total = batch_svc.list_batches(
- db,
- page=page,
- page_size=pageSize,
- keyword=keyword,
- material_id=materialId,
- status=status,
- stock_status=stockStatus,
- tenant_no=tenant_no,
- user_info=user,
- )
- _maybe_strip_qc_material_amount_fields(db, user, items)
- return success_response(_page_payload(items, total, page, pageSize))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-material-batches/options")
- async def qc_material_batch_options(
- keyword: Optional[str] = None,
- materialId: Optional[str] = None,
- onlyAvailable: bool = Query(True, alias="onlyAvailable"),
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- options = batch_svc.batch_options(
- db,
- tenant_no,
- keyword=keyword,
- material_id=materialId,
- only_available=onlyAvailable,
- )
- _maybe_strip_qc_material_amount_fields(db, user, options)
- return success_response(options)
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-material-batches/{batch_id}")
- async def get_qc_material_batch(
- batch_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- payload = batch_svc.get_batch_detail(db, batch_id, tenant_no=tenant_no)
- _maybe_strip_qc_material_amount_fields(db, user, [payload])
- return success_response(payload)
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.put("/qc-material-batches/{batch_id}")
- async def update_qc_material_batch(
- batch_id: str,
- body: QcMaterialBatchUpdateBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- data = body.model_dump(exclude_none=True)
- _strip_amount_from_write_data(db, user, data)
- obj = batch_svc.update_batch(
- db,
- batch_id,
- tenant_no=tenant_no,
- user_id=user_id,
- **data,
- )
- material = mat_svc.get_material(db, obj.material_id, tenant_no=tenant_no)
- payload = batch_svc._batch_row_to_dict(obj, material)
- _maybe_strip_qc_material_amount_fields(db, user, [payload])
- return success_response(
- payload,
- message="更新成功",
- )
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.delete("/qc-material-batches/{batch_id}")
- async def delete_qc_material_batch(
- batch_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- batch_svc.delete_batch(db, batch_id, tenant_no=tenant_no, user_id=user_id)
- return success_response(None, message="删除成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-materials")
- async def list_qc_materials(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100),
- keyword: Optional[str] = None,
- status: Optional[str] = None,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- items, total = mat_svc.list_materials(
- db, page=page, page_size=pageSize, keyword=keyword, status=status, tenant_no=tenant_no,
- user_info=user,
- )
- return success_response(_page_payload(items, total, page, pageSize))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-materials/options")
- async def qc_material_options(
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- return success_response(mat_svc.options_list(db, tenant_no=tenant_no))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/qc-materials")
- async def create_qc_material(
- body: QcMaterialBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- obj = mat_svc.create_material(
- db,
- material_code=body.material_code,
- material_name=body.material_name,
- spec_model=body.spec_model,
- unit=body.unit,
- warning_qty=Decimal(str(body.warning_qty))
- if body.warning_qty is not None
- else None,
- status=mat_svc._norm_status(body.status),
- remark=body.remark,
- tenant_no=tenant_no,
- user_id=user_id,
- )
- return success_response(mat_svc._row_to_dict(obj), message="创建成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.put("/qc-materials/{material_id}")
- async def update_qc_material(
- material_id: str,
- body: QcMaterialUpdateBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- data = body.model_dump(exclude_none=True)
- if "warning_qty" in data:
- data["warning_qty"] = Decimal(str(data["warning_qty"]))
- if "status" in data:
- data["status"] = mat_svc._norm_status(data["status"])
- obj = mat_svc.update_material(
- db,
- material_id,
- tenant_no=tenant_no,
- user_id=user_id,
- **data,
- )
- return success_response(mat_svc._row_to_dict(obj), message="更新成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.delete("/qc-materials/{material_id}")
- async def delete_qc_material(
- material_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- mat_svc.soft_delete_material(db, material_id, tenant_no=tenant_no)
- return success_response(None, message="删除成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/qc-materials/batch-stock-in")
- async def batch_stock_in_qc_material(
- body: BatchStockInBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- operate_time = datetime.now()
- if body.stock_in_time:
- rt = parse_datetime(body.stock_in_time)
- if not rt:
- return error_response(400, "入库时间格式无效")
- operate_time = rt
- items = []
- for x in body.items:
- unit_price, amount = _resolve_stock_in_amounts(
- db, user, x.unit_price, x.amount
- )
- items.append(
- {
- "material_id": x.material_id,
- "materialId": x.material_id,
- "stock_in_no": x.stock_in_no,
- "stockInNo": x.stock_in_no,
- "stock_in_date": x.stock_in_date,
- "stockInDate": x.stock_in_date,
- "expire_date": x.expire_date,
- "expireDate": x.expire_date,
- "catalog_no": x.catalog_no,
- "catalogNo": x.catalog_no,
- "batch_no": x.batch_no,
- "batchNo": x.batch_no,
- "manufacturer": x.manufacturer,
- "unit_price": unit_price,
- "unitPrice": unit_price,
- "amount": amount,
- "qty": x.qty,
- "remark": x.remark,
- }
- )
- count = batch_svc.batch_stock_in(
- db,
- items=items,
- remark=body.remark,
- tenant_no=tenant_no,
- user_id=user_id,
- operate_time=operate_time,
- )
- return success_response({"count": count}, message=f"批量入库成功,共 {count} 项")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/qc-materials/batch-stock-out")
- async def batch_stock_out_qc_material(
- body: BatchStockOutBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- operate_time = datetime.now()
- if body.stock_out_time:
- rt = parse_datetime(body.stock_out_time)
- if not rt:
- return error_response(400, "出库时间格式无效")
- operate_time = rt
- items = [
- {"batch_id": x.batch_id, "batchId": x.batch_id, "qty": x.qty, "remark": x.remark}
- for x in body.items
- ]
- count = batch_svc.batch_stock_out(
- db,
- items=items,
- receiver=body.receiver,
- purpose=body.purpose,
- remark=body.remark,
- tenant_no=tenant_no,
- user_id=user_id,
- operate_time=operate_time,
- )
- return success_response({"count": count}, message=f"批量出库成功,共 {count} 项")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/qc-materials/batch-discard")
- async def batch_discard_qc_material(
- body: BatchDiscardBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- operate_time = datetime.now()
- if body.discard_time:
- rt = parse_datetime(body.discard_time)
- if not rt:
- return error_response(400, "废弃时间格式无效")
- operate_time = rt
- items = [
- {
- "batch_id": x.batch_id,
- "batchId": x.batch_id,
- "qty": x.qty,
- "usageUnit": x.usage_unit,
- "usage_unit": x.usage_unit,
- "remark": x.remark,
- }
- for x in body.items
- ]
- count = batch_svc.batch_discard(
- db,
- items=items,
- discard_reason=body.discard_reason,
- remark=body.remark,
- tenant_no=tenant_no,
- user_id=user_id,
- operate_time=operate_time,
- )
- return success_response({"count": count}, message=f"批量废弃成功,共 {count} 项")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/qc-materials/{material_id}/stock-in")
- async def stock_in_qc_material(
- material_id: str,
- body: StockInBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- operate_time = datetime.now()
- if body.stock_in_time:
- rt = parse_datetime(body.stock_in_time)
- if not rt:
- return error_response(400, "入库时间格式无效")
- operate_time = rt
- from decimal import Decimal
- unit_price, amount = _resolve_stock_in_amounts(
- db, user, body.unit_price, body.amount
- )
- obj = batch_svc.stock_in_one(
- db,
- material_id,
- stock_in_no=body.stock_in_no,
- stock_in_date=body.stock_in_date,
- expire_date=body.expire_date,
- catalog_no=body.catalog_no,
- batch_no=body.batch_no,
- manufacturer=body.manufacturer,
- unit_price=unit_price,
- amount=amount,
- qty=Decimal(str(body.qty)),
- remark=body.remark,
- tenant_no=tenant_no,
- user_id=user_id,
- operate_time=operate_time,
- )
- return success_response(batch_svc._batch_row_to_dict(obj), message="入库成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/qc-material-batches/batch-stock-in")
- async def batch_stock_in_qc_material_batches(
- body: BatchStockInBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- """批量入库(与 /qc-materials/batch-stock-in 等价)。"""
- return await batch_stock_in_qc_material(body, db, user)
- @router.post("/qc-material-batches/batch-stock-out")
- async def batch_stock_out_qc_material_batches(
- body: BatchStockOutBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- """批量出库(与 /qc-materials/batch-stock-out 等价)。"""
- return await batch_stock_out_qc_material(body, db, user)
- @router.post("/qc-material-batches/batch-discard")
- async def batch_discard_qc_material_batches(
- body: BatchDiscardBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- """批量废弃(与 /qc-materials/batch-discard 等价)。"""
- return await batch_discard_qc_material(body, db, user)
- # ---------- 月初入库模板 ----------
- @router.get("/qc-material-month-templates")
- async def list_qc_month_templates(
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- rows = month_tpl_svc.list_templates(db, tenant_no=tenant_no)
- return success_response(rows)
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/qc-material-month-templates")
- async def create_qc_month_template(
- body: QcMonthTemplateBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- unit_price, amount = _resolve_stock_in_amounts(
- db, user, body.unit_price, body.amount
- )
- data = body.model_dump(by_alias=False)
- data["unit_price"] = unit_price if unit_price is not None else 0
- data["amount"] = amount if amount is not None else 0
- row = month_tpl_svc.create_template(
- db, data=data, tenant_no=tenant_no, user_id=user_id
- )
- return success_response(row, message="新增成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.put("/qc-material-month-templates/{row_id}")
- async def update_qc_month_template(
- row_id: str,
- body: QcMonthTemplateBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- unit_price, amount = _resolve_stock_in_amounts(
- db, user, body.unit_price, body.amount
- )
- data = body.model_dump(by_alias=False)
- data["unit_price"] = unit_price if unit_price is not None else 0
- data["amount"] = amount if amount is not None else 0
- row = month_tpl_svc.update_template(
- db, row_id, data=data, tenant_no=tenant_no, user_id=user_id
- )
- return success_response(row, message="更新成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.delete("/qc-material-month-templates/{row_id}")
- async def delete_qc_month_template(
- row_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- month_tpl_svc.soft_delete_template(db, row_id, tenant_no=tenant_no)
- return success_response(None, message="删除成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/qc-material-month-templates/preview")
- async def preview_qc_month_templates(
- body: QcMonthTemplatePreviewBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- stock_in_date = _parse_date(body.stock_in_date)
- rows = month_tpl_svc.preview_templates(
- db, tenant_no=tenant_no, stock_in_date=stock_in_date
- )
- return success_response(rows)
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/qc-material-month-templates/confirm")
- async def confirm_qc_month_templates(
- body: QcMonthTemplateConfirmBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- operate_time = datetime.now()
- if body.stock_in_time:
- rt = parse_datetime(body.stock_in_time)
- if not rt:
- return error_response(400, "入库时间格式无效")
- operate_time = rt
- stock_in_date = _parse_date(body.stock_in_date)
- items = []
- for x in body.items:
- unit_price, amount = _resolve_stock_in_amounts(
- db, user, x.unit_price, x.amount
- )
- items.append(
- {
- "material_id": x.material_id,
- "materialId": x.material_id,
- "material_code": x.material_code,
- "materialCode": x.material_code,
- "material_name": x.material_name,
- "materialName": x.material_name,
- "spec_model": x.spec_model,
- "specModel": x.spec_model,
- "unit": x.unit,
- "stock_in_no": x.stock_in_no,
- "stockInNo": x.stock_in_no,
- "expire_date": x.expire_date,
- "expireDate": x.expire_date,
- "catalog_no": x.catalog_no,
- "catalogNo": x.catalog_no,
- "batch_no": x.batch_no,
- "batchNo": x.batch_no,
- "manufacturer": x.manufacturer,
- "unit_price": unit_price,
- "unitPrice": unit_price,
- "amount": amount,
- "qty": x.qty,
- "remark": x.remark,
- }
- )
- count = month_tpl_svc.confirm_month_import(
- db,
- tenant_no=tenant_no,
- user_id=user_id,
- stock_in_date=stock_in_date,
- items=items,
- remark=body.remark,
- operate_time=operate_time,
- )
- return success_response({"count": count}, message=f"月初导入成功,共 {count} 项")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- # ---------- 物料变动记录 ----------
- @router.get("/qc-material-usages")
- async def list_qc_material_usages(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100),
- materialId: Optional[str] = None,
- changeType: Optional[str] = None,
- startTime: Optional[str] = None,
- endTime: Optional[str] = None,
- keyword: Optional[str] = None,
- stockInNo: Optional[str] = None,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterialUsage:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- items, total = mu_svc.list_usages(
- db, tenant_no, page=page, page_size=pageSize,
- material_id=materialId, change_type=changeType,
- start_time=startTime, end_time=endTime,
- keyword=keyword, stock_in_no=stockInNo,
- user_info=user,
- )
- _maybe_strip_qc_material_amount_fields(db, user, items)
- return success_response(_page_payload(items, total, page, pageSize))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/my-qc-material-usages")
- async def list_my_qc_material_usages(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100),
- materialId: Optional[str] = None,
- changeType: Optional[str] = None,
- startTime: Optional[str] = None,
- endTime: Optional[str] = None,
- keyword: Optional[str] = None,
- stockInNo: Optional[str] = None,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:myQcMaterialUsage:list")),
- ):
- """当前登录人的物料领用记录(手动出库领用人 / 任务使用填报人)。"""
- try:
- tenant_no = _get_tenant_no(db, user)
- items, total = mu_svc.list_my_usages(
- db,
- tenant_no,
- user_id=_get_current_user_id(user),
- nick_name=(user.get("real_name") or "").strip(),
- page=page,
- page_size=pageSize,
- material_id=materialId,
- change_type=changeType,
- start_time=startTime,
- end_time=endTime,
- keyword=keyword,
- stock_in_no=stockInNo,
- )
- if not user_has_menu_perm(db, user, MY_QC_MATERIAL_USAGE_VIEW_AMOUNT_PERM):
- strip_row_fields(items, "unitPrice", "amount")
- return success_response(_page_payload(items, total, page, pageSize))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-material-usages/{usage_id}/linked-task-usages")
- async def list_qc_material_linked_task_usages(
- usage_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(
- require_any_menu_code("biz:qcMaterialUsage:list", "biz:myQcMaterialUsage:list")
- ),
- ):
- """领用记录下关联的质检任务物料消耗明细。"""
- try:
- tenant_no = _get_tenant_no(db, user)
- items = mu_svc.list_linked_task_usages(
- db, usage_id, tenant_no, user_info=user
- )
- if not (
- user_has_menu_perm(db, user, QC_MATERIAL_VIEW_AMOUNT_PERM)
- or user_has_menu_perm(db, user, MY_QC_MATERIAL_USAGE_VIEW_AMOUNT_PERM)
- ):
- strip_row_fields(items, *QC_MATERIAL_AMOUNT_FIELD_NAMES)
- return success_response(items)
- except HTTPException as e:
- detail = e.detail
- msg = detail if isinstance(detail, str) else str(detail)
- return error_response(e.status_code, msg)
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-material-stock-check-requisitions")
- async def list_qc_material_stock_check_requisitions(
- page: int = Query(1, ge=1),
- pageSize: int = Query(50, ge=1, le=200),
- keyword: Optional[str] = None,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- """盘点登记:待处理领用记录(未标记已使用完)。"""
- try:
- tenant_no = _get_tenant_no(db, user)
- items, total = mu_svc.list_stock_check_requisitions(
- db,
- tenant_no,
- page=page,
- page_size=pageSize,
- keyword=keyword,
- user_info=user,
- )
- _maybe_strip_qc_material_amount_fields(db, user, items)
- return success_response(_page_payload(items, total, page, pageSize))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/qc-material-usages/{usage_id}/mark-fully-used")
- async def mark_qc_material_usage_fully_used(
- usage_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(
- require_any_menu_code("biz:qcMaterial:list", "biz:qcMaterialUsage:list")
- ),
- ):
- """标记领用记录已使用完(盘点登记 / 变动记录)。"""
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- mu_svc.mark_requisition_fully_used(
- db,
- usage_id,
- tenant_no=tenant_no,
- user_id=user_id,
- user_info=user,
- )
- return success_response(None, message="已标记为使用完")
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- # ---------- 库存盘点 ----------
- class StockCheckCreateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- title: str
- check_time: Optional[str] = Field(None, alias="checkTime")
- remark: Optional[str] = None
- class StockCheckUnusedLineItem(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- usage_id: Optional[str] = Field(None, alias="usageId")
- batch_id: Optional[str] = Field(None, alias="batchId")
- material_code: Optional[str] = Field(None, alias="materialCode")
- material_name: str = Field(..., alias="materialName")
- spec_model: Optional[str] = Field(None, alias="specModel")
- unit: Optional[str] = None
- stock_in_no: Optional[str] = Field(None, alias="stockInNo")
- stock_in_date: Optional[str] = Field(None, alias="stockInDate")
- expire_date: Optional[str] = Field(None, alias="expireDate")
- catalog_no: Optional[str] = Field(None, alias="catalogNo")
- batch_no: Optional[str] = Field(None, alias="batchNo")
- manufacturer: Optional[str] = None
- unit_price: Optional[float] = Field(None, alias="unitPrice")
- holder: Optional[str] = None
- unused_qty: float = Field(..., alias="unusedQty", gt=0)
- remark: Optional[str] = None
- class StockCheckUnusedLinesBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- items: List[StockCheckUnusedLineItem] = Field(default_factory=list)
- @router.get("/qc-material-stock-checks")
- async def list_qc_material_stock_checks(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100),
- keyword: Optional[str] = None,
- status: Optional[str] = None,
- startTime: Optional[str] = None,
- endTime: Optional[str] = None,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- items, total = stock_check_svc.list_checks(
- db,
- tenant_no,
- page=page,
- page_size=pageSize,
- keyword=keyword,
- status=status,
- start_time=startTime,
- end_time=endTime,
- user_info=user,
- )
- return success_response(_page_payload(items, total, page, pageSize))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/qc-material-stock-checks")
- async def create_qc_material_stock_check(
- body: StockCheckCreateBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- dept_id = user.get("deptId") or user.get("dept_id") or ""
- res = stock_check_svc.create_check(
- db,
- title=body.title,
- tenant_no=tenant_no,
- user_id=user_id,
- dept_id=dept_id,
- check_time=body.check_time,
- remark=body.remark,
- )
- return success_response(res, message="盘点单创建成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/qc-material-stock-checks/{check_id}")
- async def get_qc_material_stock_check(
- check_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- data = stock_check_svc.get_check_detail(db, check_id, tenant_no)
- _maybe_strip_qc_material_stock_check_detail(db, user, data)
- return success_response(data)
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.put("/qc-material-stock-checks/{check_id}/unused-lines")
- async def save_qc_material_stock_check_unused_lines(
- check_id: str,
- body: StockCheckUnusedLinesBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- dept_id = user.get("deptId") or user.get("dept_id") or ""
- count = stock_check_svc.save_unused_lines(
- db,
- check_id,
- items=[x.model_dump(by_alias=False) for x in body.items],
- tenant_no=tenant_no,
- user_id=user_id,
- dept_id=dept_id,
- )
- return success_response({"count": count}, message="登记保存成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/qc-material-stock-checks/{check_id}/complete")
- async def complete_qc_material_stock_check(
- check_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- stock_check_svc.complete_check(db, check_id, tenant_no=tenant_no, user_id=user_id)
- return success_response(None, message="盘点已完成")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/qc-material-stock-checks/{check_id}/cancel")
- async def cancel_qc_material_stock_check(
- check_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- stock_check_svc.cancel_check(db, check_id, tenant_no=tenant_no, user_id=user_id)
- return success_response(None, message="盘点已作废")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- # ---------- 质检物料金额查看授权 ----------
- class QcMaterialAmountAuthGrantBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- user_id: str = Field(..., alias="userId")
- class QcMaterialAmountAuthBatchBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- user_ids: List[str] = Field(..., alias="userIds", min_length=1)
- @router.get("/qc-material-amount-auth/users")
- async def list_qc_material_amount_auth_users(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100),
- keyword: Optional[str] = None,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code(QC_MATERIAL_AMOUNT_AUTH_LIST_PERM)),
- ):
- """已授权「质检物料金额查看」角色的用户列表。"""
- try:
- tenant_no = _get_tenant_no(db, user)
- items, total = amount_auth_svc.list_authorized_users(
- db,
- tenant_no=tenant_no,
- page=page,
- page_size=pageSize,
- keyword=keyword,
- )
- return success_response(_page_payload(items, total, page, pageSize))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/qc-material-amount-auth/users")
- async def grant_qc_material_amount_auth(
- body: QcMaterialAmountAuthGrantBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code(QC_MATERIAL_AMOUNT_AUTH_LIST_PERM)),
- ):
- """为用户绑定「质检物料金额查看」专用角色。"""
- try:
- tenant_no = _get_tenant_no(db, user)
- operator_id = _get_current_user_id(user)
- created = amount_auth_svc.grant_amount_view_role(
- db,
- tenant_no=tenant_no,
- user_id=body.user_id.strip(),
- operator_id=operator_id,
- )
- msg = "授权成功" if created else "该用户已授权"
- return success_response({"created": created}, message=msg)
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.delete("/qc-material-amount-auth/users/{user_id}")
- async def revoke_qc_material_amount_auth(
- user_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code(QC_MATERIAL_AMOUNT_AUTH_LIST_PERM)),
- ):
- """解除用户的「质检物料金额查看」专用角色。"""
- try:
- tenant_no = _get_tenant_no(db, user)
- operator_id = _get_current_user_id(user)
- removed = amount_auth_svc.revoke_amount_view_role(
- db,
- tenant_no=tenant_no,
- user_id=user_id.strip(),
- operator_id=operator_id,
- )
- if not removed:
- return error_response(400, "该用户未授权或已解除")
- return success_response(None, message="已解除授权")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/qc-material-amount-auth/users/batch-revoke")
- async def batch_revoke_qc_material_amount_auth(
- body: QcMaterialAmountAuthBatchBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code(QC_MATERIAL_AMOUNT_AUTH_LIST_PERM)),
- ):
- """批量解除「质检物料金额查看」授权。"""
- try:
- tenant_no = _get_tenant_no(db, user)
- operator_id = _get_current_user_id(user)
- count = 0
- for uid in body.user_ids:
- uid = (uid or "").strip()
- if not uid:
- continue
- if amount_auth_svc.revoke_amount_view_role(
- db,
- tenant_no=tenant_no,
- user_id=uid,
- operator_id=operator_id,
- ):
- count += 1
- return success_response({"count": count}, message=f"已解除 {count} 项授权")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
|