feat(crud): add production work order CRUD operations

This commit is contained in:
zlwu committed 2026-05-20 00:10:35 +08:00
1 parent 026b040db5
commit 9d9f43579d
1 file changed
+266
+266
View File
@@ -0,0 +1,266 @@
"""入库生产工单 CRUD"""
from __future__ import annotations
from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple
from sqlalchemy import func, or_
from sqlalchemy.orm import Session
from app.core.utils import generate_uuid
from app.models.models import (
BizCollectionProtocol,
BizProductionWorkOrder,
BizProtocolSample,
BizSampleReception,
)
from app.models.research_donation import BizResearchDonation, BizResearchDonationSample
from app.schemas.production_work_order import ProductionWorkOrderComplete, ProductionWorkOrderListQuery
_WORK_ORDER_STATUS_LABELS = {
"dpz": "待配置",
"ypz": "已配置",
"jxz": "执行中",
"ywc": "已完成",
"yzf": "已作废",
}
_RECEPTION_SOURCE_LABELS = {
"cell_storage": "细胞存储",
"research_donation": "科研捐赠",
}
def _fmt_dt(t: Optional[datetime]) -> str:
return t.strftime("%Y-%m-%d %H:%M:%S") if t else ""
def _generate_work_order_no(db: Session, tenant_no: str) -> str:
today_str = datetime.now().strftime("%Y%m%d")
prefix = f"SC{today_str}"
last = (
db.query(BizProductionWorkOrder)
.filter(
BizProductionWorkOrder.tenant_no == tenant_no,
BizProductionWorkOrder.work_order_no.like(f"{prefix}%"),
BizProductionWorkOrder.del_flag == 0,
)
.order_by(BizProductionWorkOrder.work_order_no.desc())
.first()
)
seq = 1
if last:
seq = int(last.work_order_no[-4:]) + 1
return f"{prefix}{seq:04d}"
def _wo_to_dict(wo: BizProductionWorkOrder) -> Dict[str, Any]:
return {
"id": wo.id,
"workOrderNo": wo.work_order_no,
"receptionId": wo.reception_id,
"protocolSampleId": wo.protocol_sample_id,
"collectionProtocolId": wo.collection_protocol_id,
"protocolNo": wo.protocol_no or "",
"sampleCode": wo.sample_code or "",
"receptionSource": getattr(wo, "reception_source", "cell_storage") or "cell_storage",
"receptionSourceLabel": _RECEPTION_SOURCE_LABELS.get(
getattr(wo, "reception_source", "cell_storage") or "cell_storage",
getattr(wo, "reception_source", "cell_storage") or "cell_storage",
),
"workOrderStatus": wo.work_order_status,
"workOrderStatusLabel": _WORK_ORDER_STATUS_LABELS.get(wo.work_order_status, wo.work_order_status),
"remark": wo.remark or "",
"createTime": _fmt_dt(wo.create_time),
"updateTime": _fmt_dt(wo.update_time),
}
def create_production_work_order_from_reception(
db: Session,
reception: BizSampleReception,
tenant_no: str,
user_id: Optional[str] = None,
create_dept_id: str = "",
) -> Optional[BizProductionWorkOrder]:
"""接收结论为 js 时创建待配置入库生产工单(flush,不 commit)。"""
if (reception.accept_conclusion or "").strip() != "js":
return None
exists = (
db.query(BizProductionWorkOrder)
.filter(
BizProductionWorkOrder.reception_id == reception.id,
BizProductionWorkOrder.tenant_no == tenant_no,
BizProductionWorkOrder.del_flag == 0,
)
.first()
)
if exists:
return exists
source = getattr(reception, "reception_source", "cell_storage") or "cell_storage"
if source == "research_donation":
protocol_no = ""
d = (
db.query(BizResearchDonation)
.filter(
BizResearchDonation.id == (reception.research_donation_id or ""),
BizResearchDonation.del_flag == 0,
)
.first()
)
if d:
protocol_no = d.donation_no or ""
sample_code = ""
ds = (
db.query(BizResearchDonationSample)
.filter(
BizResearchDonationSample.id == (reception.donation_sample_id or ""),
BizResearchDonationSample.del_flag == 0,
)
.first()
)
if ds:
sample_code = ds.sample_code or ""
wo = BizProductionWorkOrder(
id=generate_uuid(),
work_order_no=_generate_work_order_no(db, tenant_no),
reception_id=reception.id,
protocol_sample_id=reception.donation_sample_id or "",
collection_protocol_id=reception.research_donation_id or "",
protocol_no=protocol_no or None,
sample_code=sample_code or None,
reception_source="research_donation",
work_order_status="dpz",
tenant_no=tenant_no,
create_by=user_id,
create_dept_id=create_dept_id or "",
update_by=user_id,
create_time=datetime.now(),
update_time=datetime.now(),
del_flag=0,
)
else:
protocol_no = ""
p = (
db.query(BizCollectionProtocol)
.filter(
BizCollectionProtocol.id == reception.collection_protocol_id,
BizCollectionProtocol.del_flag == 0,
)
.first()
)
if p:
protocol_no = p.protocol_no or ""
sample_code = ""
s = (
db.query(BizProtocolSample)
.filter(
BizProtocolSample.id == reception.protocol_sample_id,
BizProtocolSample.del_flag == 0,
)
.first()
)
if s:
sample_code = s.sample_code or ""
wo = BizProductionWorkOrder(
id=generate_uuid(),
work_order_no=_generate_work_order_no(db, tenant_no),
reception_id=reception.id,
protocol_sample_id=reception.protocol_sample_id,
collection_protocol_id=reception.collection_protocol_id,
protocol_no=protocol_no or None,
sample_code=sample_code or None,
reception_source="cell_storage",
work_order_status="dpz",
tenant_no=tenant_no,
create_by=user_id,
create_dept_id=create_dept_id or "",
update_by=user_id,
create_time=datetime.now(),
update_time=datetime.now(),
del_flag=0,
)
db.add(wo)
db.flush()
return wo
def list_production_work_orders(
db: Session,
*,
tenant_no: str,
query: Optional[ProductionWorkOrderListQuery] = None,
page: int = 1,
page_size: int = 20,
) -> Dict[str, Any]:
def _filtered():
q0 = db.query(BizProductionWorkOrder).filter(BizProductionWorkOrder.del_flag == 0)
q0 = q0.filter(BizProductionWorkOrder.tenant_no == tenant_no)
if not query:
return q0
if query.work_order_no and query.work_order_no.strip():
like = f"%{query.work_order_no.strip()}%"
q0 = q0.filter(BizProductionWorkOrder.work_order_no.like(like))
if query.work_order_status and query.work_order_status.strip():
q0 = q0.filter(BizProductionWorkOrder.work_order_status == query.work_order_status.strip())
if query.protocol_no and query.protocol_no.strip():
like = f"%{query.protocol_no.strip()}%"
q0 = q0.filter(BizProductionWorkOrder.protocol_no.like(like))
if query.sample_code and query.sample_code.strip():
like = f"%{query.sample_code.strip()}%"
q0 = q0.filter(BizProductionWorkOrder.sample_code.like(like))
return q0
effective_page = query.page if query and query.page else page
effective_size = query.page_size if query and query.page_size else page_size
total = _filtered().order_by(None).with_entities(func.count(BizProductionWorkOrder.id)).scalar() or 0
effective_page = max(1, effective_page)
effective_size = min(max(1, effective_size), 100)
rows = (
_filtered()
.order_by(BizProductionWorkOrder.create_time.desc())
.offset((effective_page - 1) * effective_size)
.limit(effective_size)
.all()
)
items = [_wo_to_dict(x) for x in rows]
return {"items": items, "total": int(total)}
def get_production_work_order(
db: Session, row_id: str, tenant_no: str
) -> Optional[BizProductionWorkOrder]:
return (
db.query(BizProductionWorkOrder)
.filter(
BizProductionWorkOrder.id == row_id,
BizProductionWorkOrder.tenant_no == tenant_no,
BizProductionWorkOrder.del_flag == 0,
)
.first()
)
def complete_production_work_order(
db: Session,
row_id: str,
tenant_no: str,
user_id: str,
data: Optional[ProductionWorkOrderComplete] = None,
) -> BizProductionWorkOrder:
wo = get_production_work_order(db, row_id, tenant_no)
if not wo:
raise ValueError("工单不存在")
if wo.work_order_status == "ywc":
raise ValueError("工单已完成")
wo.work_order_status = "ywc"
wo.update_by = user_id
wo.update_time = datetime.now()
if data and data.remark is not None:
wo.remark = data.remark
db.add(wo)
db.commit()
db.refresh(wo)
return wo