feat(backend): expose sample freight APIs and wire protocol saves
Register admin sample-freight routes, enrich protocol sample details with freight, and stop writing legacy transport rows on save. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
6d542a0acf
commit
468a7c5e12
5 files changed
+385
-15
No files matched your search
@@ -8,6 +8,7 @@ from app.api.collection_protocol import router as collection_protocol_router
|
||||
from app.api.protocol_sample_payment import router as protocol_sample_payment_router
|
||||
from app.api.protocol_refund_record import router as protocol_refund_record_router
|
||||
from app.api.presign_order import router as presign_order_router
|
||||
from app.api.sample_freight import router as sample_freight_router
|
||||
from app.api.leave import router as leave_router
|
||||
from app.api.operations import router as operations_router
|
||||
from app.api.ethnic import router as ethnic_router
|
||||
@@ -50,6 +51,7 @@ __all__ = [
|
||||
"protocol_sample_payment_router",
|
||||
"protocol_refund_record_router",
|
||||
"presign_order_router",
|
||||
"sample_freight_router",
|
||||
"leave_router",
|
||||
"operations_router",
|
||||
"ethnic_router",
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
"""样本运费批次 API"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.menu_guard import require_admin_or_super_admin
|
||||
from app.core.response import error_response, success_response
|
||||
from app.crud import sample_freight as freight_svc
|
||||
from app.models.models import SysUser
|
||||
|
||||
router = APIRouter(prefix="/web/admin", tags=["样本运费批次"])
|
||||
|
||||
|
||||
def _get_tenant_no(db: Session, user: dict) -> str:
|
||||
user_id = user.get("user_id")
|
||||
if not user_id:
|
||||
raise PermissionError("用户身份信息缺失,无法确定租户")
|
||||
sys_user = db.query(SysUser).filter(SysUser.id == user_id).first()
|
||||
if not sys_user or not sys_user.tenant_no:
|
||||
raise PermissionError("用户租户信息缺失")
|
||||
return sys_user.tenant_no
|
||||
|
||||
|
||||
def _get_current_user_id(user: dict) -> str:
|
||||
user_id = user.get("user_id")
|
||||
if not user_id:
|
||||
raise PermissionError("用户身份信息缺失")
|
||||
return str(user_id)
|
||||
|
||||
|
||||
class SampleFreightItemBody(BaseModel):
|
||||
model_config = ConfigDict(extra="ignore", populate_by_name=True)
|
||||
|
||||
sample_source: str = Field(..., alias="sampleSource")
|
||||
sample_id: str = Field(..., alias="sampleId")
|
||||
protocol_id: Optional[str] = Field(None, alias="protocolId")
|
||||
sample_code: Optional[str] = Field(None, alias="sampleCode")
|
||||
|
||||
|
||||
class SampleFreightCreateBody(BaseModel):
|
||||
model_config = ConfigDict(extra="ignore", populate_by_name=True)
|
||||
|
||||
transport_mode: str = Field(..., alias="transportMode")
|
||||
freight_amount: float = Field(..., alias="freightAmount")
|
||||
items: Optional[List[SampleFreightItemBody]] = None
|
||||
|
||||
|
||||
class SampleFreightUpdateBody(BaseModel):
|
||||
model_config = ConfigDict(extra="ignore", populate_by_name=True)
|
||||
|
||||
transport_mode: Optional[str] = Field(None, alias="transportMode")
|
||||
freight_amount: Optional[float] = Field(None, alias="freightAmount")
|
||||
|
||||
|
||||
class SampleFreightItemsBody(BaseModel):
|
||||
model_config = ConfigDict(extra="ignore", populate_by_name=True)
|
||||
|
||||
items: List[SampleFreightItemBody] = Field(default_factory=list)
|
||||
|
||||
|
||||
@router.get("/sample-freights")
|
||||
async def list_sample_freights(
|
||||
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),
|
||||
):
|
||||
try:
|
||||
tenant_no = _get_tenant_no(db, user)
|
||||
items, total = freight_svc.list_freights(
|
||||
db,
|
||||
tenant_no=tenant_no,
|
||||
page=page,
|
||||
page_size=pageSize,
|
||||
keyword=keyword,
|
||||
)
|
||||
return success_response(
|
||||
{"list": items, "total": total, "page": page, "pageSize": pageSize}
|
||||
)
|
||||
except PermissionError as e:
|
||||
return error_response(403, str(e))
|
||||
|
||||
|
||||
@router.get("/sample-freights/{freight_id}")
|
||||
async def get_sample_freight(
|
||||
freight_id: str,
|
||||
user: dict = Depends(require_admin_or_super_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
tenant_no = _get_tenant_no(db, user)
|
||||
data = freight_svc.get_freight_detail(db, freight_id, tenant_no)
|
||||
return success_response(data)
|
||||
except PermissionError as e:
|
||||
return error_response(403, str(e))
|
||||
except ValueError as e:
|
||||
return error_response(404, str(e))
|
||||
|
||||
|
||||
@router.post("/sample-freights")
|
||||
async def create_sample_freight(
|
||||
body: SampleFreightCreateBody,
|
||||
user: dict = Depends(require_admin_or_super_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
tenant_no = _get_tenant_no(db, user)
|
||||
user_id = _get_current_user_id(user)
|
||||
items = [it.model_dump(by_alias=True) for it in (body.items or [])]
|
||||
data = freight_svc.create_freight(
|
||||
db,
|
||||
tenant_no=tenant_no,
|
||||
user_id=user_id,
|
||||
transport_mode_raw=body.transport_mode,
|
||||
freight_amount=body.freight_amount,
|
||||
items=items,
|
||||
)
|
||||
return success_response(data, message="创建成功")
|
||||
except PermissionError as e:
|
||||
return error_response(403, str(e))
|
||||
except ValueError as e:
|
||||
return error_response(400, str(e))
|
||||
|
||||
|
||||
@router.put("/sample-freights/{freight_id}")
|
||||
async def update_sample_freight(
|
||||
freight_id: str,
|
||||
body: SampleFreightUpdateBody,
|
||||
user: dict = Depends(require_admin_or_super_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
tenant_no = _get_tenant_no(db, user)
|
||||
user_id = _get_current_user_id(user)
|
||||
data = freight_svc.update_freight(
|
||||
db,
|
||||
freight_id,
|
||||
tenant_no=tenant_no,
|
||||
user_id=user_id,
|
||||
transport_mode_raw=body.transport_mode,
|
||||
freight_amount=body.freight_amount,
|
||||
)
|
||||
return success_response(data, message="更新成功")
|
||||
except PermissionError as e:
|
||||
return error_response(403, str(e))
|
||||
except ValueError as e:
|
||||
msg = str(e)
|
||||
code = 404 if "不存在" in msg else 400
|
||||
return error_response(code, msg)
|
||||
|
||||
|
||||
@router.post("/sample-freights/{freight_id}/link")
|
||||
async def link_sample_freight(
|
||||
freight_id: str,
|
||||
body: SampleFreightItemsBody,
|
||||
user: dict = Depends(require_admin_or_super_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
tenant_no = _get_tenant_no(db, user)
|
||||
user_id = _get_current_user_id(user)
|
||||
items = [it.model_dump(by_alias=True) for it in (body.items or [])]
|
||||
data = freight_svc.link_samples(
|
||||
db,
|
||||
freight_id,
|
||||
tenant_no=tenant_no,
|
||||
user_id=user_id,
|
||||
items=items,
|
||||
)
|
||||
return success_response(data, message="关联成功")
|
||||
except PermissionError as e:
|
||||
return error_response(403, str(e))
|
||||
except ValueError as e:
|
||||
msg = str(e)
|
||||
code = 404 if "不存在" in msg else 400
|
||||
return error_response(code, msg)
|
||||
|
||||
|
||||
@router.post("/sample-freights/{freight_id}/unlink")
|
||||
async def unlink_sample_freight(
|
||||
freight_id: str,
|
||||
body: SampleFreightItemsBody,
|
||||
user: dict = Depends(require_admin_or_super_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
tenant_no = _get_tenant_no(db, user)
|
||||
user_id = _get_current_user_id(user)
|
||||
items = [it.model_dump(by_alias=True) for it in (body.items or [])]
|
||||
data = freight_svc.unlink_samples(
|
||||
db,
|
||||
freight_id,
|
||||
tenant_no=tenant_no,
|
||||
user_id=user_id,
|
||||
items=items,
|
||||
)
|
||||
return success_response(data, message="已取消关联")
|
||||
except PermissionError as e:
|
||||
return error_response(403, str(e))
|
||||
except ValueError as e:
|
||||
msg = str(e)
|
||||
code = 404 if "不存在" in msg else 400
|
||||
return error_response(code, msg)
|
||||
@@ -607,12 +607,17 @@ def _transport_to_dict(t: BizProtocolSampleTransport) -> Dict[str, Any]:
|
||||
|
||||
|
||||
def _storage_cert_to_dict(s: BizProtocolStorageCert) -> Dict[str, Any]:
|
||||
from app.services.sample_info_progress import storage_cert_handled
|
||||
|
||||
return {
|
||||
"id": s.id,
|
||||
"collectionProtocolId": s.collection_protocol_id,
|
||||
"makeStatus": s.make_status or "0",
|
||||
"certImageUrls": s.cert_image_urls or "",
|
||||
"makeDate": (s.make_date or "").strip() if s.make_date else "",
|
||||
"handleTime": _fmt_dt(s.handle_time) if getattr(s, "handle_time", None) else "",
|
||||
"handlerUserId": getattr(s, "handler_user_id", None) or "",
|
||||
"handled": storage_cert_handled(s),
|
||||
"tenantNo": s.tenant_no,
|
||||
"orderNum": s.order_num if s.order_num is not None else 0,
|
||||
"remark": s.remark or "",
|
||||
@@ -1348,12 +1353,21 @@ def get_protocol_detail_bundle(
|
||||
)
|
||||
unit_name_map = _load_unit_name_map(db, s_rows)
|
||||
type_name_map = _load_sample_type_name_map(db, s_rows)
|
||||
from app.crud import sample_freight as sample_freight_svc
|
||||
|
||||
freight_by_sample = sample_freight_svc.freight_map_for_samples(
|
||||
db,
|
||||
tenant_no=tenant_no,
|
||||
sample_source=sample_freight_svc.SAMPLE_SOURCE_CELL,
|
||||
sample_ids=[s.id for s in s_rows],
|
||||
)
|
||||
samples_out: List[Dict[str, Any]] = []
|
||||
for s in s_rows:
|
||||
item = _sample_to_dict(s)
|
||||
item["sampleTypeName"] = type_name_map.get(s.sample_type_id or "", "")
|
||||
item["unitName"] = unit_name_map.get(s.unit_id or "", "")
|
||||
item["transport"] = transport_by_sample.get(s.id)
|
||||
item["freight"] = freight_by_sample.get(s.id)
|
||||
item["sampleReception"] = reception_by_sample.get(s.id)
|
||||
item["productionReception"] = (reception_by_sample.get(s.id) or {}).get(
|
||||
"productionReception"
|
||||
@@ -1365,9 +1379,22 @@ def get_protocol_detail_bundle(
|
||||
samples_out.append(item)
|
||||
out["samples"] = samples_out
|
||||
out["storageCert"] = _storage_cert_to_dict(sc) if sc else None
|
||||
if out["storageCert"]:
|
||||
hid = (out["storageCert"].get("handlerUserId") or "").strip()
|
||||
if hid:
|
||||
hu = db.query(SysUser).filter(SysUser.id == hid).first()
|
||||
out["storageCert"]["handlerUserName"] = (hu.nick_name or "") if hu else ""
|
||||
else:
|
||||
out["storageCert"]["handlerUserName"] = ""
|
||||
ft = get_fullmoon_todo(db, protocol_id, tenant_no)
|
||||
delivery_dt = pc.newborn_delivery_time if pc else None
|
||||
out["fullmoonTodo"] = _fullmoon_todo_detail_dict(ft, newborn_delivery_time=delivery_dt)
|
||||
hid2 = (out["fullmoonTodo"].get("handlerUserId") or "").strip()
|
||||
if hid2:
|
||||
hu2 = db.query(SysUser).filter(SysUser.id == hid2).first()
|
||||
out["fullmoonTodo"]["handlerUserName"] = (hu2.nick_name or "") if hu2 else ""
|
||||
else:
|
||||
out["fullmoonTodo"]["handlerUserName"] = ""
|
||||
out["attachments"] = urls_to_attachment_list(p.protocol_attachments, db, tenant_no)
|
||||
out["consentAttachmentFiles"] = urls_to_attachment_list(p.consent_form_urls, db, tenant_no)
|
||||
out["healthSurveyAttachmentFiles"] = urls_to_attachment_list(
|
||||
@@ -1969,7 +1996,7 @@ def _apply_payment_detail_draft_fields(
|
||||
from app.crud.dict_data import normalize_transfer_type
|
||||
|
||||
if d.pay_status != "dfj":
|
||||
raise ValueError("仅待缴费状态可编辑缴费明细")
|
||||
raise ValueError("仅已缴费状态可编辑缴费明细")
|
||||
if "transfer_type" in fields:
|
||||
d.transfer_type = normalize_transfer_type(
|
||||
db, value=str(fields.get("transfer_type") or ""), tenant_no=tenant_no
|
||||
@@ -2024,7 +2051,7 @@ def _apply_submit_receipt_finance(
|
||||
user_id: Optional[str],
|
||||
) -> None:
|
||||
if d.pay_status != "dfj":
|
||||
raise ValueError("仅待缴费状态可提交财务确认")
|
||||
raise ValueError("仅已缴费状态可提交财务确认")
|
||||
d.pay_status = "jfqrz"
|
||||
d.update_by = user_id
|
||||
d.update_time = datetime.now()
|
||||
@@ -2797,7 +2824,19 @@ def save_samples(
|
||||
db.commit()
|
||||
for o in result:
|
||||
db.refresh(o)
|
||||
save_sample_transports(db, collection_protocol_id, tenant_no, user_id, result, samples)
|
||||
# 本阶段停写旧运输明细,改挂运费批次
|
||||
from app.crud import sample_freight as sample_freight_svc
|
||||
|
||||
sample_freight_svc.apply_freight_on_protocol_save(
|
||||
db,
|
||||
tenant_no=tenant_no,
|
||||
user_id=user_id,
|
||||
sample_source=sample_freight_svc.SAMPLE_SOURCE_CELL,
|
||||
protocol_id=collection_protocol_id,
|
||||
saved_samples=result,
|
||||
input_samples=samples,
|
||||
)
|
||||
db.commit()
|
||||
return result
|
||||
|
||||
|
||||
@@ -3357,11 +3396,13 @@ def upsert_storage_cert(
|
||||
) -> BizProtocolStorageCert:
|
||||
fields = _normalize_fields(fields)
|
||||
obj = get_storage_cert(db, collection_protocol_id, tenant_no)
|
||||
_STR_FIELDS = ["make_status", "cert_image_urls", "make_date"]
|
||||
_STR_FIELDS = ["make_status", "cert_image_urls", "make_date", "handler_user_id"]
|
||||
if obj:
|
||||
for k in _STR_FIELDS:
|
||||
if k in fields:
|
||||
setattr(obj, k, (fields[k] or "").strip() or None)
|
||||
setattr(obj, k, (fields[k] or "").strip() or None if isinstance(fields[k], str) or fields[k] is None else fields[k])
|
||||
if "handle_time" in fields:
|
||||
obj.handle_time = fields.get("handle_time")
|
||||
if "remark" in fields:
|
||||
obj.remark = (fields["remark"] or "").strip() or None
|
||||
obj.update_by = user_id
|
||||
@@ -3385,13 +3426,49 @@ def upsert_storage_cert(
|
||||
)
|
||||
for k in _STR_FIELDS:
|
||||
if k in fields:
|
||||
setattr(obj, k, (fields[k] or "").strip() or None)
|
||||
val = fields[k]
|
||||
setattr(obj, k, (val or "").strip() or None if isinstance(val, str) or val is None else val)
|
||||
if "handle_time" in fields:
|
||||
obj.handle_time = fields.get("handle_time")
|
||||
db.add(obj)
|
||||
db.commit()
|
||||
db.refresh(obj)
|
||||
return obj
|
||||
|
||||
|
||||
def upsert_storage_cert_handle(
|
||||
db: Session,
|
||||
collection_protocol_id: str,
|
||||
tenant_no: str,
|
||||
user_id: Optional[str],
|
||||
*,
|
||||
handle_time: Optional[datetime] = None,
|
||||
handler_user_id: Optional[str] = None,
|
||||
cert_image_urls: Optional[str] = None,
|
||||
remark: Optional[str] = None,
|
||||
) -> BizProtocolStorageCert:
|
||||
from app.services.sample_info_progress import apply_handle_payload
|
||||
|
||||
payload = apply_handle_payload(
|
||||
handle_time=handle_time,
|
||||
handler_user_id=handler_user_id,
|
||||
attachments=cert_image_urls or "",
|
||||
remark=remark or "",
|
||||
)
|
||||
return upsert_storage_cert(
|
||||
db,
|
||||
collection_protocol_id,
|
||||
tenant_no,
|
||||
user_id,
|
||||
handle_time=payload["handle_time"],
|
||||
handler_user_id=payload["handler_user_id"],
|
||||
make_status=payload["make_or_todo_status"],
|
||||
make_date=payload["legacy_date"] or None,
|
||||
cert_image_urls=payload["attachments"] or None,
|
||||
remark=payload["remark"] or None,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 子表:满月待办
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -3401,6 +3478,9 @@ def _fullmoon_todo_empty_dict() -> Dict[str, Any]:
|
||||
return {
|
||||
"todoStatus": "0",
|
||||
"handleDate": "",
|
||||
"handleTime": "",
|
||||
"handlerUserId": "",
|
||||
"handled": False,
|
||||
"handleRemark": "",
|
||||
"insuranceAttachments": "",
|
||||
"insuranceCompanyId": "",
|
||||
@@ -3435,11 +3515,16 @@ def _fullmoon_todo_detail_dict(
|
||||
|
||||
|
||||
def _fullmoon_todo_to_dict(ft: BizProtocolFullmoonTodo) -> Dict[str, Any]:
|
||||
from app.services.sample_info_progress import insurance_handled
|
||||
|
||||
return {
|
||||
"id": ft.id,
|
||||
"collectionProtocolId": ft.collection_protocol_id,
|
||||
"todoStatus": ft.todo_status or "0",
|
||||
"handleDate": (ft.handle_date or "").strip() if ft.handle_date else "",
|
||||
"handleTime": _fmt_dt(ft.handle_time) if getattr(ft, "handle_time", None) else "",
|
||||
"handlerUserId": getattr(ft, "handler_user_id", None) or "",
|
||||
"handled": insurance_handled(ft),
|
||||
"handleRemark": ft.handle_remark or "",
|
||||
"insuranceAttachments": ft.insurance_attachments or "",
|
||||
"insuranceCompanyId": ft.insurance_company_id or "",
|
||||
@@ -3486,7 +3571,7 @@ def upsert_fullmoon_todo(
|
||||
fields = _normalize_fields(fields)
|
||||
obj = get_fullmoon_todo(db, collection_protocol_id, tenant_no)
|
||||
_STR_FIELDS = [
|
||||
"todo_status", "handle_date", "handle_remark",
|
||||
"todo_status", "handle_date", "handle_remark", "handler_user_id",
|
||||
"insurance_attachments", "insurance_company_id",
|
||||
"insurance_company_name", "insurance_type",
|
||||
"insurance_policy_no", "insurance_effective_date",
|
||||
@@ -3497,6 +3582,8 @@ def upsert_fullmoon_todo(
|
||||
for k in _STR_FIELDS:
|
||||
if k in fields:
|
||||
setattr(obj, k, (fields[k] or "").strip() or None)
|
||||
if "handle_time" in fields:
|
||||
obj.handle_time = fields.get("handle_time")
|
||||
for k in _DEC_FIELDS:
|
||||
if k in fields and fields[k] is not None:
|
||||
setattr(obj, k, Decimal(str(fields[k])))
|
||||
@@ -3524,6 +3611,8 @@ def upsert_fullmoon_todo(
|
||||
for k in _STR_FIELDS:
|
||||
if k in fields:
|
||||
setattr(obj, k, (fields[k] or "").strip() or None)
|
||||
if "handle_time" in fields:
|
||||
obj.handle_time = fields.get("handle_time")
|
||||
for k in _DEC_FIELDS:
|
||||
if k in fields and fields[k] is not None:
|
||||
setattr(obj, k, Decimal(str(fields[k])))
|
||||
@@ -3533,6 +3622,39 @@ def upsert_fullmoon_todo(
|
||||
return obj
|
||||
|
||||
|
||||
def upsert_fullmoon_todo_handle(
|
||||
db: Session,
|
||||
collection_protocol_id: str,
|
||||
tenant_no: str,
|
||||
user_id: Optional[str],
|
||||
*,
|
||||
handle_time: Optional[datetime] = None,
|
||||
handler_user_id: Optional[str] = None,
|
||||
insurance_attachments: Optional[str] = None,
|
||||
handle_remark: Optional[str] = None,
|
||||
) -> BizProtocolFullmoonTodo:
|
||||
from app.services.sample_info_progress import apply_handle_payload
|
||||
|
||||
payload = apply_handle_payload(
|
||||
handle_time=handle_time,
|
||||
handler_user_id=handler_user_id,
|
||||
attachments=insurance_attachments or "",
|
||||
remark=handle_remark or "",
|
||||
)
|
||||
return upsert_fullmoon_todo(
|
||||
db,
|
||||
collection_protocol_id,
|
||||
tenant_no,
|
||||
user_id,
|
||||
handle_time=payload["handle_time"],
|
||||
handler_user_id=payload["handler_user_id"],
|
||||
todo_status=payload["make_or_todo_status"],
|
||||
handle_date=payload["legacy_date"] or None,
|
||||
insurance_attachments=payload["attachments"] or None,
|
||||
handle_remark=payload["remark"] or None,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 开票申请(主表 + 明细子表)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -872,10 +872,19 @@ def get_donation_detail(
|
||||
)
|
||||
samples_out: List[Dict[str, Any]] = []
|
||||
unit_name_map = _load_unit_name_map(db, s_rows)
|
||||
from app.crud import sample_freight as sample_freight_svc
|
||||
|
||||
freight_by_sample = sample_freight_svc.freight_map_for_samples(
|
||||
db,
|
||||
tenant_no=tenant_no,
|
||||
sample_source=sample_freight_svc.SAMPLE_SOURCE_DONATION,
|
||||
sample_ids=sample_ids,
|
||||
)
|
||||
for s in s_rows:
|
||||
item = _sample_to_dict(s)
|
||||
item["unitName"] = unit_name_map.get(s.unit_id or "", "")
|
||||
item["transport"] = transport_by_sample.get(s.id)
|
||||
item["freight"] = freight_by_sample.get(s.id)
|
||||
item["sampleReception"] = reception_by_sample.get(s.id)
|
||||
item["productionReception"] = (reception_by_sample.get(s.id) or {}).get(
|
||||
"productionReception"
|
||||
@@ -967,12 +976,24 @@ def create_donation(
|
||||
coll_raw = _normalize_fields(raw.get("collection") or {})
|
||||
_create_collection(db, donation_id, tenant_no, user_id, coll_raw, now)
|
||||
|
||||
# samples + transports
|
||||
# samples(运费批次在 commit 后挂钩;停写旧运输依赖)
|
||||
samples_raw = raw.get("samples") or []
|
||||
_create_samples(db, donation_id, tenant_no, user_id, samples_raw, now)
|
||||
new_samples = _create_samples(db, donation_id, tenant_no, user_id, samples_raw, now)
|
||||
|
||||
db.commit()
|
||||
db.refresh(obj)
|
||||
from app.crud import sample_freight as sample_freight_svc
|
||||
|
||||
sample_freight_svc.apply_freight_on_protocol_save(
|
||||
db,
|
||||
tenant_no=tenant_no,
|
||||
user_id=user_id,
|
||||
sample_source=sample_freight_svc.SAMPLE_SOURCE_DONATION,
|
||||
protocol_id=donation_id,
|
||||
saved_samples=new_samples,
|
||||
input_samples=samples_raw,
|
||||
)
|
||||
db.commit()
|
||||
return obj
|
||||
|
||||
|
||||
@@ -1067,7 +1088,8 @@ def update_donation(
|
||||
}
|
||||
_soft_delete_samples(db, donation_id, tenant_no, user_id)
|
||||
_soft_delete_transports(db, donation_id, tenant_no, user_id)
|
||||
new_samples = _create_samples(db, donation_id, tenant_no, user_id, raw["samples"] or [], now)
|
||||
samples_raw = raw["samples"] or []
|
||||
new_samples = _create_samples(db, donation_id, tenant_no, user_id, samples_raw, now)
|
||||
new_code_to_id = {
|
||||
(s.sample_code or "").strip(): s.id
|
||||
for s in new_samples
|
||||
@@ -1080,9 +1102,25 @@ def update_donation(
|
||||
new_code_to_id=new_code_to_id,
|
||||
user_id=user_id,
|
||||
)
|
||||
else:
|
||||
samples_raw = None
|
||||
new_samples = None
|
||||
|
||||
db.commit()
|
||||
db.refresh(obj)
|
||||
if new_samples is not None and samples_raw is not None:
|
||||
from app.crud import sample_freight as sample_freight_svc
|
||||
|
||||
sample_freight_svc.apply_freight_on_protocol_save(
|
||||
db,
|
||||
tenant_no=tenant_no,
|
||||
user_id=user_id,
|
||||
sample_source=sample_freight_svc.SAMPLE_SOURCE_DONATION,
|
||||
protocol_id=donation_id,
|
||||
saved_samples=new_samples,
|
||||
input_samples=samples_raw,
|
||||
)
|
||||
db.commit()
|
||||
return obj
|
||||
|
||||
|
||||
@@ -2101,10 +2139,7 @@ def _create_samples(
|
||||
db.flush()
|
||||
result.append(obj)
|
||||
|
||||
# transport
|
||||
transport_raw = raw.get("transport")
|
||||
if isinstance(transport_raw, dict):
|
||||
_create_transport(db, donation_id, obj.id, tenant_no, user_id, transport_raw, idx, now, sample_code=obj.sample_code)
|
||||
# 本阶段不再写入旧运输明细;运费由 apply_freight_on_protocol_save 挂钩
|
||||
|
||||
return result
|
||||
|
||||
|
||||
+2
-1
@@ -13,7 +13,7 @@ from app.core.database import SessionLocal
|
||||
from app.core.api_request_log_middleware import ApiRequestLogMiddleware
|
||||
from app.core.api_crypto_middleware import ApiCryptoMiddleware
|
||||
from app.models import models # noqa: F401
|
||||
from app.api import public_router, menu_router, admin_router, workflow_router, basic_info_router, collection_protocol_router, protocol_sample_payment_router, protocol_refund_record_router, presign_order_router, leave_router, operations_router, ethnic_router, sample_reception_router, sample_production_reception_router, qc_router, research_donation_router, production_router, pilot_service_router, insurance_company_router, material_router, storage_release_review_router, sample_sms_notification_router, storage_entry_sms_notification_router, qc_sms_notification_router, protocol_sample_collection_router, research_donation_sample_collection_router, pilot_application_router, pilot_checkup_router, pilot_delivery_router, pilot_infusion_confirm_router, pilot_infusion_schedule_mgmt_router, outbound_release_review_router, clinical_payment_router, env_detect_router, cleanroom_garment_router, market_router, pre_transit_sample_router, oa_approval_router, mini_auth_router, mini_menu_router
|
||||
from app.api import public_router, menu_router, admin_router, workflow_router, basic_info_router, collection_protocol_router, protocol_sample_payment_router, protocol_refund_record_router, presign_order_router, sample_freight_router, leave_router, operations_router, ethnic_router, sample_reception_router, sample_production_reception_router, qc_router, research_donation_router, production_router, pilot_service_router, insurance_company_router, material_router, storage_release_review_router, sample_sms_notification_router, storage_entry_sms_notification_router, qc_sms_notification_router, protocol_sample_collection_router, research_donation_sample_collection_router, pilot_application_router, pilot_checkup_router, pilot_delivery_router, pilot_infusion_confirm_router, pilot_infusion_schedule_mgmt_router, outbound_release_review_router, clinical_payment_router, env_detect_router, cleanroom_garment_router, market_router, pre_transit_sample_router, oa_approval_router, mini_auth_router, mini_menu_router
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
@@ -120,6 +120,7 @@ app.include_router(collection_protocol_router, prefix=settings.API_V1_PREFIX)
|
||||
app.include_router(protocol_sample_payment_router, prefix=settings.API_V1_PREFIX)
|
||||
app.include_router(protocol_refund_record_router, prefix=settings.API_V1_PREFIX)
|
||||
app.include_router(presign_order_router, prefix=settings.API_V1_PREFIX)
|
||||
app.include_router(sample_freight_router, prefix=settings.API_V1_PREFIX)
|
||||
app.include_router(leave_router, prefix=settings.API_V1_PREFIX)
|
||||
app.include_router(operations_router, prefix=settings.API_V1_PREFIX)
|
||||
app.include_router(ethnic_router, prefix=settings.API_V1_PREFIX)
|
||||
|
||||
Reference in new issue
Block a user