|
|
@@ -0,0 +1,763 @@
|
|
|
+"""预签约订单 / 意向金转账 / 转正式协议 CRUD"""
|
|
|
+
|
|
|
+from __future__ import annotations
|
|
|
+
|
|
|
+from datetime import datetime
|
|
|
+from decimal import Decimal
|
|
|
+from typing import Any, Dict, List, Optional, Tuple
|
|
|
+
|
|
|
+from sqlalchemy import and_, func, or_
|
|
|
+from sqlalchemy.orm import Session
|
|
|
+
|
|
|
+from app.core.utils import generate_uuid, parse_datetime
|
|
|
+from app.crud.collection_protocol import urls_to_attachment_list
|
|
|
+from app.models.models import (
|
|
|
+ BizCollectionProtocol,
|
|
|
+ BizPresignOrder,
|
|
|
+ BizPresignOrderSampleType,
|
|
|
+ BizPresignTransfer,
|
|
|
+ BizProtocolPartyCustomer,
|
|
|
+)
|
|
|
+from app.services.protocol_transfer_status import can_confirm_receipt, can_submit_receipt
|
|
|
+
|
|
|
+
|
|
|
+def _now() -> datetime:
|
|
|
+ return datetime.now()
|
|
|
+
|
|
|
+
|
|
|
+def _dec(v: Any) -> Optional[Decimal]:
|
|
|
+ if v is None or v == "":
|
|
|
+ return None
|
|
|
+ return Decimal(str(v))
|
|
|
+
|
|
|
+
|
|
|
+def assert_presign_convertible(*, order_status: Optional[str]) -> None:
|
|
|
+ st = (order_status or "").strip() or "draft"
|
|
|
+ if st == "converted":
|
|
|
+ raise ValueError("该预签约订单已转正式协议")
|
|
|
+ if st == "void":
|
|
|
+ raise ValueError("已作废的预签约订单不能转正式")
|
|
|
+
|
|
|
+
|
|
|
+def _get_order_or_raise(
|
|
|
+ db: Session, *, order_id: str, tenant_no: str
|
|
|
+) -> BizPresignOrder:
|
|
|
+ obj = (
|
|
|
+ db.query(BizPresignOrder)
|
|
|
+ .filter(
|
|
|
+ BizPresignOrder.id == order_id,
|
|
|
+ BizPresignOrder.tenant_no == tenant_no,
|
|
|
+ BizPresignOrder.del_flag == 0,
|
|
|
+ )
|
|
|
+ .first()
|
|
|
+ )
|
|
|
+ if not obj:
|
|
|
+ raise ValueError("预签约订单不存在")
|
|
|
+ return obj
|
|
|
+
|
|
|
+
|
|
|
+def _get_transfer_or_raise(
|
|
|
+ db: Session, *, transfer_id: str, tenant_no: str
|
|
|
+) -> BizPresignTransfer:
|
|
|
+ obj = (
|
|
|
+ db.query(BizPresignTransfer)
|
|
|
+ .filter(
|
|
|
+ BizPresignTransfer.id == transfer_id,
|
|
|
+ BizPresignTransfer.tenant_no == tenant_no,
|
|
|
+ BizPresignTransfer.del_flag == 0,
|
|
|
+ )
|
|
|
+ .first()
|
|
|
+ )
|
|
|
+ if not obj:
|
|
|
+ raise ValueError("意向金转账笔不存在")
|
|
|
+ return obj
|
|
|
+
|
|
|
+
|
|
|
+def _transfer_to_dict(
|
|
|
+ db: Session, t: BizPresignTransfer, tenant_no: str
|
|
|
+) -> Dict[str, Any]:
|
|
|
+ return {
|
|
|
+ "id": t.id,
|
|
|
+ "presignOrderId": t.presign_order_id,
|
|
|
+ "transferAmount": float(t.transfer_amount) if t.transfer_amount is not None else None,
|
|
|
+ "transferTime": t.transfer_time.strftime("%Y-%m-%d %H:%M:%S") if t.transfer_time else "",
|
|
|
+ "transferType": t.transfer_type or "",
|
|
|
+ "transferAttachments": t.transfer_attachments or "",
|
|
|
+ "attachments": urls_to_attachment_list(t.transfer_attachments, db, tenant_no),
|
|
|
+ "payStatus": t.pay_status or "",
|
|
|
+ "financeConfirmAttachments": t.finance_confirm_attachments or "",
|
|
|
+ "financeConfirmAttachmentFiles": urls_to_attachment_list(
|
|
|
+ t.finance_confirm_attachments, db, tenant_no
|
|
|
+ ),
|
|
|
+ "financeRemark": t.finance_remark or "",
|
|
|
+ "remark": t.remark or "",
|
|
|
+ "source": "presign",
|
|
|
+ "createTime": t.create_time.strftime("%Y-%m-%d %H:%M:%S") if t.create_time else "",
|
|
|
+ "updateTime": t.update_time.strftime("%Y-%m-%d %H:%M:%S") if t.update_time else "",
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+def confirmed_deposit_amount(
|
|
|
+ db: Session, *, presign_order_id: str, tenant_no: str
|
|
|
+) -> Decimal:
|
|
|
+ rows = (
|
|
|
+ db.query(BizPresignTransfer.transfer_amount)
|
|
|
+ .filter(
|
|
|
+ BizPresignTransfer.presign_order_id == presign_order_id,
|
|
|
+ BizPresignTransfer.tenant_no == tenant_no,
|
|
|
+ BizPresignTransfer.del_flag == 0,
|
|
|
+ BizPresignTransfer.pay_status == "yjf",
|
|
|
+ )
|
|
|
+ .all()
|
|
|
+ )
|
|
|
+ total = Decimal("0")
|
|
|
+ for (amt,) in rows:
|
|
|
+ if amt is not None:
|
|
|
+ total += Decimal(str(amt))
|
|
|
+ return total
|
|
|
+
|
|
|
+
|
|
|
+def list_sample_type_ids(
|
|
|
+ db: Session, *, presign_order_id: str, tenant_no: str
|
|
|
+) -> List[str]:
|
|
|
+ rows = (
|
|
|
+ db.query(BizPresignOrderSampleType)
|
|
|
+ .filter(
|
|
|
+ BizPresignOrderSampleType.presign_order_id == presign_order_id,
|
|
|
+ BizPresignOrderSampleType.tenant_no == tenant_no,
|
|
|
+ BizPresignOrderSampleType.del_flag == 0,
|
|
|
+ )
|
|
|
+ .order_by(
|
|
|
+ BizPresignOrderSampleType.order_num.asc(),
|
|
|
+ BizPresignOrderSampleType.create_time.asc(),
|
|
|
+ )
|
|
|
+ .all()
|
|
|
+ )
|
|
|
+ return [r.sample_type_id for r in rows if r.sample_type_id]
|
|
|
+
|
|
|
+
|
|
|
+def replace_sample_types(
|
|
|
+ db: Session,
|
|
|
+ *,
|
|
|
+ presign_order_id: str,
|
|
|
+ tenant_no: str,
|
|
|
+ user_id: Optional[str],
|
|
|
+ sample_type_ids: List[str],
|
|
|
+) -> None:
|
|
|
+ now = _now()
|
|
|
+ existing = (
|
|
|
+ db.query(BizPresignOrderSampleType)
|
|
|
+ .filter(
|
|
|
+ BizPresignOrderSampleType.presign_order_id == presign_order_id,
|
|
|
+ BizPresignOrderSampleType.tenant_no == tenant_no,
|
|
|
+ BizPresignOrderSampleType.del_flag == 0,
|
|
|
+ )
|
|
|
+ .all()
|
|
|
+ )
|
|
|
+ for r in existing:
|
|
|
+ r.del_flag = 1
|
|
|
+ r.update_by = user_id
|
|
|
+ r.update_time = now
|
|
|
+ db.add(r)
|
|
|
+ seen = set()
|
|
|
+ for idx, tid in enumerate(sample_type_ids or []):
|
|
|
+ sid = (tid or "").strip()
|
|
|
+ if not sid or sid in seen:
|
|
|
+ continue
|
|
|
+ seen.add(sid)
|
|
|
+ db.add(
|
|
|
+ BizPresignOrderSampleType(
|
|
|
+ id=generate_uuid(),
|
|
|
+ presign_order_id=presign_order_id,
|
|
|
+ sample_type_id=sid,
|
|
|
+ tenant_no=tenant_no,
|
|
|
+ order_num=idx,
|
|
|
+ create_by=user_id,
|
|
|
+ create_dept_id="",
|
|
|
+ create_time=now,
|
|
|
+ update_by=user_id,
|
|
|
+ update_time=now,
|
|
|
+ del_flag=0,
|
|
|
+ )
|
|
|
+ )
|
|
|
+ db.flush()
|
|
|
+
|
|
|
+
|
|
|
+def list_transfers(
|
|
|
+ db: Session, *, presign_order_id: str, tenant_no: str
|
|
|
+) -> List[Dict[str, Any]]:
|
|
|
+ rows = (
|
|
|
+ db.query(BizPresignTransfer)
|
|
|
+ .filter(
|
|
|
+ BizPresignTransfer.presign_order_id == presign_order_id,
|
|
|
+ BizPresignTransfer.tenant_no == tenant_no,
|
|
|
+ BizPresignTransfer.del_flag == 0,
|
|
|
+ )
|
|
|
+ .order_by(BizPresignTransfer.create_time.asc())
|
|
|
+ .all()
|
|
|
+ )
|
|
|
+ return [_transfer_to_dict(db, t, tenant_no) for t in rows]
|
|
|
+
|
|
|
+
|
|
|
+def order_to_dict(
|
|
|
+ db: Session, o: BizPresignOrder, tenant_no: str, *, with_children: bool = True
|
|
|
+) -> Dict[str, Any]:
|
|
|
+ deposit = confirmed_deposit_amount(
|
|
|
+ db, presign_order_id=o.id, tenant_no=tenant_no
|
|
|
+ )
|
|
|
+ data: Dict[str, Any] = {
|
|
|
+ "id": o.id,
|
|
|
+ "orderDate": o.order_date or "",
|
|
|
+ "protocolNo": o.protocol_no or "",
|
|
|
+ "motherName": o.mother_name or "",
|
|
|
+ "expectedDeliveryDate": o.expected_delivery_date or "",
|
|
|
+ "regionId": o.region_id or "",
|
|
|
+ "hospitalId": o.hospital_id or "",
|
|
|
+ "sampleCollectTime": o.sample_collect_time.strftime("%Y-%m-%d %H:%M:%S")
|
|
|
+ if o.sample_collect_time
|
|
|
+ else "",
|
|
|
+ "payMode": o.pay_mode or "",
|
|
|
+ "contractAmount": float(o.contract_amount) if o.contract_amount is not None else None,
|
|
|
+ "salesUserId": o.sales_user_id or "",
|
|
|
+ "regionManagerUserId": o.region_manager_user_id or "",
|
|
|
+ "orderStatus": o.order_status or "",
|
|
|
+ "convertedProtocolId": o.converted_protocol_id or "",
|
|
|
+ "confirmedDepositAmount": float(deposit),
|
|
|
+ "remark": o.remark or "",
|
|
|
+ "createTime": o.create_time.strftime("%Y-%m-%d %H:%M:%S") if o.create_time else "",
|
|
|
+ "updateTime": o.update_time.strftime("%Y-%m-%d %H:%M:%S") if o.update_time else "",
|
|
|
+ }
|
|
|
+ if with_children:
|
|
|
+ data["sampleTypeIds"] = list_sample_type_ids(
|
|
|
+ db, presign_order_id=o.id, tenant_no=tenant_no
|
|
|
+ )
|
|
|
+ data["transfers"] = list_transfers(
|
|
|
+ db, presign_order_id=o.id, tenant_no=tenant_no
|
|
|
+ )
|
|
|
+ return data
|
|
|
+
|
|
|
+
|
|
|
+def list_presign_orders(
|
|
|
+ db: Session,
|
|
|
+ *,
|
|
|
+ tenant_no: str,
|
|
|
+ page: int = 1,
|
|
|
+ page_size: int = 10,
|
|
|
+ keyword: Optional[str] = None,
|
|
|
+ order_status: Optional[str] = None,
|
|
|
+ user_info=None,
|
|
|
+) -> Tuple[List[Dict[str, Any]], int]:
|
|
|
+ q = db.query(BizPresignOrder).filter(
|
|
|
+ BizPresignOrder.tenant_no == tenant_no,
|
|
|
+ BizPresignOrder.del_flag == 0,
|
|
|
+ )
|
|
|
+ st = (order_status or "").strip()
|
|
|
+ if st:
|
|
|
+ q = q.filter(BizPresignOrder.order_status == st)
|
|
|
+ kw = (keyword or "").strip()
|
|
|
+ if kw:
|
|
|
+ like = f"%{kw}%"
|
|
|
+ q = q.filter(
|
|
|
+ or_(
|
|
|
+ BizPresignOrder.protocol_no.like(like),
|
|
|
+ BizPresignOrder.mother_name.like(like),
|
|
|
+ )
|
|
|
+ )
|
|
|
+ if user_info:
|
|
|
+ from app.core.data_scope import apply_data_scope
|
|
|
+
|
|
|
+ q = apply_data_scope(q, BizPresignOrder, user_info, db, tenant_no)
|
|
|
+ total = q.order_by(None).with_entities(func.count(BizPresignOrder.id)).scalar() or 0
|
|
|
+ page = max(1, page)
|
|
|
+ page_size = min(max(1, page_size), 100)
|
|
|
+ rows = (
|
|
|
+ q.order_by(BizPresignOrder.create_time.desc())
|
|
|
+ .offset((page - 1) * page_size)
|
|
|
+ .limit(page_size)
|
|
|
+ .all()
|
|
|
+ )
|
|
|
+ return [
|
|
|
+ order_to_dict(db, o, tenant_no, with_children=False) for o in rows
|
|
|
+ ], int(total)
|
|
|
+
|
|
|
+
|
|
|
+def get_presign_order(
|
|
|
+ db: Session, *, order_id: str, tenant_no: str
|
|
|
+) -> Optional[Dict[str, Any]]:
|
|
|
+ obj = (
|
|
|
+ db.query(BizPresignOrder)
|
|
|
+ .filter(
|
|
|
+ BizPresignOrder.id == order_id,
|
|
|
+ BizPresignOrder.tenant_no == tenant_no,
|
|
|
+ BizPresignOrder.del_flag == 0,
|
|
|
+ )
|
|
|
+ .first()
|
|
|
+ )
|
|
|
+ if not obj:
|
|
|
+ return None
|
|
|
+ return order_to_dict(db, obj, tenant_no, with_children=True)
|
|
|
+
|
|
|
+
|
|
|
+def create_presign_order(
|
|
|
+ db: Session,
|
|
|
+ *,
|
|
|
+ tenant_no: str,
|
|
|
+ user_id: Optional[str],
|
|
|
+ fields: Dict[str, Any],
|
|
|
+ sample_type_ids: Optional[List[str]] = None,
|
|
|
+ transfers: Optional[List[Dict[str, Any]]] = None,
|
|
|
+) -> BizPresignOrder:
|
|
|
+ now = _now()
|
|
|
+ sample_collect = fields.get("sample_collect_time") or fields.get("sampleCollectTime")
|
|
|
+ sct = None
|
|
|
+ if sample_collect not in (None, ""):
|
|
|
+ sct = (
|
|
|
+ sample_collect
|
|
|
+ if isinstance(sample_collect, datetime)
|
|
|
+ else parse_datetime(str(sample_collect))
|
|
|
+ )
|
|
|
+ pay_mode = (fields.get("pay_mode") or fields.get("payMode") or "").strip() or None
|
|
|
+ if pay_mode and pay_mode not in ("dj", "fq"):
|
|
|
+ raise ValueError("缴费方式无效")
|
|
|
+ obj = BizPresignOrder(
|
|
|
+ id=generate_uuid(),
|
|
|
+ order_date=(fields.get("order_date") or fields.get("orderDate") or "").strip()
|
|
|
+ or None,
|
|
|
+ protocol_no=(fields.get("protocol_no") or fields.get("protocolNo") or "").strip()
|
|
|
+ or None,
|
|
|
+ mother_name=(fields.get("mother_name") or fields.get("motherName") or "").strip()
|
|
|
+ or None,
|
|
|
+ expected_delivery_date=(
|
|
|
+ fields.get("expected_delivery_date")
|
|
|
+ or fields.get("expectedDeliveryDate")
|
|
|
+ or ""
|
|
|
+ ).strip()
|
|
|
+ or None,
|
|
|
+ region_id=(fields.get("region_id") or fields.get("regionId") or "").strip() or None,
|
|
|
+ hospital_id=(fields.get("hospital_id") or fields.get("hospitalId") or "").strip()
|
|
|
+ or None,
|
|
|
+ sample_collect_time=sct,
|
|
|
+ pay_mode=pay_mode,
|
|
|
+ contract_amount=_dec(fields.get("contract_amount", fields.get("contractAmount"))),
|
|
|
+ sales_user_id=(fields.get("sales_user_id") or fields.get("salesUserId") or "").strip()
|
|
|
+ or None,
|
|
|
+ region_manager_user_id=(
|
|
|
+ fields.get("region_manager_user_id")
|
|
|
+ or fields.get("regionManagerUserId")
|
|
|
+ or ""
|
|
|
+ ).strip()
|
|
|
+ or None,
|
|
|
+ order_status=(fields.get("order_status") or fields.get("orderStatus") or "active")
|
|
|
+ .strip()
|
|
|
+ or "active",
|
|
|
+ tenant_no=tenant_no,
|
|
|
+ order_num=0,
|
|
|
+ remark=(fields.get("remark") or "").strip() or None,
|
|
|
+ create_by=user_id,
|
|
|
+ create_dept_id="",
|
|
|
+ create_time=now,
|
|
|
+ update_by=user_id,
|
|
|
+ update_time=now,
|
|
|
+ del_flag=0,
|
|
|
+ )
|
|
|
+ db.add(obj)
|
|
|
+ db.flush()
|
|
|
+ replace_sample_types(
|
|
|
+ db,
|
|
|
+ presign_order_id=obj.id,
|
|
|
+ tenant_no=tenant_no,
|
|
|
+ user_id=user_id,
|
|
|
+ sample_type_ids=sample_type_ids or [],
|
|
|
+ )
|
|
|
+ for raw in transfers or []:
|
|
|
+ if isinstance(raw, dict):
|
|
|
+ add_transfer(
|
|
|
+ db,
|
|
|
+ presign_order_id=obj.id,
|
|
|
+ tenant_no=tenant_no,
|
|
|
+ user_id=user_id,
|
|
|
+ fields=raw,
|
|
|
+ commit=False,
|
|
|
+ )
|
|
|
+ db.commit()
|
|
|
+ db.refresh(obj)
|
|
|
+ return obj
|
|
|
+
|
|
|
+
|
|
|
+def update_presign_order(
|
|
|
+ db: Session,
|
|
|
+ *,
|
|
|
+ order_id: str,
|
|
|
+ tenant_no: str,
|
|
|
+ user_id: Optional[str],
|
|
|
+ fields: Dict[str, Any],
|
|
|
+ sample_type_ids: Optional[List[str]] = None,
|
|
|
+) -> BizPresignOrder:
|
|
|
+ obj = _get_order_or_raise(db, order_id=order_id, tenant_no=tenant_no)
|
|
|
+ if (obj.order_status or "") == "converted":
|
|
|
+ raise ValueError("已转正式的预签约订单不可修改")
|
|
|
+ mapping = {
|
|
|
+ "order_date": "order_date",
|
|
|
+ "orderDate": "order_date",
|
|
|
+ "protocol_no": "protocol_no",
|
|
|
+ "protocolNo": "protocol_no",
|
|
|
+ "mother_name": "mother_name",
|
|
|
+ "motherName": "mother_name",
|
|
|
+ "expected_delivery_date": "expected_delivery_date",
|
|
|
+ "expectedDeliveryDate": "expected_delivery_date",
|
|
|
+ "region_id": "region_id",
|
|
|
+ "regionId": "region_id",
|
|
|
+ "hospital_id": "hospital_id",
|
|
|
+ "hospitalId": "hospital_id",
|
|
|
+ "pay_mode": "pay_mode",
|
|
|
+ "payMode": "pay_mode",
|
|
|
+ "sales_user_id": "sales_user_id",
|
|
|
+ "salesUserId": "sales_user_id",
|
|
|
+ "region_manager_user_id": "region_manager_user_id",
|
|
|
+ "regionManagerUserId": "region_manager_user_id",
|
|
|
+ "order_status": "order_status",
|
|
|
+ "orderStatus": "order_status",
|
|
|
+ "remark": "remark",
|
|
|
+ }
|
|
|
+ for k, attr in mapping.items():
|
|
|
+ if k in fields:
|
|
|
+ val = fields[k]
|
|
|
+ if attr in (
|
|
|
+ "order_date",
|
|
|
+ "protocol_no",
|
|
|
+ "mother_name",
|
|
|
+ "expected_delivery_date",
|
|
|
+ "region_id",
|
|
|
+ "hospital_id",
|
|
|
+ "pay_mode",
|
|
|
+ "sales_user_id",
|
|
|
+ "region_manager_user_id",
|
|
|
+ "order_status",
|
|
|
+ "remark",
|
|
|
+ ):
|
|
|
+ setattr(obj, attr, (str(val).strip() if val is not None else "") or None)
|
|
|
+ if "contract_amount" in fields or "contractAmount" in fields:
|
|
|
+ obj.contract_amount = _dec(
|
|
|
+ fields.get("contract_amount", fields.get("contractAmount"))
|
|
|
+ )
|
|
|
+ if "sample_collect_time" in fields or "sampleCollectTime" in fields:
|
|
|
+ v = fields.get("sample_collect_time", fields.get("sampleCollectTime"))
|
|
|
+ if v in (None, ""):
|
|
|
+ obj.sample_collect_time = None
|
|
|
+ elif isinstance(v, datetime):
|
|
|
+ obj.sample_collect_time = v
|
|
|
+ else:
|
|
|
+ obj.sample_collect_time = parse_datetime(str(v))
|
|
|
+ if obj.pay_mode and obj.pay_mode not in ("dj", "fq"):
|
|
|
+ raise ValueError("缴费方式无效")
|
|
|
+ obj.update_by = user_id
|
|
|
+ obj.update_time = _now()
|
|
|
+ db.add(obj)
|
|
|
+ if sample_type_ids is not None:
|
|
|
+ replace_sample_types(
|
|
|
+ db,
|
|
|
+ presign_order_id=obj.id,
|
|
|
+ tenant_no=tenant_no,
|
|
|
+ user_id=user_id,
|
|
|
+ sample_type_ids=sample_type_ids,
|
|
|
+ )
|
|
|
+ db.commit()
|
|
|
+ db.refresh(obj)
|
|
|
+ return obj
|
|
|
+
|
|
|
+
|
|
|
+def soft_delete_presign_order(
|
|
|
+ db: Session, *, order_id: str, tenant_no: str, user_id: Optional[str]
|
|
|
+) -> None:
|
|
|
+ obj = _get_order_or_raise(db, order_id=order_id, tenant_no=tenant_no)
|
|
|
+ if (obj.order_status or "") == "converted":
|
|
|
+ raise ValueError("已转正式的预签约订单不可删除")
|
|
|
+ now = _now()
|
|
|
+ obj.del_flag = 1
|
|
|
+ obj.update_by = user_id
|
|
|
+ obj.update_time = now
|
|
|
+ db.add(obj)
|
|
|
+ db.commit()
|
|
|
+
|
|
|
+
|
|
|
+def add_transfer(
|
|
|
+ db: Session,
|
|
|
+ *,
|
|
|
+ presign_order_id: str,
|
|
|
+ tenant_no: str,
|
|
|
+ user_id: Optional[str],
|
|
|
+ fields: Dict[str, Any],
|
|
|
+ commit: bool = True,
|
|
|
+) -> BizPresignTransfer:
|
|
|
+ _get_order_or_raise(db, order_id=presign_order_id, tenant_no=tenant_no)
|
|
|
+ now = _now()
|
|
|
+ tt = fields.get("transfer_time", fields.get("transferTime"))
|
|
|
+ transfer_time = None
|
|
|
+ if tt not in (None, ""):
|
|
|
+ transfer_time = tt if isinstance(tt, datetime) else parse_datetime(str(tt))
|
|
|
+ obj = BizPresignTransfer(
|
|
|
+ id=generate_uuid(),
|
|
|
+ presign_order_id=presign_order_id,
|
|
|
+ transfer_amount=_dec(fields.get("transfer_amount", fields.get("transferAmount"))),
|
|
|
+ transfer_time=transfer_time,
|
|
|
+ transfer_type=(
|
|
|
+ fields.get("transfer_type") or fields.get("transferType") or ""
|
|
|
+ ).strip()
|
|
|
+ or None,
|
|
|
+ transfer_attachments=(
|
|
|
+ fields.get("transfer_attachments")
|
|
|
+ or fields.get("transferAttachments")
|
|
|
+ or ""
|
|
|
+ ).strip()
|
|
|
+ or None,
|
|
|
+ pay_status="dfj",
|
|
|
+ tenant_no=tenant_no,
|
|
|
+ order_num=0,
|
|
|
+ remark=(fields.get("remark") or "").strip() or None,
|
|
|
+ create_by=user_id,
|
|
|
+ create_dept_id="",
|
|
|
+ create_time=now,
|
|
|
+ update_by=user_id,
|
|
|
+ update_time=now,
|
|
|
+ del_flag=0,
|
|
|
+ )
|
|
|
+ db.add(obj)
|
|
|
+ if commit:
|
|
|
+ db.commit()
|
|
|
+ db.refresh(obj)
|
|
|
+ else:
|
|
|
+ db.flush()
|
|
|
+ return obj
|
|
|
+
|
|
|
+
|
|
|
+def update_transfer_draft(
|
|
|
+ db: Session,
|
|
|
+ *,
|
|
|
+ transfer_id: str,
|
|
|
+ tenant_no: str,
|
|
|
+ user_id: Optional[str],
|
|
|
+ fields: Dict[str, Any],
|
|
|
+) -> BizPresignTransfer:
|
|
|
+ t = _get_transfer_or_raise(db, transfer_id=transfer_id, tenant_no=tenant_no)
|
|
|
+ if t.pay_status != "dfj":
|
|
|
+ raise ValueError("仅待缴费状态可编辑意向金转账笔")
|
|
|
+ if "transfer_amount" in fields or "transferAmount" in fields:
|
|
|
+ t.transfer_amount = _dec(
|
|
|
+ fields.get("transfer_amount", fields.get("transferAmount"))
|
|
|
+ )
|
|
|
+ if "transfer_time" in fields or "transferTime" in fields:
|
|
|
+ v = fields.get("transfer_time", fields.get("transferTime"))
|
|
|
+ if v in (None, ""):
|
|
|
+ t.transfer_time = None
|
|
|
+ elif isinstance(v, datetime):
|
|
|
+ t.transfer_time = v
|
|
|
+ else:
|
|
|
+ t.transfer_time = parse_datetime(str(v))
|
|
|
+ if "transfer_type" in fields or "transferType" in fields:
|
|
|
+ t.transfer_type = (
|
|
|
+ str(fields.get("transfer_type", fields.get("transferType")) or "")
|
|
|
+ ).strip() or None
|
|
|
+ if "transfer_attachments" in fields or "transferAttachments" in fields:
|
|
|
+ t.transfer_attachments = (
|
|
|
+ str(
|
|
|
+ fields.get("transfer_attachments", fields.get("transferAttachments"))
|
|
|
+ or ""
|
|
|
+ )
|
|
|
+ ).strip() or None
|
|
|
+ if "remark" in fields:
|
|
|
+ t.remark = (fields.get("remark") or "").strip() or None
|
|
|
+ t.update_by = user_id
|
|
|
+ t.update_time = _now()
|
|
|
+ db.add(t)
|
|
|
+ db.commit()
|
|
|
+ db.refresh(t)
|
|
|
+ return t
|
|
|
+
|
|
|
+
|
|
|
+def submit_receipt_finance(
|
|
|
+ db: Session, *, transfer_id: str, tenant_no: str, user_id: Optional[str]
|
|
|
+) -> BizPresignTransfer:
|
|
|
+ t = _get_transfer_or_raise(db, transfer_id=transfer_id, tenant_no=tenant_no)
|
|
|
+ if not can_submit_receipt(t.pay_status):
|
|
|
+ raise ValueError("仅待缴费状态可提交财务确认")
|
|
|
+ if not (t.transfer_type or "").strip():
|
|
|
+ raise ValueError("请选择转账方式")
|
|
|
+ if t.transfer_amount is None or Decimal(str(t.transfer_amount)) <= 0:
|
|
|
+ raise ValueError("请填写转账金额")
|
|
|
+ t.pay_status = "jfqrz"
|
|
|
+ t.update_by = user_id
|
|
|
+ t.update_time = _now()
|
|
|
+ db.add(t)
|
|
|
+ db.commit()
|
|
|
+ db.refresh(t)
|
|
|
+ return t
|
|
|
+
|
|
|
+
|
|
|
+def confirm_receipt(
|
|
|
+ db: Session,
|
|
|
+ *,
|
|
|
+ transfer_id: str,
|
|
|
+ tenant_no: str,
|
|
|
+ user_id: Optional[str],
|
|
|
+ finance_confirm_attachments: Optional[str] = None,
|
|
|
+ finance_remark: Optional[str] = None,
|
|
|
+) -> BizPresignTransfer:
|
|
|
+ t = _get_transfer_or_raise(db, transfer_id=transfer_id, tenant_no=tenant_no)
|
|
|
+ if not can_confirm_receipt(t.pay_status):
|
|
|
+ raise ValueError("仅缴费确认中状态可确认收款")
|
|
|
+ t.pay_status = "yjf"
|
|
|
+ if finance_confirm_attachments is not None:
|
|
|
+ t.finance_confirm_attachments = (finance_confirm_attachments or "").strip() or None
|
|
|
+ if finance_remark is not None:
|
|
|
+ t.finance_remark = (finance_remark or "").strip() or None
|
|
|
+ t.update_by = user_id
|
|
|
+ t.update_time = _now()
|
|
|
+ db.add(t)
|
|
|
+ db.commit()
|
|
|
+ db.refresh(t)
|
|
|
+ return t
|
|
|
+
|
|
|
+
|
|
|
+def reject_receipt(
|
|
|
+ db: Session, *, transfer_id: str, tenant_no: str, user_id: Optional[str]
|
|
|
+) -> BizPresignTransfer:
|
|
|
+ t = _get_transfer_or_raise(db, transfer_id=transfer_id, tenant_no=tenant_no)
|
|
|
+ if not can_confirm_receipt(t.pay_status):
|
|
|
+ raise ValueError("仅缴费确认中状态可退回")
|
|
|
+ t.pay_status = "dfj"
|
|
|
+ t.update_by = user_id
|
|
|
+ t.update_time = _now()
|
|
|
+ db.add(t)
|
|
|
+ db.commit()
|
|
|
+ db.refresh(t)
|
|
|
+ return t
|
|
|
+
|
|
|
+
|
|
|
+def list_presign_transfers_for_finance(
|
|
|
+ db: Session,
|
|
|
+ *,
|
|
|
+ pay_status: str,
|
|
|
+ tenant_no: str,
|
|
|
+ keyword: Optional[str] = None,
|
|
|
+ page: int = 1,
|
|
|
+ page_size: int = 10,
|
|
|
+ user_info=None,
|
|
|
+) -> Tuple[List[Dict[str, Any]], int]:
|
|
|
+ if not tenant_no:
|
|
|
+ return [], 0
|
|
|
+ st = (pay_status or "").strip()
|
|
|
+ q = (
|
|
|
+ db.query(BizPresignTransfer, BizPresignOrder)
|
|
|
+ .join(
|
|
|
+ BizPresignOrder,
|
|
|
+ and_(
|
|
|
+ BizPresignOrder.id == BizPresignTransfer.presign_order_id,
|
|
|
+ BizPresignOrder.del_flag == 0,
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ .filter(
|
|
|
+ BizPresignTransfer.tenant_no == tenant_no,
|
|
|
+ BizPresignTransfer.del_flag == 0,
|
|
|
+ BizPresignTransfer.pay_status == st,
|
|
|
+ )
|
|
|
+ )
|
|
|
+ kw = (keyword or "").strip()
|
|
|
+ if kw:
|
|
|
+ like = f"%{kw}%"
|
|
|
+ q = q.filter(
|
|
|
+ or_(
|
|
|
+ BizPresignOrder.protocol_no.like(like),
|
|
|
+ BizPresignOrder.mother_name.like(like),
|
|
|
+ )
|
|
|
+ )
|
|
|
+ if user_info:
|
|
|
+ from app.core.data_scope import apply_data_scope
|
|
|
+
|
|
|
+ q = apply_data_scope(q, BizPresignTransfer, user_info, db, tenant_no)
|
|
|
+ total = q.order_by(None).with_entities(func.count(BizPresignTransfer.id)).scalar() or 0
|
|
|
+ page = max(1, page)
|
|
|
+ page_size = min(max(1, page_size), 100)
|
|
|
+ rows = (
|
|
|
+ q.order_by(BizPresignTransfer.create_time.desc())
|
|
|
+ .offset((page - 1) * page_size)
|
|
|
+ .limit(page_size)
|
|
|
+ .all()
|
|
|
+ )
|
|
|
+ out: List[Dict[str, Any]] = []
|
|
|
+ for t, order in rows:
|
|
|
+ item = _transfer_to_dict(db, t, tenant_no)
|
|
|
+ item["protocolNo"] = (order.protocol_no if order else "") or ""
|
|
|
+ item["motherName"] = (order.mother_name if order else "") or ""
|
|
|
+ item["sampleCode"] = ""
|
|
|
+ item["presignOrderId"] = order.id if order else ""
|
|
|
+ out.append(item)
|
|
|
+ return out, int(total)
|
|
|
+
|
|
|
+
|
|
|
+def convert_to_protocol(
|
|
|
+ db: Session,
|
|
|
+ *,
|
|
|
+ order_id: str,
|
|
|
+ tenant_no: str,
|
|
|
+ user_id: Optional[str],
|
|
|
+) -> Dict[str, Any]:
|
|
|
+ """转正式协议:创建协议主单 + 当事人/采集信息 + 样本类型行;不写入样本缴费金额。"""
|
|
|
+ from app.crud import collection_protocol as cp_svc
|
|
|
+
|
|
|
+ order = _get_order_or_raise(db, order_id=order_id, tenant_no=tenant_no)
|
|
|
+ assert_presign_convertible(order_status=order.order_status)
|
|
|
+
|
|
|
+ type_ids = list_sample_type_ids(db, presign_order_id=order.id, tenant_no=tenant_no)
|
|
|
+ protocol = cp_svc.create_collection_protocol(
|
|
|
+ db,
|
|
|
+ protocol_no=order.protocol_no or "",
|
|
|
+ protocol_status="dsx",
|
|
|
+ register_date=order.order_date,
|
|
|
+ collector_user_id=user_id,
|
|
|
+ source_presign_order_id=order.id,
|
|
|
+ payment_model_version=2,
|
|
|
+ protocol_total_amount=None,
|
|
|
+ remark=order.remark,
|
|
|
+ tenant_no=tenant_no,
|
|
|
+ user_id=user_id,
|
|
|
+ )
|
|
|
+ party_fields: Dict[str, Any] = {
|
|
|
+ "mother_name": order.mother_name,
|
|
|
+ "expected_delivery_date": order.expected_delivery_date,
|
|
|
+ }
|
|
|
+ cp_svc.upsert_party_customer(
|
|
|
+ db, protocol.id, tenant_no, user_id, **party_fields
|
|
|
+ )
|
|
|
+ collection_fields: Dict[str, Any] = {
|
|
|
+ "region_id": order.region_id,
|
|
|
+ "hospital_id": order.hospital_id,
|
|
|
+ "sales_user_id": order.sales_user_id,
|
|
|
+ "sample_collect_time": order.sample_collect_time,
|
|
|
+ }
|
|
|
+ cp_svc.upsert_collection_info(
|
|
|
+ db, protocol.id, tenant_no, user_id, **collection_fields
|
|
|
+ )
|
|
|
+ if type_ids:
|
|
|
+ samples = [
|
|
|
+ {
|
|
|
+ "sampleTypeId": tid,
|
|
|
+ "sampleStatus": None,
|
|
|
+ }
|
|
|
+ for tid in type_ids
|
|
|
+ ]
|
|
|
+ cp_svc.save_samples(db, protocol.id, tenant_no, user_id, samples)
|
|
|
+
|
|
|
+ order.order_status = "converted"
|
|
|
+ order.converted_protocol_id = protocol.id
|
|
|
+ if not (order.protocol_no or "").strip():
|
|
|
+ order.protocol_no = protocol.protocol_no
|
|
|
+ order.update_by = user_id
|
|
|
+ order.update_time = _now()
|
|
|
+ db.add(order)
|
|
|
+ db.commit()
|
|
|
+ db.refresh(order)
|
|
|
+ return {
|
|
|
+ "presignOrder": order_to_dict(db, order, tenant_no, with_children=True),
|
|
|
+ "protocolId": protocol.id,
|
|
|
+ "protocolNo": protocol.protocol_no,
|
|
|
+ }
|