同步模型、API、CRUD 与 SQL 迁移/基线,支持数字与说明文案组合录入。 Co-authored-by: Cursor <cursoragent@cursor.com>
244 lines
9.4 KiB
Python
244 lines
9.4 KiB
Python
"""生产部门样本接收 API"""
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, Depends, Query
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.database import get_db
|
|
from app.core.menu_guard import require_any_menu_code, require_menu_code
|
|
from app.core.response import error_response, success_response
|
|
from app.core.utils import parse_datetime
|
|
from app.crud import sample_production_reception as prod_rec_svc
|
|
from app.crud import sample_reception as sr_svc
|
|
from app.models.models import SysUser
|
|
|
|
router = APIRouter(prefix="/web/admin", tags=["生产管理"])
|
|
|
|
PRODUCTION_RECEPTION_READ_MENU_CODES = (
|
|
"production:work-order:list",
|
|
"production:my-task:list",
|
|
"biz:sampleReception:list",
|
|
"production:sample-reception-completion:list",
|
|
)
|
|
|
|
PRODUCTION_RECEPTION_WRITE_MENU_CODES = (
|
|
"production:work-order:list",
|
|
"production:my-task:list",
|
|
)
|
|
|
|
|
|
def _get_tenant_no(db: Session, user: dict) -> str:
|
|
user_id = user.get("user_id")
|
|
if not user_id:
|
|
raise PermissionError("用户身份信息缺失,无法确定租户")
|
|
sys_user = db.query(SysUser).filter(SysUser.id == user_id).first()
|
|
if not sys_user or not sys_user.tenant_no:
|
|
raise PermissionError("用户租户信息缺失")
|
|
return sys_user.tenant_no
|
|
|
|
|
|
def _get_current_user_id(user: dict) -> str:
|
|
user_id = user.get("user_id")
|
|
if not user_id:
|
|
raise PermissionError("用户身份信息缺失")
|
|
return str(user_id)
|
|
|
|
|
|
class ProductionReceptionBody(BaseModel):
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
reception_id: Optional[str] = Field(None, alias="receptionId")
|
|
sample_code: Optional[str] = Field(None, alias="sampleCode")
|
|
source_type: Optional[str] = Field("cell_storage", alias="sourceType")
|
|
sample_type_name: Optional[str] = Field(None, alias="sampleTypeName")
|
|
handler_user_id: Optional[str] = Field(None, alias="handlerUserId")
|
|
handler_name: Optional[str] = Field(None, alias="handlerName")
|
|
process_time: str = Field(..., alias="processTime")
|
|
original_blood_weight_g: Optional[float] = Field(None, alias="originalBloodWeightG")
|
|
hes_volume_ml: Optional[float] = Field(None, alias="hesVolumeMl")
|
|
blood_type: Optional[str] = Field(None, alias="bloodType")
|
|
infectious_four_test_result: Optional[str] = Field(None, alias="infectiousFourTestResult")
|
|
enriched_blood_weight_g: Optional[float] = Field(None, alias="enrichedBloodWeightG")
|
|
cryopreservation_volume_ml: Optional[float] = Field(None, alias="cryopreservationVolumeMl")
|
|
inoculation_amount: Optional[str] = Field(None, alias="inoculationAmount")
|
|
cryopreservation_time: Optional[str] = Field(None, alias="cryopreservationTime")
|
|
storage_total_tubes: Optional[int] = Field(None, alias="storageTotalTubes")
|
|
available_cell_count_1e7: Optional[float] = Field(None, alias="availableCellCount1e7")
|
|
original_blood_volume_ml: Optional[float] = Field(None, alias="originalBloodVolumeMl")
|
|
nucleated_cell_count_1e8: Optional[float] = Field(None, alias="nucleatedCellCount1e8")
|
|
remark: Optional[str] = None
|
|
|
|
|
|
def _body_to_payload(body: ProductionReceptionBody) -> dict:
|
|
cryo_time = parse_datetime(body.cryopreservation_time) if body.cryopreservation_time else None
|
|
process_time = parse_datetime(body.process_time)
|
|
if not process_time:
|
|
raise ValueError("处理时间格式无效")
|
|
return {
|
|
"handlerUserId": body.handler_user_id,
|
|
"handlerName": body.handler_name,
|
|
"processTime": process_time,
|
|
"sampleTypeName": body.sample_type_name,
|
|
"originalBloodWeightG": body.original_blood_weight_g,
|
|
"hesVolumeMl": body.hes_volume_ml,
|
|
"bloodType": body.blood_type,
|
|
"infectiousFourTestResult": body.infectious_four_test_result,
|
|
"enrichedBloodWeightG": body.enriched_blood_weight_g,
|
|
"cryopreservationVolumeMl": body.cryopreservation_volume_ml,
|
|
"inoculationAmount": body.inoculation_amount,
|
|
"cryopreservationTime": cryo_time,
|
|
"storageTotalTubes": body.storage_total_tubes,
|
|
"availableCellCount1e7": body.available_cell_count_1e7,
|
|
"originalBloodVolumeMl": body.original_blood_volume_ml,
|
|
"nucleatedCellCount1e8": body.nucleated_cell_count_1e8,
|
|
"remark": body.remark,
|
|
}
|
|
|
|
|
|
def _resolve_reception_id(
|
|
db: Session,
|
|
*,
|
|
tenant_no: str,
|
|
reception_id: Optional[str],
|
|
sample_code: Optional[str],
|
|
source_type: Optional[str],
|
|
) -> str:
|
|
rid = (reception_id or "").strip()
|
|
if rid:
|
|
return rid
|
|
code = (sample_code or "").strip()
|
|
if not code:
|
|
raise ValueError("请提供 receptionId 或 sampleCode")
|
|
result = sr_svc.get_reception_by_sample_code(
|
|
db, sample_code=code, source_type=source_type, tenant_no=tenant_no
|
|
)
|
|
found = (result.get("receptionId") or "").strip()
|
|
if not found:
|
|
raise ValueError("请先完成质检样本接收登记")
|
|
return found
|
|
|
|
|
|
@router.get("/production-sample-receptions/by-sample-code")
|
|
async def get_production_reception_by_sample_code(
|
|
sampleCode: str = Query(..., alias="sampleCode"),
|
|
sourceType: Optional[str] = Query(None, alias="sourceType"),
|
|
db: Session = Depends(get_db),
|
|
user: dict = Depends(require_any_menu_code(*PRODUCTION_RECEPTION_READ_MENU_CODES)),
|
|
):
|
|
try:
|
|
tenant_no = _get_tenant_no(db, user)
|
|
base = sr_svc.get_reception_by_sample_code(
|
|
db, sample_code=sampleCode, source_type=sourceType, tenant_no=tenant_no
|
|
)
|
|
reception_id = base.get("receptionId")
|
|
qc_reception = None
|
|
production_reception = None
|
|
if reception_id:
|
|
qc_reception = sr_svc.get_reception_by_id(db, reception_id, tenant_no)
|
|
production_reception = prod_rec_svc.get_by_reception_id(db, reception_id, tenant_no)
|
|
return success_response(
|
|
{
|
|
"receptionId": reception_id,
|
|
"sampleInfo": base.get("sampleInfo"),
|
|
"qcReception": qc_reception,
|
|
"productionReception": production_reception,
|
|
}
|
|
)
|
|
except PermissionError as e:
|
|
return error_response(403, str(e))
|
|
except Exception as e:
|
|
return error_response(400, str(e))
|
|
|
|
|
|
@router.get("/production-sample-receptions/{row_id}")
|
|
async def get_production_reception_detail(
|
|
row_id: str,
|
|
db: Session = Depends(get_db),
|
|
user: dict = Depends(require_any_menu_code(*PRODUCTION_RECEPTION_READ_MENU_CODES)),
|
|
):
|
|
try:
|
|
tenant_no = _get_tenant_no(db, user)
|
|
item = prod_rec_svc.get_by_id(db, row_id, tenant_no)
|
|
if not item:
|
|
return error_response(404, "生产接收记录不存在")
|
|
return success_response(item)
|
|
except PermissionError as e:
|
|
return error_response(403, str(e))
|
|
except Exception as e:
|
|
return error_response(400, str(e))
|
|
|
|
|
|
@router.post("/production-sample-receptions")
|
|
async def post_production_reception(
|
|
body: ProductionReceptionBody,
|
|
db: Session = Depends(get_db),
|
|
user: dict = Depends(require_any_menu_code(*PRODUCTION_RECEPTION_WRITE_MENU_CODES)),
|
|
):
|
|
try:
|
|
tenant_no = _get_tenant_no(db, user)
|
|
user_id = _get_current_user_id(user)
|
|
sys_user = db.query(SysUser).filter(SysUser.id == user_id).first()
|
|
dept_id = (sys_user.dept_id or "") if sys_user else ""
|
|
reception_id = _resolve_reception_id(
|
|
db,
|
|
tenant_no=tenant_no,
|
|
reception_id=body.reception_id,
|
|
sample_code=body.sample_code,
|
|
source_type=body.source_type,
|
|
)
|
|
payload = _body_to_payload(body)
|
|
type_name = (body.sample_type_name or "").strip()
|
|
if not type_name:
|
|
sample_info = sr_svc.get_reception_by_sample_code(
|
|
db,
|
|
sample_code=(body.sample_code or "").strip(),
|
|
source_type=body.source_type,
|
|
tenant_no=tenant_no,
|
|
).get("sampleInfo")
|
|
if isinstance(sample_info, dict):
|
|
type_name = str(sample_info.get("sampleTypeName") or "")
|
|
obj = prod_rec_svc.create_production_reception(
|
|
db,
|
|
reception_id=reception_id,
|
|
tenant_no=tenant_no,
|
|
user_id=user_id,
|
|
create_dept_id=dept_id,
|
|
data=payload,
|
|
sample_type_name=type_name,
|
|
)
|
|
return success_response(prod_rec_svc.get_by_id(db, obj.id, tenant_no))
|
|
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("/production-sample-receptions/{row_id}")
|
|
async def put_production_reception(
|
|
row_id: str,
|
|
body: ProductionReceptionBody,
|
|
db: Session = Depends(get_db),
|
|
user: dict = Depends(require_any_menu_code(*PRODUCTION_RECEPTION_WRITE_MENU_CODES)),
|
|
):
|
|
try:
|
|
tenant_no = _get_tenant_no(db, user)
|
|
user_id = _get_current_user_id(user)
|
|
payload = _body_to_payload(body)
|
|
obj = prod_rec_svc.update_production_reception(
|
|
db,
|
|
row_id=row_id,
|
|
tenant_no=tenant_no,
|
|
user_id=user_id,
|
|
data=payload,
|
|
)
|
|
return success_response(prod_rec_svc.get_by_id(db, obj.id, tenant_no))
|
|
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))
|