协议样本、生产产出和盘点明细按行更新并保留原 id;补齐存储证明质检预填、离职人员显示名,以及样本采集通知模板字段。 Co-authored-by: Cursor <cursoragent@cursor.com>
3157 lines
115 KiB
Python
3157 lines
115 KiB
Python
"""生产管理 API 路由(7 个子模块:耗材、试剂、SOP文档、工艺工序分类、产出物类型、耗材包、工艺库)"""
|
||
from fastapi import APIRouter, Depends, Query, HTTPException
|
||
from pydantic import BaseModel, ConfigDict, Field
|
||
from sqlalchemy.orm import Session
|
||
from typing import Any, Optional, List
|
||
|
||
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,
|
||
)
|
||
from app.core.response import success_response, error_response
|
||
from app.crud import consumable as consumable_svc
|
||
from app.crud import reagent as reagent_svc
|
||
from app.crud import consumable_stock_log as consumable_stock_log_svc
|
||
from app.crud import reagent_stock_log as reagent_stock_log_svc
|
||
from app.crud import sop_document as sop_document_svc
|
||
from app.crud import process_category as process_category_svc
|
||
from app.crud import equipment as equipment_svc
|
||
from app.crud import output_type as output_type_svc
|
||
from app.crud import production_product_type as production_product_type_svc
|
||
from app.crud import consumable_pack as consumable_pack_svc
|
||
from app.crud import process as process_svc
|
||
from app.crud import process_route as route_svc
|
||
from app.schemas.process_route import (
|
||
ProcessRouteCreateBody,
|
||
ProcessRouteUpdateBody,
|
||
SetCurrentBody,
|
||
StepAddBody,
|
||
StepCopyBody,
|
||
StepUpdateBody,
|
||
StepReorderBody,
|
||
StepReagentUpdateBody,
|
||
StepConsumableUpdateBody,
|
||
StepOutputUpdateBody,
|
||
StepDataCollectionUpdateBody,
|
||
)
|
||
from app.models.models import SysUser
|
||
from app.crud import production_work_order as production_work_order_svc
|
||
from app.crud import production_task as production_task_svc
|
||
from app.crud import production_task_set as production_task_set_svc
|
||
from app.crud import production_waste_output as production_waste_output_svc
|
||
from app.crud import cell_product as cell_product_svc
|
||
from app.crud import qc_work_order as qc_work_order_svc
|
||
from app.schemas.production_work_order import (
|
||
ProductionWorkOrderListQuery,
|
||
ProductionWorkOrderComplete,
|
||
ProductionWorkOrderCreate,
|
||
ProductionWorkOrderConfigureBody,
|
||
ProductionWorkOrderUpdate,
|
||
)
|
||
from app.schemas.production_task_set import (
|
||
ProductionTaskSetCreateBody,
|
||
ProductionTaskSetFromWorkOrderBody,
|
||
ProductionTaskSetUpdateBody,
|
||
ProductionTasksFromSetBody,
|
||
)
|
||
from app.schemas.production_task import (
|
||
ProductionTaskCreate,
|
||
ProductionTaskUpdate,
|
||
ProductionTaskStatusUpdate,
|
||
ProductionTaskForwardBody,
|
||
ProductionTaskAssigneeUpdate,
|
||
ProductionTaskStepDispatchBody,
|
||
SpawnTemporaryWorkOrderBody,
|
||
TaskStepAdd,
|
||
TaskStepUpdate,
|
||
TaskStepMaterialItem,
|
||
TaskStepOutputItem,
|
||
TaskStepDataCollectionItem,
|
||
TaskInputItem,
|
||
TaskOutputItem,
|
||
TaskStepReorderBody,
|
||
ProductionTaskListByWorkOrdersBody,
|
||
)
|
||
from app.schemas.cell_product import CellProductCreate, CellProductUpdate
|
||
from app.schemas.qc_work_order import ProductionQcWorkOrderCreateBody
|
||
|
||
router = APIRouter(prefix="/web/admin", tags=["生产管理"])
|
||
|
||
_LEGACY_MATERIAL_GONE_MSG = "耗材/试剂维护已迁移至【生产物料库】,请使用 production-material 接口"
|
||
|
||
|
||
def _legacy_material_write_gone():
|
||
return error_response(410, _LEGACY_MATERIAL_GONE_MSG)
|
||
|
||
|
||
def _get_tenant_no(db: Session, user: dict) -> str:
|
||
user_id = user.get("user_id")
|
||
if not user_id:
|
||
return "zkxw"
|
||
sys_user = db.query(SysUser).filter(SysUser.id == user_id).first()
|
||
return (sys_user.tenant_no if sys_user else None) or "zkxw"
|
||
|
||
|
||
def _get_current_user_id(user: dict) -> str:
|
||
return (user.get("user_id") or "").strip()
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 1. 耗材
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class ConsumableCreateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
consumable_code: str = Field(..., alias="consumableCode")
|
||
consumable_name: str = Field(..., alias="consumableName")
|
||
spec_model: Optional[str] = Field(None, alias="specModel")
|
||
standard_usage: Optional[str] = Field(None, alias="standardUsage")
|
||
unit: Optional[str] = None
|
||
description: Optional[str] = None
|
||
warning_qty: Optional[float] = Field(0, alias="warningQty")
|
||
status: Optional[str] = Field("1", alias="status")
|
||
order_num: Optional[int] = Field(0, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class ConsumableUpdateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
consumable_code: Optional[str] = Field(None, alias="consumableCode")
|
||
consumable_name: Optional[str] = Field(None, alias="consumableName")
|
||
spec_model: Optional[str] = Field(None, alias="specModel")
|
||
standard_usage: Optional[str] = Field(None, alias="standardUsage")
|
||
unit: Optional[str] = None
|
||
description: Optional[str] = None
|
||
warning_qty: Optional[float] = Field(None, alias="warningQty")
|
||
status: Optional[str] = Field(None, alias="status")
|
||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
@router.get("/consumables")
|
||
async def list_consumables(
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
|
||
keyword: Optional[str] = None,
|
||
user: dict = Depends(require_menu_code("production:consumable:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, total = consumable_svc.list_consumables(db, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no, user_info=user)
|
||
tp = consumable_svc.total_pages(total, pageSize)
|
||
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
@router.get("/consumables/all")
|
||
async def list_all_consumables(
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, _ = consumable_svc.list_consumables(db, page=1, page_size=9999, tenant_no=tenant_no, user_info=user)
|
||
return success_response(items)
|
||
|
||
|
||
@router.post("/consumables")
|
||
async def create_consumable(
|
||
body: ConsumableCreateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
return _legacy_material_write_gone()
|
||
|
||
|
||
@router.put("/consumables/{consumable_id}")
|
||
async def update_consumable(
|
||
consumable_id: str,
|
||
body: ConsumableUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
return _legacy_material_write_gone()
|
||
|
||
|
||
@router.delete("/consumables/{consumable_id}")
|
||
async def delete_consumable(
|
||
consumable_id: str,
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
return _legacy_material_write_gone()
|
||
|
||
|
||
class StockInBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
qty: float = Field(..., gt=0)
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class StockOutBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
qty: float = Field(..., gt=0)
|
||
receiver: str = Field(..., min_length=1)
|
||
purpose: Optional[str] = None
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class BatchStockInItem(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
materialId: str
|
||
qty: float = Field(..., gt=0)
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class BatchStockInBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
items: List[BatchStockInItem]
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class BatchStockOutItem(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
materialId: str
|
||
qty: float = Field(..., gt=0)
|
||
|
||
|
||
class BatchStockOutBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
items: List[BatchStockOutItem]
|
||
receiver: str = Field(..., min_length=1)
|
||
purpose: Optional[str] = None
|
||
remark: Optional[str] = None
|
||
|
||
|
||
@router.post("/consumables/{consumable_id}/stock-in")
|
||
async def consumable_stock_in(
|
||
consumable_id: str,
|
||
body: StockInBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
from decimal import Decimal
|
||
obj = consumable_svc.stock_in(
|
||
db, consumable_id,
|
||
qty=Decimal(str(body.qty)),
|
||
remark=body.remark,
|
||
user_id=user.get("user_id"),
|
||
tenant_no=tenant_no,
|
||
)
|
||
return success_response(consumable_svc._row_to_dict(obj), message="入库成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.post("/consumables/{consumable_id}/stock-out")
|
||
async def consumable_stock_out(
|
||
consumable_id: str,
|
||
body: StockOutBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
from decimal import Decimal
|
||
obj = consumable_svc.stock_out(
|
||
db, consumable_id,
|
||
qty=Decimal(str(body.qty)),
|
||
receiver=body.receiver,
|
||
purpose=body.purpose,
|
||
remark=body.remark,
|
||
user_id=user.get("user_id"),
|
||
tenant_no=tenant_no,
|
||
)
|
||
return success_response(consumable_svc._row_to_dict(obj), message="出库成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.post("/consumables/batch-stock-in")
|
||
async def consumable_batch_stock_in(
|
||
body: BatchStockInBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items_data = [it.model_dump() for it in body.items]
|
||
count = consumable_svc.batch_stock_in(
|
||
db, items=items_data, remark=body.remark,
|
||
tenant_no=tenant_no, user_id=user.get("user_id"),
|
||
)
|
||
return success_response({"count": count}, message=f"成功入库 {count} 条")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.post("/consumables/batch-stock-out")
|
||
async def consumable_batch_stock_out(
|
||
body: BatchStockOutBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items_data = [it.model_dump() for it in body.items]
|
||
count = consumable_svc.batch_stock_out(
|
||
db, items=items_data,
|
||
receiver=body.receiver, purpose=body.purpose, remark=body.remark,
|
||
tenant_no=tenant_no, user_id=user.get("user_id"),
|
||
)
|
||
return success_response({"count": count}, message=f"成功出库 {count} 条")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.get("/consumables/{consumable_id}/stock-logs")
|
||
async def list_consumable_stock_logs(
|
||
consumable_id: str,
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
|
||
changeType: Optional[str] = None,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, total = consumable_stock_log_svc.list_stock_logs(
|
||
db, tenant_no, page=page, page_size=pageSize,
|
||
material_id=consumable_id, change_type=changeType,
|
||
user_info=user,
|
||
)
|
||
tp = (total + pageSize - 1) // pageSize if total > 0 else 0
|
||
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
@router.get("/consumables/stock-logs")
|
||
async def list_all_consumable_stock_logs(
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
|
||
keyword: Optional[str] = None,
|
||
changeType: Optional[str] = None,
|
||
startTime: Optional[str] = None,
|
||
endTime: Optional[str] = None,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, total = consumable_stock_log_svc.list_stock_logs(
|
||
db, tenant_no, page=page, page_size=pageSize,
|
||
keyword=keyword, change_type=changeType,
|
||
start_time=startTime, end_time=endTime,
|
||
user_info=user,
|
||
)
|
||
tp = (total + pageSize - 1) // pageSize if total > 0 else 0
|
||
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 2. 试剂
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class ReagentCreateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
reagent_code: str = Field(..., alias="reagentCode")
|
||
reagent_name: str = Field(..., alias="reagentName")
|
||
spec: Optional[str] = None
|
||
standard_usage: Optional[str] = Field(None, alias="standardUsage")
|
||
unit: Optional[str] = None
|
||
description: Optional[str] = None
|
||
warning_qty: Optional[float] = Field(0, alias="warningQty")
|
||
status: Optional[str] = Field("1", alias="status")
|
||
order_num: Optional[int] = Field(0, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class ReagentUpdateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
reagent_code: Optional[str] = Field(None, alias="reagentCode")
|
||
reagent_name: Optional[str] = Field(None, alias="reagentName")
|
||
spec: Optional[str] = None
|
||
standard_usage: Optional[str] = Field(None, alias="standardUsage")
|
||
unit: Optional[str] = None
|
||
description: Optional[str] = None
|
||
warning_qty: Optional[float] = Field(None, alias="warningQty")
|
||
status: Optional[str] = Field(None, alias="status")
|
||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
@router.get("/reagents")
|
||
async def list_reagents(
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
|
||
keyword: Optional[str] = None,
|
||
user: dict = Depends(require_menu_code("production:reagent:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, total = reagent_svc.list_reagents(db, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no, user_info=user)
|
||
tp = reagent_svc.total_pages(total, pageSize)
|
||
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
@router.get("/reagents/all")
|
||
async def list_all_reagents(
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, _ = reagent_svc.list_reagents(db, page=1, page_size=9999, tenant_no=tenant_no, user_info=user)
|
||
return success_response(items)
|
||
|
||
|
||
@router.post("/reagents")
|
||
async def create_reagent(
|
||
body: ReagentCreateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
return _legacy_material_write_gone()
|
||
|
||
|
||
@router.put("/reagents/{reagent_id}")
|
||
async def update_reagent(
|
||
reagent_id: str,
|
||
body: ReagentUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
return _legacy_material_write_gone()
|
||
|
||
|
||
@router.delete("/reagents/{reagent_id}")
|
||
async def delete_reagent(
|
||
reagent_id: str,
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
return _legacy_material_write_gone()
|
||
|
||
|
||
@router.post("/reagents/{reagent_id}/stock-in")
|
||
async def reagent_stock_in(
|
||
reagent_id: str,
|
||
body: StockInBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
from decimal import Decimal
|
||
obj = reagent_svc.stock_in(
|
||
db, reagent_id,
|
||
qty=Decimal(str(body.qty)),
|
||
remark=body.remark,
|
||
user_id=user.get("user_id"),
|
||
tenant_no=tenant_no,
|
||
)
|
||
return success_response(reagent_svc._row_to_dict(obj), message="入库成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.post("/reagents/{reagent_id}/stock-out")
|
||
async def reagent_stock_out(
|
||
reagent_id: str,
|
||
body: StockOutBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
from decimal import Decimal
|
||
obj = reagent_svc.stock_out(
|
||
db, reagent_id,
|
||
qty=Decimal(str(body.qty)),
|
||
receiver=body.receiver,
|
||
purpose=body.purpose,
|
||
remark=body.remark,
|
||
user_id=user.get("user_id"),
|
||
tenant_no=tenant_no,
|
||
)
|
||
return success_response(reagent_svc._row_to_dict(obj), message="出库成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.post("/reagents/batch-stock-in")
|
||
async def reagent_batch_stock_in(
|
||
body: BatchStockInBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items_data = [it.model_dump() for it in body.items]
|
||
count = reagent_svc.batch_stock_in(
|
||
db, items=items_data, remark=body.remark,
|
||
tenant_no=tenant_no, user_id=user.get("user_id"),
|
||
)
|
||
return success_response({"count": count}, message=f"成功入库 {count} 条")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.post("/reagents/batch-stock-out")
|
||
async def reagent_batch_stock_out(
|
||
body: BatchStockOutBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items_data = [it.model_dump() for it in body.items]
|
||
count = reagent_svc.batch_stock_out(
|
||
db, items=items_data,
|
||
receiver=body.receiver, purpose=body.purpose, remark=body.remark,
|
||
tenant_no=tenant_no, user_id=user.get("user_id"),
|
||
)
|
||
return success_response({"count": count}, message=f"成功出库 {count} 条")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.get("/reagents/{reagent_id}/stock-logs")
|
||
async def list_reagent_stock_logs(
|
||
reagent_id: str,
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
|
||
changeType: Optional[str] = None,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, total = reagent_stock_log_svc.list_stock_logs(
|
||
db, tenant_no, page=page, page_size=pageSize,
|
||
material_id=reagent_id, change_type=changeType,
|
||
user_info=user,
|
||
)
|
||
tp = (total + pageSize - 1) // pageSize if total > 0 else 0
|
||
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
@router.get("/reagents/stock-logs")
|
||
async def list_all_reagent_stock_logs(
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
|
||
keyword: Optional[str] = None,
|
||
changeType: Optional[str] = None,
|
||
startTime: Optional[str] = None,
|
||
endTime: Optional[str] = None,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, total = reagent_stock_log_svc.list_stock_logs(
|
||
db, tenant_no, page=page, page_size=pageSize,
|
||
keyword=keyword, change_type=changeType,
|
||
start_time=startTime, end_time=endTime,
|
||
user_info=user,
|
||
)
|
||
tp = (total + pageSize - 1) // pageSize if total > 0 else 0
|
||
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 3. SOP文档
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class SopDocumentCreateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
sop_code: str = Field(..., alias="sopCode")
|
||
sop_name: str = Field(..., alias="sopName")
|
||
version_no: Optional[str] = Field(None, alias="versionNo")
|
||
sop_attachments: Optional[str] = Field(None, alias="sopAttachments")
|
||
order_num: Optional[int] = Field(0, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class SopDocumentUpdateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
sop_code: Optional[str] = Field(None, alias="sopCode")
|
||
sop_name: Optional[str] = Field(None, alias="sopName")
|
||
version_no: Optional[str] = Field(None, alias="versionNo")
|
||
sop_attachments: Optional[str] = Field(None, alias="sopAttachments")
|
||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
@router.get("/sop-documents")
|
||
async def list_sop_documents(
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
|
||
keyword: Optional[str] = None,
|
||
user: dict = Depends(require_menu_code("production:sop-document:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, total = sop_document_svc.list_sop_documents(db, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no, user_info=user)
|
||
tp = sop_document_svc.total_pages(total, pageSize)
|
||
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
@router.get("/sop-documents/all")
|
||
async def list_all_sop_documents(
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, _ = sop_document_svc.list_sop_documents(db, page=1, page_size=9999, tenant_no=tenant_no, user_info=user)
|
||
return success_response(items)
|
||
|
||
|
||
@router.post("/sop-documents")
|
||
async def create_sop_document(
|
||
body: SopDocumentCreateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
obj = sop_document_svc.create_sop_document(
|
||
db,
|
||
sop_code=body.sop_code,
|
||
sop_name=body.sop_name,
|
||
version_no=body.version_no,
|
||
sop_attachments=body.sop_attachments,
|
||
tenant_no=tenant_no,
|
||
order_num=body.order_num,
|
||
remark=body.remark,
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(
|
||
sop_document_svc._row_to_dict(obj, db, tenant_no),
|
||
message="创建成功",
|
||
)
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/sop-documents/{sop_id}")
|
||
async def update_sop_document(
|
||
sop_id: str,
|
||
body: SopDocumentUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
|
||
obj = sop_document_svc.update_sop_document(
|
||
db,
|
||
sop_id,
|
||
sop_code=payload.get("sop_code"),
|
||
sop_name=payload.get("sop_name"),
|
||
version_no=payload.get("version_no"),
|
||
sop_attachments=payload.get("sop_attachments"),
|
||
order_num=payload.get("order_num"),
|
||
remark=payload.get("remark"),
|
||
user_id=user.get("user_id"),
|
||
)
|
||
tenant_no = _get_tenant_no(db, user)
|
||
return success_response(
|
||
sop_document_svc._row_to_dict(obj, db, tenant_no),
|
||
message="更新成功",
|
||
)
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/sop-documents/{sop_id}")
|
||
async def delete_sop_document(
|
||
sop_id: str,
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
sop_document_svc.soft_delete_sop_document(db, sop_id)
|
||
return success_response(None, message="删除成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 4. 工艺工序分类
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class ProcessCategoryCreateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
category_code: str = Field(..., alias="categoryCode")
|
||
category_name: str = Field(..., alias="categoryName")
|
||
order_num: Optional[int] = Field(0, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class ProcessCategoryUpdateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
category_code: Optional[str] = Field(None, alias="categoryCode")
|
||
category_name: Optional[str] = Field(None, alias="categoryName")
|
||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
@router.get("/process-categories")
|
||
async def list_process_categories(
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
|
||
keyword: Optional[str] = None,
|
||
user: dict = Depends(require_menu_code("production:process-category:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, total = process_category_svc.list_process_categories(db, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no, user_info=user)
|
||
tp = process_category_svc.total_pages(total, pageSize)
|
||
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
@router.get("/process-categories/all")
|
||
async def list_all_process_categories(
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, _ = process_category_svc.list_process_categories(db, page=1, page_size=9999, tenant_no=tenant_no, user_info=user)
|
||
return success_response(items)
|
||
|
||
|
||
@router.post("/process-categories")
|
||
async def create_process_category(
|
||
body: ProcessCategoryCreateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
obj = process_category_svc.create_process_category(
|
||
db,
|
||
category_code=body.category_code,
|
||
category_name=body.category_name,
|
||
tenant_no=tenant_no,
|
||
order_num=body.order_num,
|
||
remark=body.remark,
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(process_category_svc._row_to_dict(obj), message="创建成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/process-categories/{category_id}")
|
||
async def update_process_category(
|
||
category_id: str,
|
||
body: ProcessCategoryUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
|
||
obj = process_category_svc.update_process_category(
|
||
db,
|
||
category_id,
|
||
category_code=payload.get("category_code"),
|
||
category_name=payload.get("category_name"),
|
||
order_num=payload.get("order_num"),
|
||
remark=payload.get("remark"),
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(process_category_svc._row_to_dict(obj), message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/process-categories/{category_id}")
|
||
async def delete_process_category(
|
||
category_id: str,
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
process_category_svc.soft_delete_process_category(db, category_id)
|
||
return success_response(None, message="删除成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 设备管理
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class EquipmentCreateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
equipment_code: str = Field(..., alias="equipmentCode")
|
||
equipment_name: str = Field(..., alias="equipmentName")
|
||
equipment_model: Optional[str] = Field(None, alias="equipmentModel")
|
||
manufacturer: Optional[str] = None
|
||
purchase_date: Optional[str] = Field(None, alias="purchaseDate")
|
||
purchase_amount: Optional[float] = Field(None, alias="purchaseAmount")
|
||
location: Optional[str] = None
|
||
serial_no: Optional[str] = Field(None, alias="serialNo")
|
||
status: Optional[str] = "1"
|
||
order_num: Optional[int] = Field(0, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class EquipmentUpdateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
equipment_code: Optional[str] = Field(None, alias="equipmentCode")
|
||
equipment_name: Optional[str] = Field(None, alias="equipmentName")
|
||
equipment_model: Optional[str] = Field(None, alias="equipmentModel")
|
||
manufacturer: Optional[str] = None
|
||
purchase_date: Optional[str] = Field(None, alias="purchaseDate")
|
||
purchase_amount: Optional[float] = Field(None, alias="purchaseAmount")
|
||
location: Optional[str] = None
|
||
serial_no: Optional[str] = Field(None, alias="serialNo")
|
||
status: Optional[str] = None
|
||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
@router.get("/equipments")
|
||
async def list_equipments(
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
|
||
keyword: Optional[str] = None,
|
||
status: Optional[str] = None,
|
||
user: dict = Depends(require_menu_code("production:equipment:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, total = equipment_svc.list_equipments(
|
||
db,
|
||
page=page,
|
||
page_size=pageSize,
|
||
keyword=keyword,
|
||
status=status,
|
||
tenant_no=tenant_no,
|
||
user_info=user,
|
||
)
|
||
tp = equipment_svc.total_pages(total, pageSize)
|
||
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
@router.get("/equipments/all")
|
||
async def list_all_equipments(
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, _ = equipment_svc.list_equipments(
|
||
db, page=1, page_size=9999, tenant_no=tenant_no, user_info=user
|
||
)
|
||
return success_response(items)
|
||
|
||
|
||
@router.post("/equipments")
|
||
async def create_equipment(
|
||
body: EquipmentCreateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
obj = equipment_svc.create_equipment(
|
||
db,
|
||
equipment_code=body.equipment_code,
|
||
equipment_name=body.equipment_name,
|
||
equipment_model=body.equipment_model,
|
||
manufacturer=body.manufacturer,
|
||
purchase_date=body.purchase_date,
|
||
purchase_amount=body.purchase_amount,
|
||
location=body.location,
|
||
serial_no=body.serial_no,
|
||
status=body.status,
|
||
tenant_no=tenant_no,
|
||
order_num=body.order_num,
|
||
remark=body.remark,
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(equipment_svc._row_to_dict(obj), message="创建成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/equipments/{equipment_id}")
|
||
async def update_equipment(
|
||
equipment_id: str,
|
||
body: EquipmentUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
payload: dict[str, Any] = body.model_dump(by_alias=False)
|
||
obj = equipment_svc.update_equipment(
|
||
db,
|
||
equipment_id,
|
||
equipment_code=payload.get("equipment_code"),
|
||
equipment_name=payload.get("equipment_name"),
|
||
equipment_model=payload.get("equipment_model"),
|
||
manufacturer=payload.get("manufacturer"),
|
||
purchase_date=payload.get("purchase_date"),
|
||
purchase_amount=payload.get("purchase_amount"),
|
||
location=payload.get("location"),
|
||
serial_no=payload.get("serial_no"),
|
||
status=payload.get("status"),
|
||
order_num=payload.get("order_num"),
|
||
remark=payload.get("remark"),
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(equipment_svc._row_to_dict(obj), message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/equipments/{equipment_id}")
|
||
async def delete_equipment(
|
||
equipment_id: str,
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
equipment_svc.soft_delete_equipment(db, equipment_id)
|
||
return success_response(None, message="删除成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 5. 工艺产出物类型
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class OutputTypeCreateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
type_code: str = Field(..., alias="typeCode")
|
||
type_name: str = Field(..., alias="typeName")
|
||
order_num: Optional[int] = Field(0, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class OutputTypeUpdateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
type_code: Optional[str] = Field(None, alias="typeCode")
|
||
type_name: Optional[str] = Field(None, alias="typeName")
|
||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
@router.get("/output-types")
|
||
async def list_output_types(
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
|
||
keyword: Optional[str] = None,
|
||
user: dict = Depends(require_menu_code("production:output-type:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, total = output_type_svc.list_output_types(db, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no, user_info=user)
|
||
tp = output_type_svc.total_pages(total, pageSize)
|
||
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
@router.get("/output-types/all")
|
||
async def list_all_output_types(
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, _ = output_type_svc.list_output_types(db, page=1, page_size=9999, tenant_no=tenant_no, user_info=user)
|
||
return success_response(items)
|
||
|
||
|
||
@router.post("/output-types")
|
||
async def create_output_type(
|
||
body: OutputTypeCreateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
obj = output_type_svc.create_output_type(
|
||
db,
|
||
type_code=body.type_code,
|
||
type_name=body.type_name,
|
||
tenant_no=tenant_no,
|
||
order_num=body.order_num,
|
||
remark=body.remark,
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(output_type_svc._row_to_dict(obj), message="创建成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/output-types/{type_id}")
|
||
async def update_output_type(
|
||
type_id: str,
|
||
body: OutputTypeUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
|
||
obj = output_type_svc.update_output_type(
|
||
db,
|
||
type_id,
|
||
type_code=payload.get("type_code"),
|
||
type_name=payload.get("type_name"),
|
||
order_num=payload.get("order_num"),
|
||
remark=payload.get("remark"),
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(output_type_svc._row_to_dict(obj), message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/output-types/{type_id}")
|
||
async def delete_output_type(
|
||
type_id: str,
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
output_type_svc.soft_delete_output_type(db, type_id)
|
||
return success_response(None, message="删除成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 5b. 生产产品类型(生产专用,解耦中试 biz_cell_product_type)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class ProductionProductTypeCreateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
product_type_code: str = Field(..., alias="productTypeCode")
|
||
product_type_name: str = Field(..., alias="productTypeName")
|
||
order_num: Optional[int] = Field(0, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class ProductionProductTypeUpdateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
product_type_code: Optional[str] = Field(None, alias="productTypeCode")
|
||
product_type_name: Optional[str] = Field(None, alias="productTypeName")
|
||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
@router.get("/production-product-types")
|
||
async def list_production_product_types(
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
|
||
keyword: Optional[str] = None,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, total = production_product_type_svc.list_production_product_types(
|
||
db, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no, user_info=user
|
||
)
|
||
tp = production_product_type_svc.total_pages(total, pageSize)
|
||
return success_response(
|
||
{"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}
|
||
)
|
||
|
||
|
||
@router.post("/production-product-types")
|
||
async def create_production_product_type(
|
||
body: ProductionProductTypeCreateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
obj = production_product_type_svc.create_production_product_type(
|
||
db,
|
||
product_type_code=body.product_type_code,
|
||
product_type_name=body.product_type_name,
|
||
tenant_no=tenant_no,
|
||
order_num=body.order_num,
|
||
remark=body.remark,
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(production_product_type_svc._row_to_dict(obj), message="创建成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/production-product-types/{row_id}")
|
||
async def update_production_product_type(
|
||
row_id: str,
|
||
body: ProductionProductTypeUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
|
||
obj = production_product_type_svc.update_production_product_type(
|
||
db,
|
||
row_id,
|
||
product_type_code=payload.get("product_type_code"),
|
||
product_type_name=payload.get("product_type_name"),
|
||
order_num=payload.get("order_num"),
|
||
remark=payload.get("remark"),
|
||
user_id=user.get("user_id"),
|
||
tenant_no=tenant_no,
|
||
)
|
||
return success_response(production_product_type_svc._row_to_dict(obj), message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/production-product-types/{row_id}")
|
||
async def delete_production_product_type(
|
||
row_id: str,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
production_product_type_svc.soft_delete_production_product_type(
|
||
db, row_id, tenant_no=tenant_no
|
||
)
|
||
return success_response(None, message="删除成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 6. 耗材包(主 + 子项)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class PackItemInput(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
consumable_code: Optional[str] = Field(None, alias="consumableCode")
|
||
consumable_name: Optional[str] = Field(None, alias="consumableName")
|
||
spec_model: Optional[str] = Field(None, alias="specModel")
|
||
standard_usage: Optional[str] = Field(None, alias="standardUsage")
|
||
unit: Optional[str] = None
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class ConsumablePackCreateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
pack_code: str = Field(..., alias="packCode")
|
||
pack_name: str = Field(..., alias="packName")
|
||
usage_scene: Optional[str] = Field(None, alias="usageScene")
|
||
pack_type: Optional[str] = Field(None, alias="packType")
|
||
status: Optional[int] = 1
|
||
order_num: Optional[int] = Field(0, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
items: List[PackItemInput] = []
|
||
|
||
|
||
class ConsumablePackUpdateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
pack_code: Optional[str] = Field(None, alias="packCode")
|
||
pack_name: Optional[str] = Field(None, alias="packName")
|
||
usage_scene: Optional[str] = Field(None, alias="usageScene")
|
||
pack_type: Optional[str] = Field(None, alias="packType")
|
||
status: Optional[int] = None
|
||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
items: Optional[List[PackItemInput]] = None
|
||
|
||
|
||
@router.get("/consumable-packs")
|
||
async def list_consumable_packs(
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
|
||
keyword: Optional[str] = None,
|
||
user: dict = Depends(require_menu_code("production:consumable-pack:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, total = consumable_pack_svc.list_consumable_packs(db, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no, user_info=user)
|
||
tp = consumable_pack_svc.total_pages(total, pageSize)
|
||
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
@router.get("/consumable-packs/all")
|
||
async def list_all_consumable_packs(
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items = consumable_pack_svc.list_all_packs(db, tenant_no, user_info=user)
|
||
return success_response(items)
|
||
|
||
|
||
@router.get("/consumable-packs/{pack_id}")
|
||
async def get_consumable_pack_detail(
|
||
pack_id: str,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
detail = consumable_pack_svc.get_pack_detail(db, pack_id)
|
||
if not detail:
|
||
return error_response(404, "耗材包不存在")
|
||
return success_response(detail)
|
||
|
||
|
||
@router.post("/consumable-packs")
|
||
async def create_consumable_pack(
|
||
body: ConsumablePackCreateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items_data = [it.model_dump(by_alias=False) for it in body.items] if body.items else None
|
||
obj = consumable_pack_svc.create_consumable_pack(
|
||
db,
|
||
pack_code=body.pack_code,
|
||
pack_name=body.pack_name,
|
||
usage_scene=body.usage_scene,
|
||
pack_type=body.pack_type,
|
||
status=body.status,
|
||
items=items_data,
|
||
tenant_no=tenant_no,
|
||
order_num=body.order_num,
|
||
remark=body.remark,
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(consumable_pack_svc._row_to_dict(obj), message="创建成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/consumable-packs/{pack_id}")
|
||
async def update_consumable_pack(
|
||
pack_id: str,
|
||
body: ConsumablePackUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
|
||
items_data = [it.model_dump(by_alias=False) for it in body.items] if body.items is not None else None
|
||
obj = consumable_pack_svc.update_consumable_pack(
|
||
db,
|
||
pack_id,
|
||
pack_code=payload.get("pack_code"),
|
||
pack_name=payload.get("pack_name"),
|
||
usage_scene=payload.get("usage_scene"),
|
||
pack_type=payload.get("pack_type"),
|
||
status=payload.get("status"),
|
||
items=items_data,
|
||
order_num=payload.get("order_num"),
|
||
remark=payload.get("remark"),
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(consumable_pack_svc._row_to_dict(obj), message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/consumable-packs/{pack_id}")
|
||
async def delete_consumable_pack(
|
||
pack_id: str,
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
consumable_pack_svc.soft_delete_consumable_pack(db, pack_id)
|
||
return success_response(None, message="删除成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 7. 工艺库(主 + 3 个子表:试剂、耗材、产出物)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class ProcessReagentInput(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
id: Optional[str] = None
|
||
reagent_code: Optional[str] = Field(None, alias="reagentCode")
|
||
reagent_name: Optional[str] = Field(None, alias="reagentName")
|
||
spec: Optional[str] = None
|
||
standard_usage: Optional[str] = Field(None, alias="standardUsage")
|
||
unit: Optional[str] = None
|
||
is_key_material: Optional[str] = Field("0", alias="isKeyMaterial")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class ProcessConsumableInput(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
id: Optional[str] = None
|
||
consumable_code: Optional[str] = Field(None, alias="consumableCode")
|
||
consumable_name: Optional[str] = Field(None, alias="consumableName")
|
||
spec_model: Optional[str] = Field(None, alias="specModel")
|
||
standard_usage: Optional[str] = Field(None, alias="standardUsage")
|
||
unit: Optional[str] = None
|
||
is_required: Optional[str] = Field("1", alias="isRequired")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class ProcessOutputInput(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
id: Optional[str] = None
|
||
output_type_id: Optional[str] = Field(None, alias="outputTypeId")
|
||
output_name: Optional[str] = Field(None, alias="outputName")
|
||
output_barcode: Optional[str] = Field(None, alias="outputBarcode")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class ProcessDataCollectionInput(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
id: Optional[str] = None
|
||
collection_name: str = Field(..., alias="collectionName")
|
||
unit: Optional[str] = None
|
||
order_num: Optional[int] = Field(0, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class ProcessCreateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
process_code: str = Field(..., alias="processCode")
|
||
category_id: Optional[str] = Field(None, alias="categoryId")
|
||
process_name: str = Field(..., alias="processName")
|
||
description: Optional[str] = None
|
||
equipment_ids: Optional[List[str]] = Field(None, alias="equipmentIds")
|
||
estimated_duration: Optional[str] = Field(None, alias="estimatedDuration")
|
||
sop_ids: Optional[str] = Field(None, alias="sopIds")
|
||
sop_content: Optional[str] = Field(None, alias="sopContent")
|
||
order_num: Optional[int] = Field(0, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
reagents: List[ProcessReagentInput] = []
|
||
consumables: List[ProcessConsumableInput] = []
|
||
outputs: List[ProcessOutputInput] = []
|
||
data_collections: List[ProcessDataCollectionInput] = Field([], alias="dataCollections")
|
||
|
||
|
||
class ProcessUpdateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
process_code: Optional[str] = Field(None, alias="processCode")
|
||
category_id: Optional[str] = Field(None, alias="categoryId")
|
||
process_name: Optional[str] = Field(None, alias="processName")
|
||
description: Optional[str] = None
|
||
equipment_ids: Optional[List[str]] = Field(None, alias="equipmentIds")
|
||
estimated_duration: Optional[str] = Field(None, alias="estimatedDuration")
|
||
sop_ids: Optional[str] = Field(None, alias="sopIds")
|
||
sop_content: Optional[str] = Field(None, alias="sopContent")
|
||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
reagents: Optional[List[ProcessReagentInput]] = None
|
||
consumables: Optional[List[ProcessConsumableInput]] = None
|
||
outputs: Optional[List[ProcessOutputInput]] = None
|
||
data_collections: Optional[List[ProcessDataCollectionInput]] = Field(None, alias="dataCollections")
|
||
|
||
|
||
@router.get("/processes")
|
||
async def list_processes(
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
|
||
keyword: Optional[str] = None,
|
||
user: dict = Depends(require_menu_code("production:process:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, total = process_svc.list_processes(db, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no, user_info=user)
|
||
tp = process_svc.total_pages(total, pageSize)
|
||
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
@router.get("/processes/all")
|
||
async def list_all_processes(
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items = process_svc.list_all_processes(db, tenant_no, user_info=user)
|
||
return success_response(items)
|
||
|
||
|
||
@router.get("/processes/{process_id}")
|
||
async def get_process_detail(
|
||
process_id: str,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
detail = process_svc.get_process_detail(db, process_id)
|
||
if not detail:
|
||
return error_response(404, "工艺不存在")
|
||
return success_response(detail)
|
||
|
||
|
||
@router.post("/processes")
|
||
async def create_process(
|
||
body: ProcessCreateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
reagents_data = [r.model_dump(by_alias=False) for r in body.reagents] if body.reagents else None
|
||
consumables_data = [c.model_dump(by_alias=False) for c in body.consumables] if body.consumables else None
|
||
outputs_data = [o.model_dump(by_alias=False) for o in body.outputs] if body.outputs else None
|
||
data_collections_data = [d.model_dump(by_alias=False) for d in body.data_collections] if body.data_collections else None
|
||
# Convert string fields to appropriate types for sub-records
|
||
for rd in (reagents_data or []):
|
||
if "is_key_material" in rd and isinstance(rd["is_key_material"], str):
|
||
rd["is_key_material"] = int(rd["is_key_material"])
|
||
for cd in (consumables_data or []):
|
||
if "is_required" in cd and isinstance(cd["is_required"], str):
|
||
cd["is_required"] = int(cd["is_required"])
|
||
obj = process_svc.create_process(
|
||
db,
|
||
process_code=body.process_code,
|
||
category_id=body.category_id,
|
||
process_name=body.process_name,
|
||
description=body.description,
|
||
equipment_ids=body.equipment_ids,
|
||
estimated_duration=body.estimated_duration,
|
||
sop_ids=body.sop_ids,
|
||
sop_content=body.sop_content,
|
||
reagents=reagents_data,
|
||
consumables=consumables_data,
|
||
outputs=outputs_data,
|
||
data_collections=data_collections_data,
|
||
tenant_no=tenant_no,
|
||
order_num=body.order_num,
|
||
remark=body.remark,
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(process_svc._row_to_dict(obj), message="创建成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/processes/{process_id}")
|
||
async def update_process(
|
||
process_id: str,
|
||
body: ProcessUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
|
||
reagents_data = [r.model_dump(by_alias=False) for r in body.reagents] if body.reagents is not None else None
|
||
consumables_data = [c.model_dump(by_alias=False) for c in body.consumables] if body.consumables is not None else None
|
||
outputs_data = [o.model_dump(by_alias=False) for o in body.outputs] if body.outputs is not None else None
|
||
data_collections_data = [d.model_dump(by_alias=False) for d in body.data_collections] if body.data_collections is not None else None
|
||
# Convert string fields to appropriate types for sub-records
|
||
for rd in (reagents_data or []):
|
||
if "is_key_material" in rd and isinstance(rd["is_key_material"], str):
|
||
rd["is_key_material"] = int(rd["is_key_material"])
|
||
for cd in (consumables_data or []):
|
||
if "is_required" in cd and isinstance(cd["is_required"], str):
|
||
cd["is_required"] = int(cd["is_required"])
|
||
update_kwargs: dict[str, Any] = {
|
||
"process_code": payload.get("process_code"),
|
||
"category_id": payload.get("category_id"),
|
||
"process_name": payload.get("process_name"),
|
||
"description": payload.get("description"),
|
||
"estimated_duration": payload.get("estimated_duration"),
|
||
"sop_ids": payload.get("sop_ids"),
|
||
"sop_content": payload.get("sop_content"),
|
||
"reagents": reagents_data,
|
||
"consumables": consumables_data,
|
||
"outputs": outputs_data,
|
||
"data_collections": data_collections_data,
|
||
"order_num": payload.get("order_num"),
|
||
"remark": payload.get("remark"),
|
||
"user_id": user.get("user_id"),
|
||
}
|
||
if "equipment_ids" in payload:
|
||
update_kwargs["equipment_ids"] = payload.get("equipment_ids")
|
||
obj = process_svc.update_process(db, process_id, **update_kwargs)
|
||
return success_response(process_svc._row_to_dict(obj), message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/processes/{process_id}")
|
||
async def delete_process(
|
||
process_id: str,
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
process_svc.soft_delete_process(db, process_id)
|
||
return success_response(None, message="删除成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 8. 工艺路线管理
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
@router.get("/process-routes")
|
||
async def list_process_routes(
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
|
||
routeName: Optional[str] = None,
|
||
routeCode: Optional[str] = None,
|
||
productTypeId: Optional[str] = None,
|
||
user: dict = Depends(require_menu_code("production:process-route:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, total = route_svc.list_routes(
|
||
db, page=page, page_size=pageSize,
|
||
route_name=routeName, route_code=routeCode,
|
||
product_type_id=productTypeId, tenant_no=tenant_no,
|
||
user_info=user,
|
||
)
|
||
tp = route_svc.total_pages(total, pageSize)
|
||
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
@router.get("/process-routes/{route_id}")
|
||
async def get_process_route_detail(
|
||
route_id: str,
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
detail = route_svc.get_route_detail(db, route_id)
|
||
if not detail:
|
||
return error_response(404, "路线不存在")
|
||
return success_response(detail)
|
||
|
||
|
||
@router.post("/process-routes")
|
||
async def create_process_route(
|
||
body: ProcessRouteCreateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
obj = route_svc.create_route(
|
||
db,
|
||
route_code=body.route_code,
|
||
route_name=body.route_name,
|
||
product_type_id=body.product_type_id,
|
||
version=body.version,
|
||
remark=body.remark,
|
||
tenant_no=tenant_no,
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(route_svc._route_row_to_dict(obj), message="创建成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/process-routes/{route_id}")
|
||
async def update_process_route(
|
||
route_id: str,
|
||
body: ProcessRouteUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
route_svc.assert_route_writable(db, route_id, user)
|
||
payload = body.model_dump(exclude_unset=True, by_alias=False)
|
||
update_kwargs = {
|
||
"user_id": user.get("user_id"),
|
||
"route_code": payload.get("route_code"),
|
||
"route_name": payload.get("route_name"),
|
||
"product_type_id": payload.get("product_type_id"),
|
||
"version": payload.get("version"),
|
||
"remark": payload.get("remark"),
|
||
}
|
||
if "equipment_ids" in payload:
|
||
update_kwargs["equipment_ids"] = payload.get("equipment_ids")
|
||
obj = route_svc.update_route(db, route_id, **update_kwargs)
|
||
return success_response(route_svc._route_row_to_dict(obj), message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/process-routes/set-current")
|
||
async def set_current_process_route(
|
||
body: SetCurrentBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
route_svc.assert_route_writable(db, body.route_id, user)
|
||
tenant_no = _get_tenant_no(db, user)
|
||
obj = route_svc.set_current_route(db, body.route_id, tenant_no)
|
||
return success_response(route_svc._route_row_to_dict(obj), message="设置成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/process-routes/{route_id}")
|
||
async def delete_process_route(
|
||
route_id: str,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
route_svc.assert_route_writable(db, route_id, user)
|
||
route_svc.soft_delete_route(db, route_id)
|
||
return success_response(None, message="删除成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
# ----- 步骤操作 -----
|
||
|
||
@router.post("/process-routes/step/add")
|
||
async def add_route_step(
|
||
body: StepAddBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
route_svc.assert_route_writable(db, body.route_id, user)
|
||
tenant_no = _get_tenant_no(db, user)
|
||
step_data = route_svc.add_step(
|
||
db,
|
||
route_id=body.route_id,
|
||
process_id=body.process_id,
|
||
step_order=body.step_order,
|
||
tenant_no=tenant_no,
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(step_data, message="添加成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.post("/process-routes/step/copy")
|
||
async def copy_route_step(
|
||
body: StepCopyBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
route_svc.assert_step_route_writable(db, body.step_id, user)
|
||
tenant_no = _get_tenant_no(db, user)
|
||
step_data = route_svc.copy_step(
|
||
db,
|
||
step_id=body.step_id,
|
||
tenant_no=tenant_no,
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(step_data, message="复制成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/process-routes/step/update")
|
||
async def update_route_step(
|
||
body: StepUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
route_svc.assert_step_route_writable(db, body.step_id, user)
|
||
payload = body.model_dump(exclude_unset=True, by_alias=False)
|
||
payload.pop("step_id", None)
|
||
obj = route_svc.update_step(db, body.step_id, user_id=user.get("user_id"), **payload)
|
||
return success_response(route_svc._step_row_to_dict(obj), message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/process-routes/step/reorder")
|
||
async def reorder_route_steps(
|
||
body: StepReorderBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
route_svc.assert_route_writable(db, body.route_id, user)
|
||
route_svc.reorder_steps(db, body.route_id, body.step_ids)
|
||
return success_response(None, message="排序成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/process-routes/step/{step_id}")
|
||
async def delete_route_step(
|
||
step_id: str,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
route_svc.assert_step_route_writable(db, step_id, user)
|
||
route_svc.soft_delete_step(db, step_id)
|
||
return success_response(None, message="删除成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
# ----- 步骤子表 -----
|
||
|
||
@router.put("/process-routes/step/reagent/update")
|
||
async def update_step_reagents(
|
||
body: StepReagentUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
route_svc.assert_step_route_writable(db, body.step_id, user)
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items_data = [it.model_dump(by_alias=False) for it in body.items]
|
||
route_svc.update_step_reagents(db, body.step_id, items_data, tenant_no, user.get("user_id"))
|
||
return success_response(None, message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/process-routes/step/consumable/update")
|
||
async def update_step_consumables(
|
||
body: StepConsumableUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
route_svc.assert_step_route_writable(db, body.step_id, user)
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items_data = [it.model_dump(by_alias=False) for it in body.items]
|
||
route_svc.update_step_consumables(db, body.step_id, items_data, tenant_no, user.get("user_id"))
|
||
return success_response(None, message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/process-routes/step/output/update")
|
||
async def update_step_outputs(
|
||
body: StepOutputUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
route_svc.assert_step_route_writable(db, body.step_id, user)
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items_data = [it.model_dump(by_alias=False) for it in body.items]
|
||
route_svc.update_step_outputs(db, body.step_id, items_data, tenant_no, user.get("user_id"))
|
||
return success_response(None, message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/process-routes/step/data-collection/update")
|
||
async def update_step_data_collections(
|
||
body: StepDataCollectionUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
route_svc.assert_step_route_writable(db, body.step_id, user)
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items_data = [it.model_dump(by_alias=False) for it in body.items]
|
||
route_svc.update_step_data_collections(db, body.step_id, items_data, tenant_no, user.get("user_id"))
|
||
return success_response(None, message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 9. 入库生产工单
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
@router.post("/production-work-orders")
|
||
async def create_production_work_order(
|
||
body: ProductionWorkOrderCreate,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
wo_type = (body.work_order_type or "inbound").strip()
|
||
if wo_type == "temporary":
|
||
wo = production_work_order_svc.create_temp_production_work_order(
|
||
db,
|
||
tenant_no=tenant_no,
|
||
user_id=user.get("user_id"),
|
||
create_dept_id=user.get("dept_id", ""),
|
||
remark=body.remark or "",
|
||
)
|
||
else:
|
||
wo = production_work_order_svc.create_production_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",
|
||
remark=body.remark or "",
|
||
)
|
||
db.commit()
|
||
return success_response(production_work_order_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))
|
||
|
||
|
||
@router.get("/production-work-orders")
|
||
async def list_production_work_orders(
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
|
||
keyword: Optional[str] = None,
|
||
workOrderNo: Optional[str] = None,
|
||
workOrderStatus: Optional[str] = Query(None, description="工单状态,多个用英文逗号分隔"),
|
||
workOrderType: Optional[str] = None,
|
||
receptionSource: Optional[str] = Query(None, alias="receptionSource"),
|
||
bizType: Optional[str] = Query(None, alias="bizType"),
|
||
protocolNo: Optional[str] = None,
|
||
sampleCode: Optional[str] = None,
|
||
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"),
|
||
plannedStartTimeStart: Optional[str] = Query(None, alias="plannedStartTimeStart"),
|
||
plannedStartTimeEnd: Optional[str] = Query(None, alias="plannedStartTimeEnd"),
|
||
excludeVoidProtocol: bool = Query(True, alias="excludeVoidProtocol"),
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
query = ProductionWorkOrderListQuery(
|
||
page=page, page_size=pageSize,
|
||
keyword=keyword,
|
||
work_order_no=workOrderNo, work_order_status=workOrderStatus,
|
||
work_order_type=workOrderType,
|
||
reception_source=receptionSource,
|
||
biz_type=bizType,
|
||
protocol_no=protocolNo, sample_code=sampleCode,
|
||
create_time_start=createTimeStart,
|
||
create_time_end=createTimeEnd,
|
||
infusion_time_start=infusionTimeStart,
|
||
infusion_time_end=infusionTimeEnd,
|
||
planned_start_time_start=plannedStartTimeStart,
|
||
planned_start_time_end=plannedStartTimeEnd,
|
||
exclude_void_protocol=excludeVoidProtocol,
|
||
)
|
||
result = production_work_order_svc.list_production_work_orders(
|
||
db, tenant_no=tenant_no, query=query, page=page, page_size=pageSize,
|
||
user_info=user,
|
||
)
|
||
items = result.get("items", [])
|
||
total = result.get("total", 0)
|
||
tp = production_task_svc.total_pages(total, pageSize)
|
||
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
@router.get("/production-work-orders/tab-step-counts")
|
||
async def production_work_order_tab_step_counts(
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
counts = production_task_svc.count_production_work_order_tab_step_badges(
|
||
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("/production-work-orders/mine-tab-step-counts")
|
||
async def production_work_order_mine_tab_step_counts(
|
||
user: dict = Depends(require_menu_code("production:my-task:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
counts = production_task_svc.count_mine_production_work_order_tab_step_badges(
|
||
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("/production-work-orders/{wo_id}")
|
||
async def get_production_work_order(
|
||
wo_id: str,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
wo = production_work_order_svc.get_production_work_order(db, wo_id, tenant_no)
|
||
if not wo:
|
||
return error_response(404, "工单不存在")
|
||
return success_response(production_work_order_svc._wo_to_response_dict(db, wo))
|
||
|
||
|
||
@router.put("/production-work-orders/{wo_id}")
|
||
async def update_production_work_order_api(
|
||
wo_id: str,
|
||
body: ProductionWorkOrderUpdate,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
wo = production_work_order_svc.update_production_work_order(
|
||
db,
|
||
wo_id,
|
||
tenant_no,
|
||
user.get("user_id"),
|
||
remark=body.remark,
|
||
)
|
||
db.commit()
|
||
return success_response(
|
||
production_work_order_svc._wo_to_response_dict(db, wo),
|
||
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.put("/production-work-orders/{wo_id}/complete")
|
||
async def complete_production_work_order(
|
||
wo_id: str,
|
||
body: ProductionWorkOrderComplete,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
wo = production_work_order_svc.complete_production_work_order(
|
||
db, wo_id, tenant_no, user.get("user_id"), body,
|
||
)
|
||
return success_response(production_work_order_svc._wo_to_response_dict(db, wo), message="操作成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/production-work-orders/{wo_id}/void")
|
||
async def void_production_work_order_api(
|
||
wo_id: str,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
wo = production_work_order_svc.void_production_work_order(
|
||
db, wo_id, tenant_no, user.get("user_id"),
|
||
)
|
||
return success_response(
|
||
production_work_order_svc._wo_to_response_dict(db, wo),
|
||
message="工单已作废",
|
||
)
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/production-work-orders/{wo_id}")
|
||
async def delete_production_work_order_api(
|
||
wo_id: str,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
production_work_order_svc.delete_production_work_order(db, wo_id, tenant_no, user.get("user_id"))
|
||
return success_response(None, message="删除成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.post("/production-work-orders/{wo_id}/configure")
|
||
async def configure_production_work_order(
|
||
wo_id: str,
|
||
body: ProductionWorkOrderConfigureBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
wo = production_work_order_svc.configure_temp_production_work_order(
|
||
db,
|
||
wo_id,
|
||
task_name=body.task_name or "",
|
||
task_type=body.task_type or "cell_storage",
|
||
route_id=body.route_id,
|
||
description=body.description or "",
|
||
remark=body.remark or "",
|
||
assignee_user_id=body.assignee_user_id,
|
||
tenant_no=tenant_no,
|
||
user_id=user.get("user_id"),
|
||
create_dept_id=user.get("dept_id", ""),
|
||
)
|
||
db.commit()
|
||
return success_response(production_work_order_svc._wo_to_dict(wo), 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.get("/production-work-orders/{wo_id}/cell-product-options")
|
||
async def list_production_work_order_cell_product_options(
|
||
wo_id: str,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items = qc_work_order_svc.list_step_output_dispatch_options_for_production_work_order(
|
||
db, production_work_order_id=wo_id, tenant_no=tenant_no
|
||
)
|
||
return success_response(items)
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.get("/production-work-orders/{wo_id}/qc-info")
|
||
async def get_production_work_order_qc_info_api(
|
||
wo_id: str,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
data = qc_work_order_svc.get_production_work_order_qc_info(
|
||
db, production_work_order_id=wo_id, tenant_no=tenant_no
|
||
)
|
||
return success_response(data)
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.post("/production-work-orders/{wo_id}/qc-work-orders")
|
||
async def create_qc_work_order_from_production_api(
|
||
wo_id: str,
|
||
body: ProductionQcWorkOrderCreateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
wo = qc_work_order_svc.create_qc_work_order_from_production(
|
||
db,
|
||
production_work_order_id=wo_id,
|
||
production_step_output_id=body.production_step_output_id,
|
||
remark=body.remark or "",
|
||
tenant_no=tenant_no,
|
||
user_id=user.get("user_id"),
|
||
create_dept_id=user.get("dept_id", ""),
|
||
)
|
||
db.commit()
|
||
return success_response(qc_work_order_svc._wo_to_dict(wo), 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.get("/production-task-sets")
|
||
async def list_production_task_sets(
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
|
||
keyword: Optional[str] = None,
|
||
status: Optional[str] = None,
|
||
user: dict = Depends(require_menu_code("production:task-set:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, total = production_task_set_svc.list_sets(
|
||
db, tenant_no=tenant_no, page=page, page_size=pageSize, keyword=keyword, status=status,
|
||
)
|
||
tp = production_task_set_svc.total_pages(total, pageSize)
|
||
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
@router.get("/production-task-sets/active")
|
||
async def list_active_production_task_sets(
|
||
user: dict = Depends(require_any_menu_code("production:task-set:list", "production:work-order:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items = production_task_set_svc.list_active_sets(db, tenant_no=tenant_no)
|
||
return success_response({"list": items})
|
||
|
||
|
||
@router.post("/production-task-sets/from-work-order")
|
||
async def create_production_task_set_from_work_order(
|
||
body: ProductionTaskSetFromWorkOrderBody,
|
||
user: dict = Depends(
|
||
require_any_menu_code("production:work-order:list", "production:task-set:add")
|
||
),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
detail = production_task_set_svc.create_set_from_work_order(
|
||
db,
|
||
tenant_no=tenant_no,
|
||
user_id=user.get("user_id"),
|
||
work_order_id=body.work_order_id,
|
||
set_name=body.set_name,
|
||
remark=body.remark,
|
||
items=(
|
||
[x.model_dump(by_alias=False) for x in body.items]
|
||
if body.items is not None
|
||
else None
|
||
),
|
||
)
|
||
db.commit()
|
||
return success_response(detail, message="保存成功")
|
||
except ValueError as e:
|
||
db.rollback()
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.get("/production-task-sets/{set_id}")
|
||
async def get_production_task_set(
|
||
set_id: str,
|
||
user: dict = Depends(require_any_menu_code("production:task-set:list", "production:work-order:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
detail = production_task_set_svc.get_set(db, set_id, tenant_no)
|
||
if not detail:
|
||
return error_response(404, "集合不存在")
|
||
return success_response(detail)
|
||
|
||
|
||
@router.post("/production-task-sets")
|
||
async def create_production_task_set(
|
||
body: ProductionTaskSetCreateBody,
|
||
user: dict = Depends(require_menu_code("production:task-set:add")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
detail = production_task_set_svc.create_set(
|
||
db,
|
||
tenant_no=tenant_no,
|
||
user_id=user.get("user_id"),
|
||
set_name=body.set_name,
|
||
status=body.status or "1",
|
||
remark=body.remark,
|
||
items=[x.model_dump(by_alias=False) for x in body.items],
|
||
)
|
||
db.commit()
|
||
return success_response(detail, message="创建成功")
|
||
except ValueError as e:
|
||
db.rollback()
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/production-task-sets/{set_id}")
|
||
async def update_production_task_set(
|
||
set_id: str,
|
||
body: ProductionTaskSetUpdateBody,
|
||
user: dict = Depends(require_menu_code("production:task-set:edit")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
production_task_set_svc.assert_set_writable(db, set_id, tenant_no, user)
|
||
data = body.model_dump(by_alias=False, exclude_unset=True)
|
||
items = None
|
||
if "items" in data:
|
||
items = [x.model_dump(by_alias=False) for x in (body.items or [])]
|
||
detail = production_task_set_svc.update_set(
|
||
db,
|
||
set_id,
|
||
tenant_no=tenant_no,
|
||
user_id=user.get("user_id"),
|
||
set_name=data.get("set_name"),
|
||
status=data.get("status"),
|
||
remark=data.get("remark"),
|
||
items=items,
|
||
)
|
||
db.commit()
|
||
return success_response(detail, message="更新成功")
|
||
except ValueError as e:
|
||
db.rollback()
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/production-task-sets/{set_id}")
|
||
async def delete_production_task_set(
|
||
set_id: str,
|
||
user: dict = Depends(require_menu_code("production:task-set:remove")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
production_task_set_svc.assert_set_writable(db, set_id, tenant_no, user)
|
||
production_task_set_svc.delete_set(
|
||
db, set_id, tenant_no=tenant_no, user_id=user.get("user_id")
|
||
)
|
||
db.commit()
|
||
return success_response(None, message="删除成功")
|
||
except ValueError as e:
|
||
db.rollback()
|
||
return error_response(400, str(e))
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 10. 生产任务
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
@router.post("/production-tasks")
|
||
async def create_production_task(
|
||
body: ProductionTaskCreate,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
data = body.model_dump(by_alias=False)
|
||
task = production_task_svc.create_production_task(
|
||
db, tenant_no=tenant_no, user_id=user.get("user_id"),
|
||
create_dept_id="", data=data,
|
||
)
|
||
db.commit()
|
||
result = production_task_svc._task_to_dict(task)
|
||
if data.get("route_id"):
|
||
production_task_svc._enrich_tasks_with_step_summaries(db, [result], tenant_no=tenant_no)
|
||
return success_response(result, message="创建成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.post("/production-tasks/from-set")
|
||
async def create_production_tasks_from_set(
|
||
body: ProductionTasksFromSetBody,
|
||
user: dict = Depends(require_menu_code("production:work-order:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
result = production_task_set_svc.create_tasks_from_set(
|
||
db,
|
||
tenant_no=tenant_no,
|
||
user_id=user.get("user_id"),
|
||
create_dept_id="",
|
||
work_order_id=body.work_order_id,
|
||
set_id=body.set_id,
|
||
items=(
|
||
[x.model_dump(by_alias=False) for x in body.items]
|
||
if body.items is not None
|
||
else None
|
||
),
|
||
)
|
||
db.commit()
|
||
return success_response(result, message="创建成功")
|
||
except ValueError as e:
|
||
db.rollback()
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.get("/production-tasks")
|
||
async def list_production_tasks(
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
|
||
keyword: Optional[str] = None,
|
||
workOrderId: Optional[str] = None,
|
||
taskNo: Optional[str] = None,
|
||
taskStatus: Optional[str] = None,
|
||
startedTimeStart: Optional[str] = Query(None, alias="startedTimeStart"),
|
||
startedTimeEnd: Optional[str] = Query(None, alias="startedTimeEnd"),
|
||
includeStepSummary: bool = Query(False, alias="includeStepSummary"),
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, total = production_task_svc.list_production_tasks(
|
||
db, tenant_no=tenant_no, page=page, page_size=pageSize,
|
||
keyword=keyword,
|
||
work_order_id=workOrderId, task_no=taskNo, task_status=taskStatus,
|
||
started_time_start=startedTimeStart,
|
||
started_time_end=startedTimeEnd,
|
||
user_info=user,
|
||
include_step_summary=includeStepSummary,
|
||
)
|
||
tp = production_task_svc.total_pages(total, pageSize)
|
||
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
@router.get("/production-waste-outputs")
|
||
async def list_production_waste_outputs(
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
|
||
keyword: Optional[str] = None,
|
||
createTimeStart: Optional[str] = Query(None, alias="createTimeStart"),
|
||
createTimeEnd: Optional[str] = Query(None, alias="createTimeEnd"),
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, total = production_waste_output_svc.list_production_waste_outputs(
|
||
db,
|
||
tenant_no=tenant_no,
|
||
page=page,
|
||
page_size=pageSize,
|
||
keyword=keyword,
|
||
create_time_start=createTimeStart,
|
||
create_time_end=createTimeEnd,
|
||
user_info=user,
|
||
)
|
||
tp = production_waste_output_svc.total_pages(total, pageSize)
|
||
return success_response(
|
||
{"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}
|
||
)
|
||
|
||
|
||
@router.post("/production-tasks/list-by-work-orders")
|
||
async def list_production_tasks_by_work_orders(
|
||
body: ProductionTaskListByWorkOrdersBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
wo_ids = [x.strip() for x in body.work_order_ids if x and str(x).strip()]
|
||
if not wo_ids:
|
||
return success_response(
|
||
{"list": [], "total": 0, "page": 1, "pageSize": body.page_size, "totalPages": 0}
|
||
)
|
||
effective_page_size = min(max(body.page_size, len(wo_ids) * 100), 500)
|
||
items, total = production_task_svc.list_production_tasks(
|
||
db,
|
||
tenant_no=tenant_no,
|
||
page=1,
|
||
page_size=effective_page_size,
|
||
work_order_ids=wo_ids,
|
||
user_info=user,
|
||
include_step_summary=body.include_step_summary,
|
||
)
|
||
tp = production_task_svc.total_pages(total, effective_page_size)
|
||
return success_response(
|
||
{
|
||
"list": items,
|
||
"total": total,
|
||
"page": 1,
|
||
"pageSize": effective_page_size,
|
||
"totalPages": tp,
|
||
}
|
||
)
|
||
|
||
|
||
@router.get("/production-tasks/mine")
|
||
async def list_my_production_tasks(
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
|
||
keyword: Optional[str] = None,
|
||
taskStatus: Optional[str] = None,
|
||
workOrderType: Optional[str] = None,
|
||
receptionSource: Optional[str] = Query(None, alias="receptionSource"),
|
||
bizType: Optional[str] = Query(None, alias="bizType"),
|
||
scope: str = Query("current", description="current|history"),
|
||
includeStepSummary: bool = Query(False, alias="includeStepSummary"),
|
||
excludeVoidProtocol: bool = Query(True, alias="excludeVoidProtocol"),
|
||
plannedStartTimeStart: Optional[str] = Query(None, alias="plannedStartTimeStart"),
|
||
plannedStartTimeEnd: Optional[str] = Query(None, alias="plannedStartTimeEnd"),
|
||
user: dict = Depends(require_menu_code("production:my-task:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, total = production_task_svc.list_mine_production_tasks(
|
||
db,
|
||
assignee_user_id=_get_current_user_id(user),
|
||
tenant_no=tenant_no,
|
||
scope=scope,
|
||
page=page,
|
||
page_size=pageSize,
|
||
keyword=keyword,
|
||
task_status=taskStatus,
|
||
work_order_type=workOrderType,
|
||
reception_source=receptionSource,
|
||
biz_type=bizType,
|
||
include_step_summary=includeStepSummary,
|
||
exclude_void_protocol=excludeVoidProtocol,
|
||
planned_start_time_start=plannedStartTimeStart,
|
||
planned_start_time_end=plannedStartTimeEnd,
|
||
)
|
||
tp = production_task_svc.total_pages(total, pageSize)
|
||
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
@router.post("/production-tasks/{task_id}/forward")
|
||
async def forward_production_task_api(
|
||
task_id: str,
|
||
body: ProductionTaskForwardBody,
|
||
operateScope: Optional[str] = Query(None, alias="operateScope"),
|
||
user: dict = Depends(require_any_menu_code("production:my-task:list", "production:work-order:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
scope = (operateScope or "mine").strip().lower()
|
||
require_assignee = scope == "mine"
|
||
from_uid = _get_current_user_id(user) if require_assignee else (user.get("user_id") or "")
|
||
task = production_task_svc.forward_production_task(
|
||
db,
|
||
task_id,
|
||
from_user_id=from_uid,
|
||
to_user_id=body.to_user_id,
|
||
tenant_no=tenant_no,
|
||
operator_id=user.get("user_id"),
|
||
create_dept_id=user.get("dept_id", ""),
|
||
remark=body.remark,
|
||
require_assignee=require_assignee,
|
||
)
|
||
db.commit()
|
||
item = production_task_svc._task_to_dict(task)
|
||
production_task_svc._enrich_assignee_names(db, [item])
|
||
return success_response(item, message="转发成功")
|
||
except PermissionError as e:
|
||
db.rollback()
|
||
return error_response(403, str(e))
|
||
except ValueError as e:
|
||
db.rollback()
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.post("/production-tasks/{task_id}/spawn-temporary-work-order")
|
||
async def spawn_temporary_work_order_from_task_api(
|
||
task_id: str,
|
||
body: SpawnTemporaryWorkOrderBody,
|
||
operateScope: Optional[str] = Query(None, alias="operateScope"),
|
||
user: dict = Depends(
|
||
require_any_menu_code("production:my-task:list", "production:work-order:list")
|
||
),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
result = production_task_svc.spawn_temporary_work_order_from_task(
|
||
db,
|
||
task_id,
|
||
tenant_no=tenant_no,
|
||
user_id=user.get("user_id"),
|
||
create_dept_id=user.get("dept_id", ""),
|
||
remark=body.remark or "",
|
||
)
|
||
db.commit()
|
||
return success_response(result, message="临时工单已创建")
|
||
except ValueError as e:
|
||
db.rollback()
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/production-tasks/{task_id}/assignee")
|
||
async def reassign_production_task_api(
|
||
task_id: str,
|
||
body: ProductionTaskAssigneeUpdate,
|
||
user: dict = Depends(require_menu_code("production:work-order:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
task = production_task_svc.reassign_production_task(
|
||
db,
|
||
task_id,
|
||
assignee_user_id=body.assignee_user_id,
|
||
tenant_no=tenant_no,
|
||
operator_id=user.get("user_id"),
|
||
create_dept_id=user.get("dept_id", ""),
|
||
remark=body.remark,
|
||
)
|
||
db.commit()
|
||
item = production_task_svc._task_to_dict(task)
|
||
production_task_svc._enrich_assignee_names(db, [item])
|
||
return success_response(item, message="改派成功")
|
||
except ValueError as e:
|
||
db.rollback()
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/production-tasks/{task_id}/steps/dispatch")
|
||
async def dispatch_production_task_steps_api(
|
||
task_id: str,
|
||
body: ProductionTaskStepDispatchBody,
|
||
operateScope: Optional[str] = Query(None, alias="operateScope"),
|
||
user: dict = Depends(require_any_menu_code("production:my-task:list", "production:work-order:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
payload = [
|
||
{
|
||
"step_id": it.step_id,
|
||
"assignee_user_ids": it.assignee_user_ids,
|
||
"planned_start_time": it.planned_start_time,
|
||
}
|
||
for it in body.steps
|
||
]
|
||
detail = production_task_svc.dispatch_task_steps(
|
||
db,
|
||
task_id,
|
||
tenant_no=tenant_no,
|
||
steps_payload=payload,
|
||
operator_id=user.get("user_id"),
|
||
create_dept_id=user.get("dept_id", ""),
|
||
)
|
||
db.commit()
|
||
return success_response(detail, message="步骤派发成功")
|
||
except ValueError as e:
|
||
db.rollback()
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.get("/production-tasks/{task_id}/editor-init")
|
||
async def get_production_task_editor_init(
|
||
task_id: str,
|
||
includeReference: bool = Query(True, alias="includeReference"),
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""编辑生产任务弹窗初始化:任务详情 + 工艺库 + 步骤配置参考数据(聚合接口)。"""
|
||
tenant_no = _get_tenant_no(db, user)
|
||
payload = production_task_svc.get_production_task_editor_init(
|
||
db,
|
||
task_id,
|
||
tenant_no,
|
||
user_info=user,
|
||
include_reference=includeReference,
|
||
)
|
||
if not payload:
|
||
return error_response(404, "任务不存在")
|
||
return success_response(payload)
|
||
|
||
|
||
@router.get("/production-tasks/{task_id}")
|
||
async def get_production_task_detail(
|
||
task_id: str,
|
||
includeStepSubTables: bool = Query(True, alias="includeStepSubTables"),
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
detail = production_task_svc.get_production_task_detail(
|
||
db,
|
||
task_id,
|
||
tenant_no,
|
||
include_step_sub_tables=includeStepSubTables,
|
||
)
|
||
if not detail:
|
||
return error_response(404, "任务不存在")
|
||
return success_response(detail)
|
||
|
||
|
||
@router.put("/production-tasks/{task_id}")
|
||
async def update_production_task(
|
||
task_id: str,
|
||
body: ProductionTaskUpdate,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
data = body.model_dump(exclude_unset=True, by_alias=False)
|
||
task = production_task_svc.update_production_task(
|
||
db, task_id, tenant_no, user.get("user_id"), data,
|
||
)
|
||
return success_response(production_task_svc._task_to_dict(task), message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/production-tasks/{task_id}")
|
||
async def delete_production_task(
|
||
task_id: str,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
production_task_svc.delete_production_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))
|
||
|
||
|
||
@router.put("/production-tasks/{task_id}/status")
|
||
async def update_production_task_status(
|
||
task_id: str,
|
||
body: ProductionTaskStatusUpdate,
|
||
operateScope: Optional[str] = Query(None, alias="operateScope"),
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
task = production_task_svc.update_task_status(
|
||
db,
|
||
task_id,
|
||
tenant_no,
|
||
user.get("user_id"),
|
||
body.task_status,
|
||
conclusion=body.conclusion,
|
||
remark=body.remark,
|
||
)
|
||
return success_response(production_task_svc._task_to_dict(task), message="操作成功")
|
||
except PermissionError as e:
|
||
return error_response(403, str(e))
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.post("/production-tasks/{task_id}/steps")
|
||
async def add_production_task_step(
|
||
task_id: str,
|
||
body: TaskStepAdd,
|
||
operateScope: Optional[str] = Query(None, alias="operateScope"),
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
data = body.model_dump(by_alias=False)
|
||
step_data = production_task_svc.add_task_step(
|
||
db, task_id, tenant_no, user.get("user_id"), data,
|
||
)
|
||
return success_response(step_data, message="添加成功")
|
||
except PermissionError as e:
|
||
return error_response(403, str(e))
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/production-tasks/{task_id}/steps/{step_id}")
|
||
async def update_production_task_step(
|
||
task_id: str,
|
||
step_id: str,
|
||
body: TaskStepUpdate,
|
||
operateScope: Optional[str] = Query(None, alias="operateScope"),
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
data = body.model_dump(exclude_unset=True, by_alias=False)
|
||
step = production_task_svc.update_task_step(
|
||
db, step_id, tenant_no, user.get("user_id"), data,
|
||
)
|
||
return success_response(production_task_svc._step_to_dict(step), message="更新成功")
|
||
except PermissionError as e:
|
||
return error_response(403, str(e))
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/production-tasks/{task_id}/steps/{step_id}")
|
||
async def delete_production_task_step(
|
||
task_id: str,
|
||
step_id: str,
|
||
operateScope: Optional[str] = Query(None, alias="operateScope"),
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
production_task_svc.delete_task_step(db, step_id, tenant_no, user.get("user_id"))
|
||
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))
|
||
|
||
|
||
@router.put("/production-tasks/step/reorder")
|
||
async def reorder_production_task_steps(
|
||
body: TaskStepReorderBody,
|
||
operateScope: Optional[str] = Query(None, alias="operateScope"),
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
production_task_svc.reorder_task_steps(db, body.task_id, body.step_ids)
|
||
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))
|
||
|
||
|
||
@router.post("/production-tasks/{task_id}/steps/{step_id}/copy")
|
||
async def copy_production_task_step(
|
||
task_id: str,
|
||
step_id: str,
|
||
operateScope: Optional[str] = Query(None, alias="operateScope"),
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
step_data = production_task_svc.copy_task_step(
|
||
db,
|
||
step_id=step_id,
|
||
tenant_no=tenant_no,
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(step_data, message="复制成功")
|
||
except PermissionError as e:
|
||
return error_response(403, str(e))
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/production-tasks/{task_id}/steps/{step_id}/reagents")
|
||
async def replace_task_step_reagents(
|
||
task_id: str,
|
||
step_id: str,
|
||
body: List[TaskStepMaterialItem],
|
||
operateScope: Optional[str] = Query(None, alias="operateScope"),
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items_data = []
|
||
for it in body:
|
||
d = it.model_dump(by_alias=False)
|
||
items_data.append({
|
||
"id": d.get("id"),
|
||
"reagent_code": d.get("material_code"),
|
||
"reagent_name": d.get("material_name"),
|
||
"spec": d.get("spec"),
|
||
"standard_usage": d.get("standard_usage"),
|
||
"actual_usage": d.get("actual_usage"),
|
||
"unit": d.get("unit"),
|
||
"requisition_usage_id": d.get("requisition_usage_id"),
|
||
"is_key_material": d.get("is_key_material", "0"),
|
||
"remark": d.get("remark"),
|
||
})
|
||
production_task_svc.replace_step_reagents(db, step_id, tenant_no, user.get("user_id"), items_data)
|
||
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))
|
||
|
||
|
||
@router.put("/production-tasks/{task_id}/steps/{step_id}/consumables")
|
||
async def replace_task_step_consumables(
|
||
task_id: str,
|
||
step_id: str,
|
||
body: List[TaskStepMaterialItem],
|
||
operateScope: Optional[str] = Query(None, alias="operateScope"),
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items_data = []
|
||
for it in body:
|
||
d = it.model_dump(by_alias=False)
|
||
items_data.append({
|
||
"id": d.get("id"),
|
||
"consumable_code": d.get("material_code"),
|
||
"consumable_name": d.get("material_name"),
|
||
"spec_model": d.get("spec_model"),
|
||
"standard_usage": d.get("standard_usage"),
|
||
"actual_usage": d.get("actual_usage"),
|
||
"unit": d.get("unit"),
|
||
"requisition_usage_id": d.get("requisition_usage_id"),
|
||
"is_required": d.get("is_required", "1"),
|
||
"remark": d.get("remark"),
|
||
})
|
||
production_task_svc.replace_step_consumables(db, step_id, tenant_no, user.get("user_id"), items_data)
|
||
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))
|
||
|
||
|
||
@router.put("/production-tasks/{task_id}/steps/{step_id}/configured-reagents")
|
||
async def replace_task_step_configured_reagents(
|
||
task_id: str,
|
||
step_id: str,
|
||
body: List[TaskStepMaterialItem],
|
||
operateScope: Optional[str] = Query(None, alias="operateScope"),
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items_data = []
|
||
for it in body:
|
||
d = it.model_dump(by_alias=False)
|
||
items_data.append({
|
||
"id": d.get("id"),
|
||
"configured_reagent_code": d.get("material_code"),
|
||
"configured_reagent_name": d.get("material_name"),
|
||
"spec": d.get("spec"),
|
||
"standard_usage": d.get("standard_usage"),
|
||
"actual_usage": d.get("actual_usage"),
|
||
"unit": d.get("unit"),
|
||
"requisition_usage_id": d.get("requisition_usage_id"),
|
||
"remark": d.get("remark"),
|
||
})
|
||
production_task_svc.replace_step_configured_reagents(
|
||
db, step_id, tenant_no, user.get("user_id"), items_data
|
||
)
|
||
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))
|
||
|
||
|
||
@router.put("/production-tasks/{task_id}/steps/{step_id}/data-collection")
|
||
async def replace_task_step_data_collections(
|
||
task_id: str,
|
||
step_id: str,
|
||
body: List[TaskStepDataCollectionItem],
|
||
operateScope: Optional[str] = Query(None, alias="operateScope"),
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items_data = [it.model_dump(by_alias=False) for it in body]
|
||
production_task_svc.replace_step_data_collections(db, step_id, tenant_no, user.get("user_id"), items_data)
|
||
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))
|
||
|
||
|
||
@router.put("/production-tasks/{task_id}/steps/{step_id}/outputs")
|
||
async def replace_task_step_outputs(
|
||
task_id: str,
|
||
step_id: str,
|
||
body: List[TaskStepOutputItem],
|
||
operateScope: Optional[str] = Query(None, alias="operateScope"),
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items_data = [it.model_dump(by_alias=False) for it in body]
|
||
saved = production_task_svc.replace_step_outputs(
|
||
db, step_id, tenant_no, user.get("user_id"), items_data
|
||
)
|
||
return success_response(saved, message="更新成功")
|
||
except PermissionError as e:
|
||
return error_response(403, str(e))
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.post("/production-tasks/{task_id}/inputs")
|
||
async def set_production_task_inputs(
|
||
task_id: str,
|
||
body: List[TaskInputItem],
|
||
operateScope: Optional[str] = Query(None, alias="operateScope"),
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items_data = [it.model_dump(by_alias=False) for it in body]
|
||
production_task_svc.set_task_inputs(db, task_id, tenant_no, user.get("user_id"), items_data)
|
||
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))
|
||
|
||
|
||
@router.post("/production-tasks/{task_id}/outputs")
|
||
async def set_production_task_outputs(
|
||
task_id: str,
|
||
body: List[TaskOutputItem],
|
||
operateScope: Optional[str] = Query(None, alias="operateScope"),
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items_data = [it.model_dump(by_alias=False) for it in body]
|
||
production_task_svc.set_task_outputs(db, task_id, tenant_no, user.get("user_id"), items_data)
|
||
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))
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 11. 细胞产品库
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
@router.get("/cell-products/next-code")
|
||
async def get_next_cell_product_code(
|
||
count: int = Query(1, ge=1, le=50),
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
if count <= 1:
|
||
code = cell_product_svc.get_next_code(db, tenant_no)
|
||
return success_response({"code": code})
|
||
codes = cell_product_svc.reserve_next_codes(db, tenant_no, count)
|
||
return success_response({"codes": codes, "code": codes[0] if codes else ""})
|
||
|
||
|
||
@router.get("/cell-products/max-passage-seq")
|
||
async def get_cell_product_max_passage_seq(
|
||
productCode: str = Query(..., alias="productCode"),
|
||
passage: Optional[str] = None,
|
||
passages: Optional[str] = None,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""查询 `{productCode}-{代次}-XX` 在细胞产品库中的最大两位序号。"""
|
||
tenant_no = _get_tenant_no(db, user)
|
||
base = (productCode or "").strip()
|
||
if not base:
|
||
return error_response(400, "产品编码不能为空")
|
||
passage_list: List[str] = []
|
||
if passages:
|
||
passage_list.extend(p.strip() for p in passages.split(",") if p and p.strip())
|
||
if passage and passage.strip():
|
||
passage_list.append(passage.strip())
|
||
if not passage_list:
|
||
return error_response(400, "请指定代次")
|
||
max_seq_map = cell_product_svc.get_max_passage_product_seq_map(
|
||
db, tenant_no, base, passage_list
|
||
)
|
||
next_seq_map = {p: max_seq_map.get(p, 0) + 1 for p in max_seq_map}
|
||
if len(max_seq_map) == 1:
|
||
only_passage = next(iter(max_seq_map))
|
||
only_max = max_seq_map[only_passage]
|
||
only_next = next_seq_map[only_passage]
|
||
return success_response(
|
||
{
|
||
"passage": only_passage,
|
||
"maxSeq": only_max,
|
||
"nextSeq": only_next,
|
||
"maxSeqByPassage": max_seq_map,
|
||
"nextSeqByPassage": next_seq_map,
|
||
}
|
||
)
|
||
return success_response(
|
||
{"maxSeqByPassage": max_seq_map, "nextSeqByPassage": next_seq_map}
|
||
)
|
||
|
||
|
||
@router.get("/cell-products/max-suffix-seq")
|
||
async def get_cell_product_max_suffix_seq(
|
||
productCode: str = Query(..., alias="productCode"),
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""查询 `{productCode}-XX` 在细胞产品库中的最大两位序号。"""
|
||
tenant_no = _get_tenant_no(db, user)
|
||
base = (productCode or "").strip()
|
||
if not base:
|
||
return error_response(400, "产品编码不能为空")
|
||
max_seq = cell_product_svc.get_max_product_suffix_seq(db, tenant_no, base)
|
||
return success_response({"maxSeq": max_seq, "nextSeq": max_seq + 1})
|
||
|
||
|
||
@router.get("/cell-products/check-code")
|
||
async def check_cell_product_code(
|
||
productCode: str = Query(..., alias="productCode"),
|
||
excludeId: Optional[str] = Query(None, alias="excludeId"),
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""检查产品编码是否已被占用(编辑时可排除当前记录)。"""
|
||
tenant_no = _get_tenant_no(db, user)
|
||
code = (productCode or "").strip()
|
||
if not code:
|
||
return error_response(400, "产品编码不能为空")
|
||
exists = cell_product_svc.product_code_exists(
|
||
db, tenant_no, code, exclude_id=excludeId
|
||
)
|
||
return success_response({"exists": exists, "productCode": code})
|
||
|
||
|
||
@router.get("/cell-products")
|
||
async def list_cell_products(
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
|
||
keyword: Optional[str] = None,
|
||
productCode: Optional[str] = None,
|
||
productName: Optional[str] = None,
|
||
productTypeId: Optional[str] = None,
|
||
productStatus: Optional[str] = None,
|
||
passage: Optional[str] = None,
|
||
sourceType: Optional[str] = None,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
result = cell_product_svc.list_cell_products(
|
||
db, tenant_no=tenant_no, page=page, page_size=pageSize,
|
||
keyword=keyword,
|
||
product_code=productCode, product_name=productName,
|
||
product_type_id=productTypeId, product_status=productStatus,
|
||
passage=passage, source_type=sourceType,
|
||
user_info=user,
|
||
)
|
||
items = result.get("items", [])
|
||
total = result.get("total", 0)
|
||
tp = cell_product_svc.total_pages(total, pageSize)
|
||
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
@router.get("/cell-products/{product_id}")
|
||
async def get_cell_product_detail(
|
||
product_id: str,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
obj = cell_product_svc.get_cell_product(db, product_id, tenant_no)
|
||
if not obj:
|
||
return error_response(404, "细胞产品不存在")
|
||
return success_response(cell_product_svc.product_to_response(db, tenant_no, obj))
|
||
|
||
|
||
@router.get("/cell-products/{product_id}/chain")
|
||
async def get_cell_product_chain(
|
||
product_id: str,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
chain = cell_product_svc.get_product_chain(db, product_id, tenant_no)
|
||
if not chain:
|
||
raise HTTPException(status_code=404, detail="细胞产品不存在")
|
||
return success_response(chain)
|
||
|
||
|
||
@router.get("/cell-products/{product_id}/lineage-qc")
|
||
async def get_cell_product_lineage_qc(
|
||
product_id: str,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
data = cell_product_svc.get_product_lineage_qc(db, product_id, tenant_no)
|
||
if not data:
|
||
raise HTTPException(status_code=404, detail="细胞产品不存在")
|
||
return success_response(data)
|
||
|
||
|
||
@router.post("/cell-products")
|
||
async def create_cell_product(
|
||
body: CellProductCreate,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items = body.items or []
|
||
qty = int(body.quantity) if body.quantity is not None else 1
|
||
qty = max(1, min(qty, 50))
|
||
if items:
|
||
created = cell_product_svc.create_cell_products_batch(
|
||
db,
|
||
tenant_no=tenant_no,
|
||
user_id=user.get("user_id"),
|
||
create_dept_id="",
|
||
data=body,
|
||
items=items,
|
||
require_storage_slot=False,
|
||
)
|
||
elif qty > 1:
|
||
created = cell_product_svc.create_cell_products_by_quantity(
|
||
db,
|
||
tenant_no=tenant_no,
|
||
user_id=user.get("user_id"),
|
||
create_dept_id="",
|
||
data=body,
|
||
count=qty,
|
||
)
|
||
else:
|
||
obj = cell_product_svc.create_cell_product(
|
||
db, tenant_no=tenant_no, user_id=user.get("user_id"),
|
||
create_dept_id="", data=body,
|
||
)
|
||
return success_response(
|
||
cell_product_svc.product_to_response(db, tenant_no, obj), message="创建成功"
|
||
)
|
||
resp_list = [
|
||
cell_product_svc.product_to_response(db, tenant_no, obj) for obj in created
|
||
]
|
||
n = len(created)
|
||
return success_response(
|
||
{"list": resp_list, "count": n},
|
||
message=f"成功创建 {n} 份细胞产品",
|
||
)
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/cell-products/{product_id}")
|
||
async def update_cell_product(
|
||
product_id: str,
|
||
body: CellProductUpdate,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
obj = cell_product_svc.update_cell_product(
|
||
db, product_id, tenant_no, user.get("user_id"), data=body,
|
||
)
|
||
return success_response(
|
||
cell_product_svc.product_to_response(db, tenant_no, obj), message="更新成功"
|
||
)
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/cell-products/{product_id}")
|
||
async def delete_cell_product(
|
||
product_id: str,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
cell_product_svc.delete_cell_product(db, product_id, tenant_no, user.get("user_id"))
|
||
return success_response(None, message="删除成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
from app.api.production_material import router as production_material_router
|
||
|
||
router.include_router(production_material_router)
|