| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853 |
- """细胞存储 — 采集协议单 / 审核 / 财务确认 / 分期预警 API"""
- from datetime import datetime, timedelta
- 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 sqlalchemy import and_ as sa_and, func as sa_func, or_ as sa_or
- 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.core.utils import parse_datetime
- from app.crud import collection_protocol as cp_svc
- from app.crud import customer as customer_crud
- from app.models.models import (
- BizCollectionProtocol,
- BizCustomer,
- BizProtocolPartyCustomer,
- BizProtocolSample,
- BizProtocolStorageCert,
- 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)
- _CAMEL_TO_SNAKE_DATETIME = {
- "newbornDeliveryTime": "newborn_delivery_time",
- "sampleCollectTime": "sample_collect_time",
- "maternalBloodCollectTime": "maternal_blood_collect_time",
- }
- def _parse_datetime_fields(d: dict, camel_keys: list) -> None:
- for camel in camel_keys:
- val = d.get(camel)
- if val is not None and isinstance(val, str):
- snake = _CAMEL_TO_SNAKE_DATETIME.get(camel, camel)
- parsed = parse_datetime(val)
- if parsed:
- d.pop(camel, None)
- d[snake] = parsed
- else:
- d.pop(camel, None)
- d[snake] = None
- def _ensure_customer_for_mother(db: Session, party_customer: dict, tenant_no: str, user_id: str) -> dict:
- """If mother info exists but no customerId, create a customer record and link it."""
- if not party_customer:
- return party_customer
- customer_id = party_customer.get("customer_id") or party_customer.get("customerId")
- mother_name = (party_customer.get("mother_name") or party_customer.get("motherName") or "").strip()
- if customer_id or not mother_name:
- return party_customer
- # Build customer fields from mother info
- mother_phone = (party_customer.get("mother_phone") or party_customer.get("motherPhone") or "").strip() or None
- mother_id_card = (party_customer.get("mother_id_card") or party_customer.get("motherIdCard") or "").strip() or None
- # Check if customer with this id_card already exists
- if mother_id_card and customer_crud.id_card_taken(db, mother_id_card, tenant_no):
- existing = db.query(BizCustomer).filter(
- sa_and(
- BizCustomer.id_card_no == mother_id_card,
- BizCustomer.tenant_no == tenant_no,
- BizCustomer.del_flag == 0,
- )
- ).first()
- if existing:
- party_customer["customer_id"] = existing.id
- return party_customer
- # Create new customer
- try:
- obj = customer_crud.create_customer(
- db,
- customer_name=mother_name,
- sex="1", # female (mother)
- id_card_no=mother_id_card,
- phone_no=mother_phone,
- tenant_no=tenant_no,
- user_id=user_id,
- )
- party_customer["customer_id"] = obj.id
- except ValueError:
- # If creation fails (e.g. duplicate), try to find existing
- pass
- return party_customer
- # ---------------------------------------------------------------------------
- # 采集协议单
- # ---------------------------------------------------------------------------
- @router.get("/collection-protocols")
- async def list_collection_protocols(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
- keyword: Optional[str] = None,
- protocolStatus: Optional[str] = Query(None, alias="protocolStatus"),
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- current_user_id = _get_current_user_id(user)
- items, total = cp_svc.list_collection_protocols(
- db,
- page=page,
- page_size=pageSize,
- keyword=keyword,
- protocol_status=protocolStatus,
- tenant_no=tenant_no,
- scope_collector_user_id=current_user_id,
- user_info=user,
- )
- tp = cp_svc.total_pages(total, pageSize)
- return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
- @router.get("/collection-protocols/{protocol_id}")
- async def get_collection_protocol_detail(
- protocol_id: str,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- current_user_id = _get_current_user_id(user)
- bundle = cp_svc.get_protocol_detail_bundle(
- db, protocol_id, tenant_no, scope_collector_user_id=current_user_id
- )
- if not bundle:
- return error_response(404, "协议单不存在或无权查看")
- return success_response(bundle)
- class ProtocolCreateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- protocol_no: str = Field(..., alias="protocolNo")
- protocol_no_id: Optional[str] = Field(None, alias="protocolNoId")
- protocol_status: str = Field("dsx", alias="protocolStatus")
- protocol_attachments: Optional[str] = Field(None, alias="protocolAttachments")
- register_date: Optional[str] = Field(None, alias="registerDate")
- collector_user_id: Optional[str] = Field(None, alias="collectorUserId")
- remark: Optional[str] = None
- party_customer: Optional[dict] = Field(None, alias="partyCustomer")
- collection_info: Optional[dict] = Field(None, alias="collectionInfo")
- samples: Optional[list] = Field(None, alias="samples")
- payment: Optional[dict] = Field(None, alias="payment")
- payment_details: Optional[list] = Field(None, alias="paymentDetails")
- attachments: Optional[list] = Field(None, alias="attachments")
- storage_cert: Optional[dict] = Field(None, alias="storageCert")
- class ProtocolUpdateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- protocol_no: Optional[str] = Field(None, alias="protocolNo")
- protocol_status: Optional[str] = Field(None, alias="protocolStatus")
- protocol_attachments: Optional[str] = Field(None, alias="protocolAttachments")
- register_date: Optional[str] = Field(None, alias="registerDate")
- effective_time: Optional[str] = Field(None, alias="effectiveTime")
- effective_desc: Optional[str] = Field(None, alias="effectiveDesc")
- expire_time: Optional[str] = Field(None, alias="expireTime")
- expire_desc: Optional[str] = Field(None, alias="expireDesc")
- void_time: Optional[str] = Field(None, alias="voidTime")
- void_desc: Optional[str] = Field(None, alias="voidDesc")
- to_research_time: Optional[str] = Field(None, alias="toResearchTime")
- to_research_desc: Optional[str] = Field(None, alias="toResearchDesc")
- collector_user_id: Optional[str] = Field(None, alias="collectorUserId")
- remark: Optional[str] = None
- party_customer: Optional[dict] = Field(None, alias="partyCustomer")
- collection_info: Optional[dict] = Field(None, alias="collectionInfo")
- samples: Optional[list] = Field(None, alias="samples")
- payment: Optional[dict] = Field(None, alias="payment")
- payment_details: Optional[list] = Field(None, alias="paymentDetails")
- attachments: Optional[list] = Field(None, alias="attachments")
- storage_cert: Optional[dict] = Field(None, alias="storageCert")
- def _resolve_protocol_attachments(
- protocol_attachments: Optional[str],
- attachments: Optional[list],
- ) -> Optional[str]:
- from_urls = cp_svc.attachments_to_urls(attachments)
- if from_urls:
- return from_urls
- return protocol_attachments
- def _persist_protocol_sub_resources(
- db: Session,
- protocol_id: str,
- tenant_no: str,
- user_id: str,
- *,
- party_customer: Optional[dict],
- collection_info: Optional[dict],
- samples: Optional[list],
- payment: Optional[dict],
- payment_details: Optional[list],
- storage_cert: Optional[dict],
- ) -> None:
- if party_customer:
- _parse_datetime_fields(party_customer, ["newbornDeliveryTime"])
- party_customer = _ensure_customer_for_mother(db, party_customer, tenant_no, user_id)
- cp_svc.upsert_party_customer(db, protocol_id, tenant_no, user_id, **party_customer)
- if collection_info:
- _parse_datetime_fields(collection_info, ["sampleCollectTime", "maternalBloodCollectTime"])
- cp_svc.upsert_collection_info(db, protocol_id, tenant_no, user_id, **collection_info)
- if samples is not None:
- cp_svc.save_samples(db, protocol_id, tenant_no, user_id, samples)
- pay = None
- if payment:
- pay = cp_svc.upsert_payment(db, protocol_id, tenant_no, user_id, **payment)
- if payment_details is not None:
- if not pay and payment:
- pay = cp_svc.upsert_payment(db, protocol_id, tenant_no, user_id, **payment)
- if pay:
- aligned_details = cp_svc.align_fq_payment_details_payload(pay, payment_details)
- cp_svc.save_payment_details(db, pay.id, tenant_no, user_id, aligned_details)
- if storage_cert:
- cp_svc.upsert_storage_cert(db, protocol_id, tenant_no, user_id, **storage_cert)
- @router.post("/collection-protocols")
- async def create_collection_protocol(
- body: ProtocolCreateBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- try:
- protocol_attachments = _resolve_protocol_attachments(
- body.protocol_attachments,
- body.attachments,
- )
- obj = cp_svc.create_collection_protocol(
- db,
- protocol_no=body.protocol_no,
- protocol_no_id=body.protocol_no_id,
- protocol_status=body.protocol_status,
- protocol_attachments=protocol_attachments,
- register_date=body.register_date,
- collector_user_id=user_id,
- remark=body.remark,
- tenant_no=tenant_no,
- user_id=user_id,
- )
- protocol_id = obj.id
- _persist_protocol_sub_resources(
- db,
- protocol_id,
- tenant_no,
- user_id,
- party_customer=body.party_customer,
- collection_info=body.collection_info,
- samples=body.samples,
- payment=body.payment,
- payment_details=body.payment_details,
- storage_cert=body.storage_cert,
- )
- return success_response(cp_svc._protocol_to_dict(obj), message="创建成功")
- except ValueError as e:
- return error_response(400, str(e))
- @router.put("/collection-protocols/{protocol_id}")
- async def update_collection_protocol(
- protocol_id: str,
- body: ProtocolUpdateBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- try:
- payload = body.model_dump(exclude_unset=True, by_alias=False)
- sub_keys = {
- "party_customer", "collection_info", "samples",
- "payment", "payment_details", "attachments", "storage_cert",
- }
- main_payload = {k: v for k, v in payload.items() if k not in sub_keys}
- main_payload.pop("collector_user_id", None)
- if body.attachments is not None:
- resolved = _resolve_protocol_attachments(
- main_payload.get("protocol_attachments"),
- body.attachments,
- )
- if resolved is not None:
- main_payload["protocol_attachments"] = resolved
- obj = cp_svc.update_collection_protocol(
- db,
- protocol_id,
- tenant_no=tenant_no,
- user_id=user_id,
- scope_collector_user_id=user_id,
- **{k: v for k, v in main_payload.items() if v is not None},
- )
- _persist_protocol_sub_resources(
- db,
- protocol_id,
- tenant_no,
- user_id,
- party_customer=body.party_customer,
- collection_info=body.collection_info,
- samples=body.samples,
- payment=body.payment,
- payment_details=body.payment_details,
- storage_cert=body.storage_cert,
- )
- return success_response(cp_svc._protocol_to_dict(obj), message="更新成功")
- except ValueError as e:
- return error_response(400, str(e))
- @router.delete("/collection-protocols/{protocol_id}")
- async def delete_collection_protocol(
- protocol_id: str,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- try:
- cp_svc.soft_delete_collection_protocol(
- db,
- protocol_id,
- tenant_no=tenant_no,
- scope_collector_user_id=user_id,
- user_id=user_id,
- )
- return success_response(None, message="删除成功")
- except ValueError as e:
- return error_response(400, str(e))
- # ---------------------------------------------------------------------------
- # 审核
- # ---------------------------------------------------------------------------
- @router.get("/collection-protocol-audits")
- async def list_collection_protocol_audits(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
- keyword: Optional[str] = None,
- auditStatus: Optional[str] = Query(None, alias="auditStatus"),
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- items, total = cp_svc.list_audits(
- db,
- page=page,
- page_size=pageSize,
- keyword=keyword,
- audit_status=auditStatus,
- tenant_no=tenant_no,
- user_info=user,
- )
- tp = cp_svc.total_pages(total, pageSize)
- return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
- class AuditCreateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- audit_status: str = Field(..., alias="auditStatus")
- audit_remark: Optional[str] = Field(None, alias="auditRemark")
- @router.post("/collection-protocols/{protocol_id}/audit")
- async def submit_collection_protocol_audit(
- protocol_id: str,
- body: AuditCreateBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- try:
- cp_svc.create_audit(
- db,
- collection_protocol_id=protocol_id,
- audit_status=body.audit_status,
- audit_remark=body.audit_remark,
- tenant_no=tenant_no,
- user_id=user.get("user_id"),
- )
- p = db.query(BizCollectionProtocol).filter(BizCollectionProtocol.id == protocol_id).first()
- pn = (p.protocol_no if p else "") or ""
- return success_response({"protocolNo": pn}, message="审核已提交")
- except ValueError as e:
- return error_response(400, str(e))
- # ---------------------------------------------------------------------------
- # 收款确认
- # ---------------------------------------------------------------------------
- @router.get("/protocol-payment-details")
- async def list_payment_details(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
- payStatus: str = Query("dfj", alias="payStatus"),
- keyword: str = Query("", alias="keyword"),
- transferType: str = Query("", alias="transferType"),
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- items, total = cp_svc.list_payment_details_page(
- db,
- page=page,
- page_size=pageSize,
- pay_status=payStatus,
- keyword=keyword or None,
- transfer_type=transferType or None,
- tenant_no=tenant_no,
- user_info=user,
- )
- tp = cp_svc.total_pages(total, pageSize)
- return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
- class BatchDetailIdsBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- detail_ids: List[str] = Field(..., alias="detailIds", min_length=1)
- class RefundFinanceSubmitBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- refund_amount: float = Field(..., alias="refundAmount", gt=0)
- refund_remark: Optional[str] = Field(None, alias="refundRemark")
- refund_attachments: Optional[str] = Field(None, alias="refundAttachments")
- class BatchRefundFinanceSubmitBody(RefundFinanceSubmitBody):
- model_config = ConfigDict(populate_by_name=True)
- detail_ids: List[str] = Field(..., alias="detailIds", min_length=1)
- @router.post("/protocol-payment-details/{detail_id}/submit-receipt-finance")
- async def post_submit_receipt_finance(
- detail_id: str,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- try:
- cp_svc.submit_receipt_finance(
- db,
- detail_id=detail_id,
- tenant_no=tenant_no,
- user_id=user.get("user_id"),
- )
- return success_response(None, message="提交成功")
- except ValueError as e:
- return error_response(400, str(e))
- @router.post("/protocol-payment-details/submit-receipt-finance-batch")
- async def post_submit_receipt_finance_batch(
- body: BatchDetailIdsBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- try:
- count = cp_svc.batch_submit_receipt_finance(
- db,
- detail_ids=body.detail_ids,
- 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("/protocol-payment-details/{detail_id}/submit-refund-finance")
- async def post_submit_refund_finance(
- detail_id: str,
- body: RefundFinanceSubmitBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- try:
- cp_svc.submit_refund_finance(
- db,
- detail_id=detail_id,
- tenant_no=tenant_no,
- user_id=user.get("user_id"),
- refund_amount=body.refund_amount,
- refund_remark=body.refund_remark,
- refund_attachments=body.refund_attachments,
- )
- return success_response(None, message="提交成功")
- except ValueError as e:
- return error_response(400, str(e))
- @router.post("/protocol-payment-details/submit-refund-finance-batch")
- async def post_submit_refund_finance_batch(
- body: BatchRefundFinanceSubmitBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- try:
- count = cp_svc.batch_submit_refund_finance(
- db,
- detail_ids=body.detail_ids,
- tenant_no=tenant_no,
- user_id=user.get("user_id"),
- refund_amount=body.refund_amount,
- refund_remark=body.refund_remark,
- refund_attachments=body.refund_attachments,
- )
- return success_response({"count": count}, message=f"已成功提交 {count} 条")
- except ValueError as e:
- return error_response(400, str(e))
- @router.get("/protocol-payment-details/receipt-confirm")
- async def list_receipt_confirm(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- items, total = cp_svc.list_receipt_confirm_details(db, page=page, page_size=pageSize, tenant_no=tenant_no, user_info=user)
- tp = cp_svc.total_pages(total, pageSize)
- return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
- @router.get("/protocol-payment-details/receipt-confirmed")
- async def list_receipt_confirmed(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- items, total = cp_svc.list_receipt_confirmed_details(
- db, page=page, page_size=pageSize, tenant_no=tenant_no, user_info=user
- )
- tp = cp_svc.total_pages(total, pageSize)
- return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
- class FinanceConfirmBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- finance_confirm_attachments: Optional[str] = Field(None, alias="financeConfirmAttachments")
- finance_remark: Optional[str] = Field(None, alias="financeRemark")
- class RefundFinanceConfirmBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- refund_finance_confirm_attachments: Optional[str] = Field(
- None, alias="refundFinanceConfirmAttachments"
- )
- refund_finance_remark: Optional[str] = Field(None, alias="refundFinanceRemark")
- @router.post("/protocol-payment-details/{detail_id}/receipt-confirm")
- async def post_receipt_confirm(
- detail_id: str,
- body: FinanceConfirmBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- try:
- cp_svc.confirm_receipt(
- db,
- detail_id=detail_id,
- tenant_no=tenant_no,
- user_id=user.get("user_id"),
- finance_confirm_attachments=body.finance_confirm_attachments,
- finance_remark=body.finance_remark,
- )
- return success_response(None, message="确认成功")
- except ValueError as e:
- return error_response(400, str(e))
- # ---------------------------------------------------------------------------
- # 退费确认
- # ---------------------------------------------------------------------------
- @router.get("/protocol-payment-details/refund-confirm")
- async def list_refund_confirm(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- items, total = cp_svc.list_refund_confirm_details(db, page=page, page_size=pageSize, tenant_no=tenant_no, user_info=user)
- tp = cp_svc.total_pages(total, pageSize)
- return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
- @router.get("/protocol-payment-details/refund-confirmed")
- async def list_refund_confirmed(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- items, total = cp_svc.list_refund_confirmed_details(
- db, page=page, page_size=pageSize, tenant_no=tenant_no, user_info=user
- )
- tp = cp_svc.total_pages(total, pageSize)
- return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
- @router.post("/protocol-payment-details/{detail_id}/refund-confirm")
- async def post_refund_confirm(
- detail_id: str,
- body: RefundFinanceConfirmBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- try:
- cp_svc.confirm_refund(
- db,
- detail_id=detail_id,
- tenant_no=tenant_no,
- user_id=user.get("user_id"),
- refund_finance_confirm_attachments=body.refund_finance_confirm_attachments,
- refund_finance_remark=body.refund_finance_remark,
- )
- return success_response(None, message="确认成功")
- except ValueError as e:
- return error_response(400, str(e))
- # ---------------------------------------------------------------------------
- # 储存证明管理 / 满月待办
- # ---------------------------------------------------------------------------
- @router.get("/storage-certs")
- async def list_storage_certs(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
- keyword: Optional[str] = None,
- makeStatus: Optional[str] = Query(None, alias="makeStatus"),
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- page_items, total = cp_svc.list_storage_certs(
- db,
- tenant_no=tenant_no,
- keyword=keyword,
- make_status=makeStatus,
- page=page,
- page_size=pageSize,
- user_info=user,
- )
- page_safe = max(1, page)
- page_size_safe = min(max(1, pageSize), 100)
- tp = cp_svc.total_pages(total, page_size_safe)
- return success_response(
- {"list": page_items, "total": total, "page": page_safe, "pageSize": page_size_safe, "totalPages": tp}
- )
- @router.get("/fullmoon-todos")
- async def list_fullmoon_todos(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
- keyword: Optional[str] = None,
- todoStatus: Optional[str] = Query(None, alias="todoStatus"),
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- party_join = sa_and(
- BizProtocolPartyCustomer.collection_protocol_id == BizCollectionProtocol.id,
- BizProtocolPartyCustomer.del_flag == 0,
- BizProtocolPartyCustomer.tenant_no == tenant_no,
- )
- q = (
- db.query(BizCollectionProtocol, BizProtocolPartyCustomer)
- .outerjoin(BizProtocolPartyCustomer, party_join)
- .filter(
- BizCollectionProtocol.del_flag == 0,
- BizCollectionProtocol.tenant_no == tenant_no,
- BizCollectionProtocol.protocol_status == "sxz",
- )
- )
- kw = (keyword or "").strip()
- if kw:
- like = f"%{kw}%"
- q = q.filter(
- sa_or(
- BizCollectionProtocol.protocol_no.like(like),
- BizProtocolPartyCustomer.mother_name.like(like),
- )
- )
- from app.core.data_scope import apply_data_scope
- q = apply_data_scope(q, BizCollectionProtocol, user, db, tenant_no)
- rows = q.order_by(BizCollectionProtocol.create_time.desc()).all()
- protocol_ids = [r.id for r, _ in rows]
- ft_map: Dict[str, Any] = {}
- if protocol_ids:
- from app.models.models import BizProtocolFullmoonTodo
- ft_rows = (
- db.query(BizProtocolFullmoonTodo)
- .filter(
- BizProtocolFullmoonTodo.collection_protocol_id.in_(protocol_ids),
- BizProtocolFullmoonTodo.tenant_no == tenant_no,
- BizProtocolFullmoonTodo.del_flag == 0,
- )
- .all()
- )
- for ft in ft_rows:
- ft_map[ft.collection_protocol_id] = ft
- now = datetime.now()
- _empty_ft: Dict[str, Any] = {
- "todoStatus": "0",
- "handleDate": "",
- "handleRemark": "",
- "insuranceAttachments": "",
- "insuranceCompanyId": "",
- "insuranceCompanyName": "",
- "insuranceType": "",
- "insurancePolicyNo": "",
- "insuranceEffectiveDate": "",
- "insuranceExpiryDate": "",
- "insuranceSubscribed": "0",
- "insuranceFee": None,
- }
- items: List[Dict[str, Any]] = []
- for proto, pc in rows:
- dt = pc.newborn_delivery_time if pc else None
- fullmoon_date_str = ""
- is_fullmoon_passed = False
- if dt:
- fullmoon_date = dt + timedelta(days=30)
- fullmoon_date_str = fullmoon_date.strftime("%Y-%m-%d")
- is_fullmoon_passed = fullmoon_date <= now
- ft = ft_map.get(proto.id)
- ts = (ft.todo_status or "0") if ft else "0"
- if todoStatus and todoStatus.strip() and ts != todoStatus.strip():
- continue
- ft_dict = cp_svc._fullmoon_todo_to_dict(ft) if ft else dict(_empty_ft)
- items.append(
- {
- "protocolId": proto.id,
- "id": proto.id,
- "protocolNo": proto.protocol_no or "",
- "protocolStatus": proto.protocol_status or "",
- "motherName": (pc.mother_name if pc else "") or "",
- "motherPhone": (pc.mother_phone if pc else "") or "",
- "newbornName": (pc.newborn_name if pc else "") or "",
- "newbornDeliveryTime": cp_svc._fmt_dt(dt) if dt else "",
- "fullmoonDate": fullmoon_date_str,
- "isFullmoonPassed": is_fullmoon_passed,
- **ft_dict,
- }
- )
- total = len(items)
- page_safe = max(1, page)
- page_size_safe = min(max(1, pageSize), 100)
- start = (page_safe - 1) * page_size_safe
- page_items = items[start : start + page_size_safe]
- tp = cp_svc.total_pages(total, page_size_safe)
- return success_response(
- {"list": page_items, "total": total, "page": page_safe, "pageSize": page_size_safe, "totalPages": tp}
- )
- class FullmoonTodoInsuranceBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- insurance_company_id: Optional[str] = Field(None, alias="insuranceCompanyId")
- insurance_company_name: Optional[str] = Field(None, alias="insuranceCompanyName")
- insurance_type: Optional[str] = Field(None, alias="insuranceType")
- insurance_policy_no: Optional[str] = Field(None, alias="insurancePolicyNo")
- insurance_effective_date: Optional[str] = Field(None, alias="insuranceEffectiveDate")
- insurance_expiry_date: Optional[str] = Field(None, alias="insuranceExpiryDate")
- insurance_subscribed: Optional[str] = Field(None, alias="insuranceSubscribed")
- insurance_fee: Optional[float] = Field(None, alias="insuranceFee")
- insurance_attachments: Optional[str] = Field(None, alias="insuranceAttachments")
- todo_status: Optional[str] = Field(None, alias="todoStatus")
- handle_date: Optional[str] = Field(None, alias="handleDate")
- handle_remark: Optional[str] = Field(None, alias="handleRemark")
- @router.put("/fullmoon-todos/{protocol_id}/insurance")
- async def update_fullmoon_todo_insurance(
- protocol_id: str,
- body: FullmoonTodoInsuranceBody,
- user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- try:
- payload = body.model_dump(exclude_unset=True, by_alias=False)
- ft = cp_svc.upsert_fullmoon_todo(
- db,
- protocol_id,
- tenant_no,
- user_id,
- **payload,
- )
- return success_response(cp_svc._fullmoon_todo_to_dict(ft), message="更新成功")
- except ValueError as e:
- return error_response(400, str(e))
|