"""输注计划管理 · 出库交接 CRUD""" from __future__ import annotations from datetime import datetime from typing import Any, Dict, List, Optional from fastapi import HTTPException from sqlalchemy.orm import Session from app.core.utils import generate_uuid from app.crud import outbound_release_material as orm_material_svc from app.crud import outbound_handover_inventory as handover_inv_svc from app.crud import pilot_infusion_schedule_mgmt as schedule_mgmt_svc from app.models.models import BizOutboundHandover, SysUser def _fmt_dt(t: Optional[datetime]) -> str: return t.strftime("%Y-%m-%d %H:%M:%S") if t else "" def _parse_handover_time(value: str) -> datetime: raw = (value or "").strip() if not raw: raise HTTPException(status_code=400, detail="交接时间不能为空") for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M"): try: return datetime.strptime(raw, fmt) except ValueError: continue raise HTTPException(status_code=400, detail="交接时间格式无效") def _handover_row_to_dict(row: Optional[BizOutboundHandover]) -> Dict[str, Any]: if not row: return { "handoverUserId": "", "handoverPerson": "", "handoverTime": "", "handoverLocation": "", "receiverPerson": "", "cellPurpose": "", "attachments": "", "remark": "", } return { "handoverUserId": row.handover_user_id or "", "handoverPerson": row.handover_person or "", "handoverTime": _fmt_dt(row.handover_time), "handoverLocation": row.handover_location or "", "receiverPerson": row.receiver_person or "", "cellPurpose": row.cell_purpose or "", "attachments": row.attachments or "", "remark": row.remark or "", } def _handover_view_dict(row: Optional[BizOutboundHandover]) -> Dict[str, Any]: if not row: return { "handoverPerson": "", "handoverTime": "", "handoverLocation": "", "receiverPerson": "", "cellPurpose": "", "attachments": "", "remark": "", } return { "handoverPerson": row.handover_person or "", "handoverTime": _fmt_dt(row.handover_time), "handoverLocation": row.handover_location or "", "receiverPerson": row.receiver_person or "", "cellPurpose": row.cell_purpose or "", "attachments": row.attachments or "", "remark": row.remark or "", } def get_handover_by_schedule( db: Session, schedule_id: str, tenant_no: str, ) -> Optional[BizOutboundHandover]: sid = (schedule_id or "").strip() if not sid: return None return ( db.query(BizOutboundHandover) .filter( BizOutboundHandover.infusion_schedule_id == sid, BizOutboundHandover.tenant_no == tenant_no, BizOutboundHandover.del_flag == 0, ) .first() ) def get_outbound_handover_view( db: Session, schedule_id: str, tenant_no: str, ) -> Dict[str, Any]: """供输注计划 detail 聚合的交接只读信息。""" row = get_handover_by_schedule(db, schedule_id, tenant_no) return _handover_view_dict(row) def get_outbound_handover( db: Session, schedule_id: str, tenant_no: str, ) -> Dict[str, Any]: """获取出库交接编辑数据(物料 + 交接信息 + 摘要)。""" detail = schedule_mgmt_svc.get_infusion_schedule_detail(db, schedule_id, tenant_no) if not detail: raise HTTPException(status_code=404, detail="输注计划不存在") outbound_material = orm_material_svc.get_outbound_material(db, schedule_id, tenant_no) handover_row = get_handover_by_schedule(db, schedule_id, tenant_no) return { "scheduleSummary": { "applicationNo": detail.get("applicationNo") or "", "patientName": detail.get("patientName") or "", "infusionTime": detail.get("infusionTime") or "", }, "outboundMaterial": { "remark": outbound_material.get("remark") or "", "cells": outbound_material.get("cells") or [], "qcMaterials": outbound_material.get("qcMaterials") or [], "paperMaterials": outbound_material.get("paperMaterials") or [], }, "handover": _handover_row_to_dict(handover_row), } def save_outbound_handover( db: Session, schedule_id: str, *, outbound_material: Dict[str, Any], handover: Dict[str, Any], tenant_no: str, user_id: str, create_dept_id: str = "", ) -> Dict[str, Any]: """保存出库物料台账 + 出库交接(单事务,调用方 commit)。""" detail = schedule_mgmt_svc.get_infusion_schedule_detail(db, schedule_id, tenant_no) if not detail: raise HTTPException(status_code=404, detail="输注计划不存在") handover_user_id = (handover.get("handoverUserId") or handover.get("handover_user_id") or "").strip() receiver_person = (handover.get("receiverPerson") or handover.get("receiver_person") or "").strip() handover_location = (handover.get("handoverLocation") or handover.get("handover_location") or "").strip() handover_remark = (handover.get("remark") or "").strip() cell_purpose = (handover.get("cellPurpose") or handover.get("cell_purpose") or "").strip() attachments = (handover.get("attachments") or "").strip() handover_time_raw = handover.get("handoverTime") or handover.get("handover_time") or "" if not handover_user_id: raise HTTPException(status_code=400, detail="交接人不能为空") if not receiver_person: raise HTTPException(status_code=400, detail="接收人不能为空") sys_user = ( db.query(SysUser) .filter( SysUser.id == handover_user_id, SysUser.del_flag == 0, ) .first() ) if not sys_user: raise HTTPException(status_code=400, detail="交接人用户不存在或已删除") handover_time = _parse_handover_time(str(handover_time_raw)) handover_person = (sys_user.nick_name or sys_user.user_no or "").strip() if not handover_person: handover_person = handover_user_id om = outbound_material or {} cells = om.get("cells") or [] qc_materials = om.get("qcMaterials") or om.get("qc_materials") or [] paper_materials = om.get("paperMaterials") or om.get("paper_materials") or [] material_remark = om.get("remark") # 读取保存前台账,用于回滚上次出库交接的库存/状态变更 previous_material = orm_material_svc.get_outbound_material(db, schedule_id, tenant_no) previous_cell_ids = [ (c.get("cellProductId") or c.get("cell_product_id") or "").strip() for c in (previous_material.get("cells") or []) ] had_previous_handover = get_handover_by_schedule(db, schedule_id, tenant_no) is not None handover_inv_svc.revert_handover_inventory( db, schedule_id, cell_product_ids=previous_cell_ids if had_previous_handover else [], tenant_no=tenant_no, user_id=user_id, ) orm_material_svc.save_outbound_material( db, schedule_id, paper_attachments=None, paper_materials=paper_materials, remark=material_remark, cells=cells, qc_materials=qc_materials, tenant_no=tenant_no, user_id=user_id, create_dept_id=create_dept_id, ) handover_inv_svc.apply_handover_inventory( db, schedule_id, cells=cells, qc_materials=qc_materials, tenant_no=tenant_no, user_id=user_id, create_dept_id=create_dept_id, receiver_person=receiver_person, ) now = datetime.now() row = get_handover_by_schedule(db, schedule_id, tenant_no) if not row: row = BizOutboundHandover( id=generate_uuid(), infusion_schedule_id=schedule_id, tenant_no=tenant_no, order_num=0, create_by=user_id, create_dept_id=create_dept_id or "", create_time=now, del_flag=0, ) db.add(row) db.flush() else: row.update_by = user_id row.update_time = now row.handover_user_id = handover_user_id row.handover_person = handover_person row.handover_time = handover_time row.handover_location = handover_location or None row.receiver_person = receiver_person row.cell_purpose = cell_purpose or None row.attachments = attachments or None row.remark = handover_remark or None db.add(row) db.flush() return get_outbound_handover(db, schedule_id, tenant_no)