Files
zkxw/backend/app/api/pilot_application.py
T
zlwuandCursor 5dad1bb0e3 fix: 协议子资源单事务保存,并补齐样本编码/OA/质检等近期能力
锁定缴费校验失败时整单回滚,避免样本半成功落库;前端禁止删除锁定缴费样本,改码走更正编码。同步落地样本更正、采集微信通知、OA提及未读、细胞产品套餐与相关迁移文档。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-26 11:02:25 +08:00

903 lines
33 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_admin_or_super_admin
from app.core.response import error_response, success_response
from app.crud import pilot_application
from app.models.models import SysUser
router = APIRouter(prefix="/web/admin", tags=["中试服务申请单管理"])
def _get_tenant_no(db: Session, user: dict) -> str:
user_id = user.get("user_id")
if not user_id:
raise PermissionError("用户身份信息缺失,无法确定租户")
sys_user = db.query(SysUser).filter(SysUser.id == user_id).first()
if not sys_user or not sys_user.tenant_no:
raise PermissionError("用户租户信息缺失")
return sys_user.tenant_no
# ---------------------------------------------------------------------------
# Pydantic Schemas
# ---------------------------------------------------------------------------
class IdBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
id: str
class PlanIdBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
plan_id: str = Field(..., alias="infusionPlanId")
class ApplicationCreateBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
channel_service_order_id: Optional[str] = Field(None, alias="channelServiceOrderId")
remark: Optional[str] = None
biz_type: Optional[str] = Field(None, alias="bizType")
project_title: Optional[str] = Field(None, alias="projectTitle")
customer_name: Optional[str] = Field(None, alias="customerName")
customer_contact: Optional[str] = Field(None, alias="customerContact")
customer_phone: Optional[str] = Field(None, alias="customerPhone")
class ApplicationUpdateBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
id: str
remark: Optional[str] = None
project_title: Optional[str] = Field(None, alias="projectTitle")
customer_name: Optional[str] = Field(None, alias="customerName")
customer_contact: Optional[str] = Field(None, alias="customerContact")
customer_phone: Optional[str] = Field(None, alias="customerPhone")
class PatientSaveBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
application_id: str = Field(..., alias="pilotApplicationId")
customer_id: Optional[str] = Field(None, alias="customerId")
patient_name: str = Field(..., alias="patientName")
sex: Optional[str] = None
age: Optional[int] = None
id_card_no: Optional[str] = Field(None, alias="idCardNo")
phone_no: Optional[str] = Field(None, alias="phoneNo")
emergency_contact: Optional[str] = Field(None, alias="emergencyContact")
emergency_phone: Optional[str] = Field(None, alias="emergencyPhone")
clinical_diagnosis: Optional[str] = Field(None, alias="clinicalDiagnosis")
diagnosis_attachments: Optional[str] = Field(None, alias="diagnosisAttachments")
need_checkup: int = Field(1, alias="needCheckup")
class InfusionPlanCreateBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
pilot_application_id: str = Field(..., alias="pilotApplicationId")
cell_type: str = Field(..., alias="cellType")
spec_text: Optional[str] = Field(None, alias="specText")
total_course_count: int = Field(..., alias="totalCourseCount")
course_interval: Optional[str] = Field(None, alias="courseInterval")
infusion_route: Optional[str] = Field(None, alias="infusionRoute")
infusion_start_date: Optional[str] = Field(None, alias="infusionStartDate")
class InfusionPlanUpdateBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
id: str
cell_type: Optional[str] = Field(None, alias="cellType")
spec_text: Optional[str] = Field(None, alias="specText")
total_course_count: Optional[int] = Field(None, alias="totalCourseCount")
course_interval: Optional[str] = Field(None, alias="courseInterval")
infusion_route: Optional[str] = Field(None, alias="infusionRoute")
infusion_start_date: Optional[str] = Field(None, alias="infusionStartDate")
class InfusionScheduleUpdateBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
id: str
infusion_time: Optional[str] = Field(None, alias="infusionTime")
remark: Optional[str] = None
attachments: Optional[str] = None
class ScheduleCreateBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
plan_id: str = Field(..., alias="infusionPlanId")
infusion_time: Optional[str] = Field(None, alias="infusionTime")
remark: Optional[str] = None
amount: Optional[float] = None
# --- 复合保存 Schema(参考采集协议单一次性保存模式)---
class PatientNestedBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
customer_id: Optional[str] = Field(None, alias="customerId")
patient_name: Optional[str] = Field(None, alias="patientName")
sex: Optional[str] = None
age: Optional[int] = None
id_card_no: Optional[str] = Field(None, alias="idCardNo")
phone_no: Optional[str] = Field(None, alias="phoneNo")
emergency_contact: Optional[str] = Field(None, alias="emergencyContact")
emergency_phone: Optional[str] = Field(None, alias="emergencyPhone")
clinical_diagnosis: Optional[str] = Field(None, alias="clinicalDiagnosis")
diagnosis_attachments: Optional[str] = Field(None, alias="diagnosisAttachments")
need_checkup: int = Field(1, alias="needCheckup")
class ScheduleProductNestedBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
id: Optional[str] = None
price_id: Optional[str] = Field(None, alias="priceId")
project_code: Optional[str] = Field(None, alias="projectCode")
project_name: Optional[str] = Field(None, alias="projectName")
cell_type_name: Optional[str] = Field(None, alias="cellTypeName")
spec_text: Optional[str] = Field(None, alias="specText")
package_id: Optional[str] = Field(None, alias="packageId")
package_text: Optional[str] = Field(None, alias="packageText")
agreement_price: Optional[float] = Field(None, alias="agreementPrice")
actual_price: Optional[float] = Field(None, alias="actualPrice")
quantity: Optional[int] = 1
status: Optional[str] = None
remark: Optional[str] = None
class InfusionScheduleNestedBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
id: Optional[str] = None
sort_no: Optional[int] = Field(None, alias="sortNo")
infusion_time: Optional[str] = Field(None, alias="infusionTime")
infusion_location: Optional[str] = Field(None, alias="infusionLocation")
infusion_method: Optional[str] = Field(None, alias="infusionMethod")
amount: Optional[float] = None
package_id: Optional[str] = Field(None, alias="packageId")
package_text: Optional[str] = Field(None, alias="packageText")
cell_products: Optional[list[ScheduleProductNestedBody]] = Field(None, alias="cellProducts")
class InfusionPlanNestedBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
id: Optional[str] = None
cell_type: Optional[str] = Field(None, alias="cellType")
spec_text: Optional[str] = Field(None, alias="specText")
total_course_count: Optional[int] = Field(None, alias="totalCourseCount")
course_interval: Optional[str] = Field(None, alias="courseInterval")
infusion_route: Optional[str] = Field(None, alias="infusionRoute")
infusion_start_date: Optional[str] = Field(None, alias="infusionStartDate")
channel_agreement_id: Optional[str] = Field(None, alias="channelAgreementId")
class ApplicationSaveBody(BaseModel):
"""一揽子保存:申请单 + 患者信息 + 输注方案 + 输注计划(含细胞产品)"""
model_config = ConfigDict(populate_by_name=True)
id: Optional[str] = None
application_name: Optional[str] = Field(None, alias="applicationName")
channel_service_order_id: Optional[str] = Field(None, alias="channelServiceOrderId")
channel_id: Optional[str] = Field(None, alias="channelId")
biz_type: Optional[str] = Field(None, alias="bizType")
project_title: Optional[str] = Field(None, alias="projectTitle")
customer_name: Optional[str] = Field(None, alias="customerName")
customer_contact: Optional[str] = Field(None, alias="customerContact")
customer_phone: Optional[str] = Field(None, alias="customerPhone")
remark: Optional[str] = None
consent_form_urls: Optional[str] = Field(None, alias="consentFormUrls")
consent_attachments: Optional[list] = Field(None, alias="consentAttachments")
attachment_urls: Optional[str] = Field(None, alias="attachmentUrls")
other_attachments: Optional[list] = Field(None, alias="otherAttachments")
patient: Optional[PatientNestedBody] = None
infusion_plan: Optional[InfusionPlanNestedBody] = Field(None, alias="infusionPlan")
infusion_schedules: Optional[list[InfusionScheduleNestedBody]] = Field(
None, alias="infusionSchedules"
)
infusion_plans: Optional[list[InfusionPlanNestedBody]] = Field(None, alias="infusionPlans")
# ---------------------------------------------------------------------------
# 1. 申请单 CRUD + 状态变更
# ---------------------------------------------------------------------------
@router.get("/pilot-application/list")
async def api_list_applications(
page: int = Query(1, ge=1),
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
keyword: Optional[str] = Query(None, alias="keyword"),
channelId: Optional[str] = Query(None, alias="channelId"),
applicationStatus: Optional[str] = Query(None, alias="applicationStatus"),
channelServiceOrderId: Optional[str] = Query(None, alias="channelServiceOrderId"),
bizType: Optional[str] = Query(None, alias="bizType"),
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
tenant_no = _get_tenant_no(db, user)
result = pilot_application.list_applications(
db,
tenant_no=tenant_no,
page=page,
page_size=pageSize,
channel_id=channelId,
application_status=applicationStatus,
channel_service_order_id=channelServiceOrderId,
keyword=keyword,
biz_type=bizType,
)
return success_response(result)
@router.get("/pilot-application/detail")
async def api_get_application(
id: str = Query(...),
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
tenant_no = _get_tenant_no(db, user)
result = pilot_application.get_application(db, app_id=id, tenant_no=tenant_no)
if result is None:
return error_response(404, "申请单不存在")
return success_response(result)
@router.post("/pilot-application/create")
async def api_create_application(
body: ApplicationCreateBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
data = {
"channelServiceOrderId": body.channel_service_order_id or "",
"remark": body.remark,
"bizType": body.biz_type or "pilot_service",
"projectTitle": body.project_title,
"customerName": body.customer_name,
"customerContact": body.customer_contact,
"customerPhone": body.customer_phone,
}
result = pilot_application.create_application(
db,
data=data,
tenant_no=tenant_no,
user_id=user.get("user_id"),
dept_id=user.get("region_id", ""),
)
return success_response(result, message="创建成功")
except ValueError as e:
return error_response(400, str(e))
@router.put("/pilot-application/update")
async def api_update_application(
body: ApplicationUpdateBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
data = {}
if body.remark is not None:
data["remark"] = body.remark
if body.project_title is not None:
data["projectTitle"] = body.project_title
if body.customer_name is not None:
data["customerName"] = body.customer_name
if body.customer_contact is not None:
data["customerContact"] = body.customer_contact
if body.customer_phone is not None:
data["customerPhone"] = body.customer_phone
result = pilot_application.update_application(
db, app_id=body.id, data=data, tenant_no=tenant_no
)
return success_response(result, message="更新成功")
except ValueError as e:
return error_response(400, str(e))
@router.post("/pilot-application/save")
async def api_save_application(
body: ApplicationSaveBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
user_id = user.get("user_id")
dept_id = user.get("region_id", "")
is_edit = bool(body.id)
biz_type = body.biz_type or "pilot_service"
app_data: dict = {
"channelServiceOrderId": body.channel_service_order_id or "",
"channelId": body.channel_id or "",
"bizType": biz_type,
"remark": body.remark,
}
if body.application_name is not None:
app_data["applicationName"] = body.application_name
if body.project_title is not None:
app_data["projectTitle"] = body.project_title
if body.customer_name is not None:
app_data["customerName"] = body.customer_name
if body.customer_contact is not None:
app_data["customerContact"] = body.customer_contact
if body.customer_phone is not None:
app_data["customerPhone"] = body.customer_phone
if body.consent_attachments is not None:
app_data["consentAttachments"] = body.consent_attachments
elif body.consent_form_urls is not None:
app_data["consentFormUrls"] = body.consent_form_urls
if body.other_attachments is not None:
app_data["otherAttachments"] = body.other_attachments
elif body.attachment_urls is not None:
app_data["attachmentUrls"] = body.attachment_urls
if (
not is_edit
and biz_type not in ("clinical_application", "basic_experiment")
and not app_data["channelServiceOrderId"]
):
return error_response(400, "请选择渠道服务订单")
if not is_edit and biz_type == "clinical_application" and not app_data["channelId"]:
return error_response(400, "请选择渠道")
if is_edit:
app_result = pilot_application.update_application(
db,
app_id=body.id,
data=app_data,
tenant_no=tenant_no,
)
app_id = body.id
else:
app_result = pilot_application.create_application(
db,
data=app_data,
tenant_no=tenant_no,
user_id=user_id,
dept_id=dept_id,
)
app_id = app_result.get("id")
if not app_id:
return error_response(500, "创建申请单失败,未获取到ID")
if body.patient:
pilot_application.save_patient(
db,
app_id=app_id,
data=body.patient.model_dump(by_alias=True),
tenant_no=tenant_no,
user_id=user_id,
dept_id=dept_id,
)
if body.infusion_plan is not None or body.infusion_schedules:
plan_dict = (
body.infusion_plan.model_dump(by_alias=True) if body.infusion_plan else {}
)
schedules = [
s.model_dump(by_alias=True) for s in (body.infusion_schedules or [])
]
if is_edit:
pilot_application.upsert_application_infusion(
db,
app_id=app_id,
plan_data=plan_dict,
schedules_data=schedules,
tenant_no=tenant_no,
user_id=user_id,
dept_id=dept_id,
)
else:
pilot_application.save_application_infusion(
db,
app_id=app_id,
plan_data=plan_dict,
schedules_data=schedules,
tenant_no=tenant_no,
user_id=user_id,
dept_id=dept_id,
replace_existing=False,
)
elif body.infusion_plans:
for plan_data in body.infusion_plans:
plan_result = pilot_application.create_infusion_plan(
db,
app_id=app_id,
data=plan_data.model_dump(by_alias=True),
tenant_no=tenant_no,
user_id=user_id,
dept_id=dept_id,
)
plan_id = plan_result.get("id")
if plan_id and (plan_data.total_course_count or 0) > 0:
pilot_application.auto_generate_schedules(
db,
plan_id=plan_id,
tenant_no=tenant_no,
user_id=user_id,
dept_id=dept_id,
)
app_result = pilot_application.get_application(db, app_id=app_id, tenant_no=tenant_no)
return success_response(app_result, message="保存成功")
except ValueError as e:
return error_response(400, str(e))
except Exception as e:
return error_response(500, str(e))
@router.delete("/pilot-application/delete")
async def api_delete_application(
body: IdBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
pilot_application.soft_delete_application(db, app_id=body.id, tenant_no=tenant_no)
return success_response(None, message="删除成功")
except ValueError as e:
return error_response(400, str(e))
@router.post("/pilot-application/start")
async def api_start_application(
body: IdBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
result = pilot_application.start_application(
db, app_id=body.id, tenant_no=tenant_no, user_id=user.get("user_id")
)
return success_response(result, message="操作成功")
except ValueError as e:
return error_response(400, str(e))
@router.post("/pilot-application/complete")
async def api_complete_application(
body: IdBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
result = pilot_application.complete_application(
db, app_id=body.id, tenant_no=tenant_no, user_id=user.get("user_id")
)
return success_response(result, message="操作成功")
except ValueError as e:
return error_response(400, str(e))
class SaleScheduleProductLineBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
price_id: Optional[str] = Field(None, alias="priceId")
project_code: Optional[str] = Field(None, alias="projectCode")
project_name: Optional[str] = Field(None, alias="projectName")
cell_type_name: Optional[str] = Field(None, alias="cellTypeName")
spec_text: Optional[str] = Field(None, alias="specText")
package_id: Optional[str] = Field(None, alias="packageId")
package_text: Optional[str] = Field(None, alias="packageText")
agreement_price: Optional[float] = Field(None, alias="agreementPrice")
actual_price: Optional[float] = Field(None, alias="actualPrice")
quantity: Optional[int] = 1
pickup_time: Optional[str] = Field(None, alias="pickupTime")
amount: Optional[float] = None
class SaleSchedulesCreateBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
product_lines: list[SaleScheduleProductLineBody] = Field(..., alias="productLines")
@router.post("/pilot-application/{application_id}/sale-schedules")
async def api_create_sale_schedules(
application_id: str,
body: SaleSchedulesCreateBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
product_lines = [line.model_dump(by_alias=True) for line in body.product_lines]
results = pilot_application.create_basic_experiment_sale_schedules(
db,
application_id=application_id,
product_lines=product_lines,
tenant_no=tenant_no,
user_id=user.get("user_id"),
dept_id=user.get("region_id", ""),
)
return success_response(results, message="售卖计划创建成功")
except ValueError as e:
return error_response(400, str(e))
@router.post("/pilot-application/void")
async def api_void_application(
body: IdBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
result = pilot_application.void_application(
db, app_id=body.id, tenant_no=tenant_no, user_id=user.get("user_id")
)
return success_response(result, message="操作成功")
except ValueError as e:
return error_response(400, str(e))
# ---------------------------------------------------------------------------
# 2. 患者信息
# ---------------------------------------------------------------------------
@router.get("/pilot-patient/detail")
async def api_get_patient(
application_id: str = Query(..., alias="pilotApplicationId"),
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
tenant_no = _get_tenant_no(db, user)
result = pilot_application.get_patient_by_application(
db, app_id=application_id, tenant_no=tenant_no
)
if result is None:
return success_response(None)
return success_response(result)
@router.post("/pilot-patient/save")
async def api_save_patient(
body: PatientSaveBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
data = {
"customerId": body.customer_id,
"patientName": body.patient_name,
"sex": body.sex,
"age": body.age,
"idCardNo": body.id_card_no,
"phoneNo": body.phone_no,
"emergencyContact": body.emergency_contact,
"emergencyPhone": body.emergency_phone,
"clinicalDiagnosis": body.clinical_diagnosis,
"diagnosisAttachments": body.diagnosis_attachments,
"needCheckup": body.need_checkup,
}
result = pilot_application.save_patient(
db,
app_id=body.application_id,
data=data,
tenant_no=tenant_no,
user_id=user.get("user_id"),
dept_id=user.get("region_id", ""),
)
return success_response(result, message="保存成功")
except ValueError as e:
return error_response(400, str(e))
# ---------------------------------------------------------------------------
# 3. 输注方案
# ---------------------------------------------------------------------------
@router.get("/pilot-infusion-plan/list")
async def api_list_plans(
application_id: str = Query(..., alias="pilotApplicationId"),
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
tenant_no = _get_tenant_no(db, user)
result = pilot_application.list_infusion_plans(
db, app_id=application_id, tenant_no=tenant_no
)
return success_response(result)
@router.post("/pilot-infusion-plan/create")
async def api_create_plan(
body: InfusionPlanCreateBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
data = {
"cellType": body.cell_type,
"specText": body.spec_text,
"totalCourseCount": body.total_course_count,
"courseInterval": body.course_interval,
"infusionRoute": body.infusion_route,
"infusionStartDate": body.infusion_start_date,
}
result = pilot_application.create_infusion_plan(
db,
app_id=body.pilot_application_id,
data=data,
tenant_no=tenant_no,
user_id=user.get("user_id"),
dept_id=user.get("region_id", ""),
)
return success_response(result, message="创建成功")
except ValueError as e:
return error_response(400, str(e))
@router.put("/pilot-infusion-plan/update")
async def api_update_plan(
body: InfusionPlanUpdateBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
data = {}
if body.cell_type is not None:
data["cellType"] = body.cell_type
if body.spec_text is not None:
data["specText"] = body.spec_text
if body.total_course_count is not None:
data["totalCourseCount"] = body.total_course_count
if body.course_interval is not None:
data["courseInterval"] = body.course_interval
if body.infusion_route is not None:
data["infusionRoute"] = body.infusion_route
if body.infusion_start_date is not None:
data["infusionStartDate"] = body.infusion_start_date
result = pilot_application.update_infusion_plan(
db, plan_id=body.id, data=data, tenant_no=tenant_no
)
return success_response(result, message="更新成功")
except ValueError as e:
return error_response(400, str(e))
@router.delete("/pilot-infusion-plan/delete")
async def api_delete_plan(
body: IdBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
pilot_application.soft_delete_infusion_plan(
db, plan_id=body.id, tenant_no=tenant_no
)
return success_response(None, message="删除成功")
except ValueError as e:
return error_response(400, str(e))
# ---------------------------------------------------------------------------
# 4. 输注计划
# ---------------------------------------------------------------------------
@router.get("/pilot-infusion-schedule/list")
async def api_list_schedules(
plan_id: str = Query(..., alias="infusionPlanId"),
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
tenant_no = _get_tenant_no(db, user)
result = pilot_application.list_schedules_by_plan(
db, plan_id=plan_id, tenant_no=tenant_no
)
return success_response(result)
@router.post("/pilot-infusion-schedule/auto-generate")
async def api_auto_generate(
body: PlanIdBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
result = pilot_application.auto_generate_schedules(
db,
plan_id=body.plan_id,
tenant_no=tenant_no,
user_id=user.get("user_id"),
dept_id=user.get("region_id", ""),
)
return success_response(result, message="生成成功")
except ValueError as e:
return error_response(400, str(e))
@router.post("/pilot-infusion-schedule/create")
async def api_create_schedule(
body: ScheduleCreateBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
data = {
"infusionTime": body.infusion_time,
"remark": body.remark,
"amount": body.amount,
}
result = pilot_application.create_schedule(
db,
plan_id=body.plan_id,
data=data,
tenant_no=tenant_no,
user_id=user.get("user_id"),
dept_id=user.get("region_id", ""),
)
return success_response(result, message="创建成功")
except ValueError as e:
return error_response(400, str(e))
@router.put("/pilot-infusion-schedule/update")
async def api_update_schedule(
body: InfusionScheduleUpdateBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
data = {}
if body.infusion_time is not None:
data["infusionTime"] = body.infusion_time
if body.remark is not None:
data["remark"] = body.remark
if body.attachments is not None:
data["attachments"] = body.attachments
result = pilot_application.update_schedule(
db, schedule_id=body.id, data=data, tenant_no=tenant_no
)
return success_response(result, message="更新成功")
except ValueError as e:
return error_response(400, str(e))
@router.delete("/pilot-infusion-schedule/delete")
async def api_delete_schedule(
body: IdBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
pilot_application.soft_delete_schedule(
db, schedule_id=body.id, tenant_no=tenant_no
)
return success_response(None, message="删除成功")
except ValueError as e:
return error_response(400, str(e))
@router.post("/pilot-infusion-schedule/mark-complete")
async def api_mark_complete(
body: IdBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
result = pilot_application.mark_schedule_complete(
db, schedule_id=body.id, tenant_no=tenant_no, user_id=user.get("user_id")
)
return success_response(result, message="操作成功")
except ValueError as e:
return error_response(400, str(e))
# ---------------------------------------------------------------------------
# 出库生产工单 / 出库质检工单 联动
# ---------------------------------------------------------------------------
class ScheduleIdBody(BaseModel):
scheduleId: str
@router.post("/pilot-infusion-schedule/create-production-work-order")
async def api_create_outbound_production(
body: ScheduleIdBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
from app.crud import production_work_order as pwo_svc
from app.crud.qc_work_order import resolve_outbound_biz_type_for_schedule
outbound_biz_type = resolve_outbound_biz_type_for_schedule(
db, body.scheduleId, tenant_no
)
wo = pwo_svc.create_outbound_production_work_order(
db,
schedule_id=body.scheduleId,
tenant_no=tenant_no,
user_id=user.get("user_id"),
create_dept_id=user.get("dept_id", ""),
biz_type=outbound_biz_type,
)
db.commit()
db.refresh(wo)
return success_response(pwo_svc._wo_to_dict(wo), message="创建出库生产工单成功")
except ValueError as e:
return error_response(400, str(e))
except Exception as e:
db.rollback()
return error_response(400, str(e))
class OutboundQcBody(BaseModel):
scheduleId: str
cellProductId: str = ""
@router.post("/pilot-infusion-schedule/create-qc-work-order")
async def api_create_outbound_qc(
body: OutboundQcBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
from app.crud import qc_work_order as qc_svc
from app.crud.qc_work_order import resolve_outbound_biz_type_for_schedule
outbound_biz_type = resolve_outbound_biz_type_for_schedule(
db, body.scheduleId, tenant_no
)
wo = qc_svc.create_outbound_qc_work_order(
db,
schedule_id=body.scheduleId,
cell_product_id=body.cellProductId,
tenant_no=tenant_no,
user_id=user.get("user_id"),
create_dept_id=user.get("dept_id", ""),
biz_type=outbound_biz_type,
)
return success_response(qc_svc._wo_to_dict(wo), message="创建出库质检工单成功")
except ValueError as e:
return error_response(400, str(e))
except Exception as e:
db.rollback()
return error_response(400, str(e))