Files
zkxw/backend/app/api/sample_production_reception.py
T
zlwuandCursor 2d6b367a67 feat(backend): 生产接收接种量改为 VARCHAR(255) 自由文本
同步模型、API、CRUD 与 SQL 迁移/基线,支持数字与说明文案组合录入。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 15:00:02 +08:00

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))