Files
zkxw/backend/app/crud/clinical_payment.py
T

528 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""临床应用缴费 CRUD"""
import uuid
from datetime import datetime
from decimal import Decimal
from typing import Any, Dict, List, Optional, Tuple
from sqlalchemy import and_, func
from sqlalchemy.orm import Session
from app.models.clinical_payment import BizPilotPayment
from app.models.models import (
BizPilotApplication,
BizPilotPatient,
BizPilotInfusionPlan,
BizPilotInfusionSchedule,
)
# ─── helpers ──────────────────────────────────────────────
def _row_to_dict(row: BizPilotPayment, *, extra: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
d: Dict[str, Any] = {
"id": row.id,
"pilotApplicationId": row.pilot_application_id or "",
"infusionScheduleId": row.infusion_schedule_id or "",
"bizType": row.biz_type or "clinical_application",
"amount": float(row.amount) if row.amount else 0,
"transferType": row.transfer_type or "",
"transferAmount": float(row.transfer_amount) if row.transfer_amount else None,
"transferTime": row.transfer_time.strftime("%Y-%m-%d %H:%M:%S") if row.transfer_time else "",
"transferAttachments": row.transfer_attachments or "",
"payStatus": row.pay_status or "dfj",
"financeConfirmAttachments": row.finance_confirm_attachments or "",
"financeRemark": row.finance_remark or "",
"financeConfirmTime": row.finance_confirm_time.strftime("%Y-%m-%d %H:%M:%S") if row.finance_confirm_time else "",
"refundAmount": float(row.refund_amount) if row.refund_amount else None,
"refundRemark": row.refund_remark or "",
"refundAttachments": row.refund_attachments or "",
"refundApplyBy": row.refund_apply_by or "",
"refundApplyTime": row.refund_apply_time.strftime("%Y-%m-%d %H:%M:%S") if row.refund_apply_time else "",
"refundFinanceConfirmAttachments": row.refund_finance_confirm_attachments or "",
"refundFinanceRemark": row.refund_finance_remark or "",
"refundFinanceConfirmTime": row.refund_finance_confirm_time.strftime("%Y-%m-%d %H:%M:%S") if row.refund_finance_confirm_time else "",
"tenantNo": row.tenant_no or "",
"remark": row.remark or "",
"createBy": row.create_by or "",
"createTime": row.create_time.strftime("%Y-%m-%d %H:%M:%S") if row.create_time else "",
"updateTime": row.update_time.strftime("%Y-%m-%d %H:%M:%S") if row.update_time else "",
}
if extra:
d.update(extra)
return d
def _get_or_raise(db: Session, *, payment_id: str, tenant_no: str) -> BizPilotPayment:
row = (
db.query(BizPilotPayment)
.filter(
and_(
BizPilotPayment.id == payment_id,
BizPilotPayment.tenant_no == tenant_no,
BizPilotPayment.del_flag == 0,
)
)
.first()
)
if not row:
raise ValueError("缴费记录不存在")
return row
def _enrich_payment(db: Session, row: BizPilotPayment) -> Dict[str, Any]:
"""关联查询申请单、患者、输注计划信息,附加到 dict"""
app_row = (
db.query(BizPilotApplication)
.filter(BizPilotApplication.id == row.pilot_application_id)
.first()
)
patient_row = None
schedule_row = (
db.query(BizPilotInfusionSchedule)
.filter(BizPilotInfusionSchedule.id == row.infusion_schedule_id)
.first()
)
if app_row:
patient_row = (
db.query(BizPilotPatient)
.filter(
BizPilotPatient.pilot_application_id == app_row.id,
BizPilotPatient.del_flag == 0,
)
.first()
)
cell_type = schedule_row.cell_type if schedule_row else ""
spec_text = schedule_row.spec_text if schedule_row else ""
cell_type_spec = " / ".join(x for x in (cell_type, spec_text) if x)
app_biz = (app_row.biz_type or "") if app_row else ""
extra = {
"applicationNo": app_row.application_no if app_row else "",
"applicationName": app_row.application_name if app_row else "",
"channelName": app_row.channel_name if app_row else "",
"patientName": "" if app_biz == "basic_experiment" else (patient_row.patient_name if patient_row else ""),
"cellType": cell_type,
"specText": spec_text,
"cellTypeSpec": cell_type_spec,
"paymentAmount": float(row.amount) if row.amount else 0,
"infusionTime": schedule_row.infusion_time.strftime("%Y-%m-%d %H:%M:%S") if schedule_row and schedule_row.infusion_time else "",
}
if app_biz == "basic_experiment" and app_row:
extra["projectTitle"] = app_row.project_title or ""
extra["customerName"] = app_row.customer_name or ""
extra["customerContact"] = app_row.customer_contact or ""
extra["customerPhone"] = app_row.customer_phone or ""
return _row_to_dict(row, extra=extra)
def map_payments_by_schedule_id(
db: Session,
*,
application_id: str,
tenant_no: str,
) -> Dict[str, Dict[str, Any]]:
"""按输注计划 ID 索引临床应用缴费记录"""
rows = (
db.query(BizPilotPayment)
.filter(
BizPilotPayment.pilot_application_id == application_id,
BizPilotPayment.tenant_no == tenant_no,
BizPilotPayment.del_flag == 0,
)
.all()
)
return {row.infusion_schedule_id: _enrich_payment(db, row) for row in rows}
def map_payments_by_schedule_ids(
db: Session,
*,
schedule_ids: List[str],
tenant_no: str,
) -> Dict[str, Dict[str, Any]]:
"""按输注计划 ID 批量索引缴费记录(列表页展示用,不做关联 enrich)"""
unique_ids = [sid for sid in {sid for sid in schedule_ids if sid}]
if not unique_ids:
return {}
rows = (
db.query(BizPilotPayment)
.filter(
BizPilotPayment.infusion_schedule_id.in_(unique_ids),
BizPilotPayment.tenant_no == tenant_no,
BizPilotPayment.del_flag == 0,
)
.all()
)
return {row.infusion_schedule_id: _row_to_dict(row) for row in rows}
# ─── create (auto) ────────────────────────────────────────
def auto_create_payment(
db: Session,
*,
application_id: str,
schedule_id: str,
amount: Any,
tenant_no: str,
user_id: str,
dept_id: str,
biz_type: str = "clinical_application",
) -> Optional[BizPilotPayment]:
"""输注计划创建时自动调用,生成 dfj 缴费记录"""
if amount is None or Decimal(str(amount)) <= 0:
return None
now = datetime.now()
row = BizPilotPayment(
id=str(uuid.uuid4()),
pilot_application_id=application_id,
infusion_schedule_id=schedule_id,
biz_type=biz_type or "clinical_application",
amount=Decimal(str(amount)),
pay_status="dfj",
tenant_no=tenant_no,
order_num=0,
create_by=user_id,
create_dept_id=dept_id or "",
create_time=now,
update_by=user_id,
update_time=now,
del_flag=0,
)
db.add(row)
return row
# ─── list ─────────────────────────────────────────────────
def list_payments(
db: Session,
*,
page: int = 1,
page_size: int = 10,
pay_status: Optional[str] = None,
keyword: Optional[str] = None,
biz_type: Optional[str] = None,
tenant_no: str = "",
user_info=None,
) -> Tuple[List[Dict[str, Any]], int]:
q = (
db.query(BizPilotPayment)
.filter(
BizPilotPayment.del_flag == 0,
BizPilotPayment.tenant_no == tenant_no,
)
)
if biz_type and biz_type.strip():
q = q.filter(BizPilotPayment.biz_type == biz_type.strip())
if pay_status and pay_status.strip():
q = q.filter(BizPilotPayment.pay_status == pay_status.strip())
if keyword and keyword.strip():
kw = f"%{keyword.strip()}%"
sub = (
db.query(BizPilotApplication.id)
.filter(
BizPilotApplication.del_flag == 0,
BizPilotApplication.application_no.like(kw),
)
.subquery()
)
q = q.filter(BizPilotPayment.pilot_application_id.in_(sub))
if user_info:
from app.core.data_scope import apply_data_scope
q = apply_data_scope(q, BizPilotPayment, user_info, db, tenant_no)
total = q.order_by(None).with_entities(func.count(BizPilotPayment.id)).scalar() or 0
page = max(1, page)
page_size = min(max(1, page_size), 100)
rows = (
q.order_by(BizPilotPayment.create_time.desc())
.offset((page - 1) * page_size)
.limit(page_size)
.all()
)
out = [_enrich_payment(db, r) for r in rows]
return out, int(total)
# ─── submit receipt (dfj → jfqrz) ────────────────────────
def _apply_submit_receipt(
db: Session,
*,
tenant_no: str,
row: BizPilotPayment,
user_id: Optional[str],
remark: Optional[str] = None,
transfer_attachments: Optional[str] = None,
transfer_amount: Optional[Any] = None,
transfer_type: Optional[str] = None,
) -> None:
from app.crud.dict_data import normalize_transfer_type
if row.pay_status != "dfj":
raise ValueError("仅待缴费状态可提交缴费确认")
plan_amount = Decimal(str(row.amount or 0))
if transfer_amount is not None and Decimal(str(transfer_amount)) != plan_amount:
raise ValueError("实缴金额须与计划金额一致")
if transfer_type is not None:
row.transfer_type = normalize_transfer_type(
db, value=transfer_type, tenant_no=tenant_no
)
row.pay_status = "jfqrz"
row.transfer_amount = plan_amount
if remark is not None:
row.remark = remark.strip() or None
if transfer_attachments is not None:
row.transfer_attachments = transfer_attachments.strip() or None
row.update_by = user_id
row.update_time = datetime.now()
def submit_receipt(
db: Session,
*,
payment_id: str,
tenant_no: str,
user_id: Optional[str],
remark: Optional[str] = None,
transfer_attachments: Optional[str] = None,
transfer_amount: Optional[Any] = None,
transfer_type: Optional[str] = None,
) -> BizPilotPayment:
row = _get_or_raise(db, payment_id=payment_id, tenant_no=tenant_no)
_apply_submit_receipt(
db,
tenant_no=tenant_no,
row=row,
user_id=user_id,
remark=remark,
transfer_attachments=transfer_attachments,
transfer_amount=transfer_amount,
transfer_type=transfer_type,
)
db.add(row)
db.commit()
db.refresh(row)
return row
def batch_submit_receipt(
db: Session,
*,
payment_ids: List[str],
tenant_no: str,
user_id: Optional[str],
remark: Optional[str] = None,
transfer_attachments: Optional[str] = None,
) -> int:
ids = [str(i).strip() for i in (payment_ids or []) if str(i).strip()]
if not ids:
raise ValueError("请选择缴费记录")
count = 0
for pid in ids:
row = _get_or_raise(db, payment_id=pid, tenant_no=tenant_no)
if row.pay_status != "dfj":
continue
_apply_submit_receipt(
db,
tenant_no=tenant_no,
row=row,
user_id=user_id,
remark=remark,
transfer_attachments=transfer_attachments,
)
db.add(row)
count += 1
db.commit()
return count
# ─── confirm receipt (jfqrz → yjf) ──────────────────────
def confirm_receipt(
db: Session,
*,
payment_id: str,
tenant_no: str,
user_id: Optional[str],
finance_confirm_attachments: Optional[str] = None,
finance_remark: Optional[str] = None,
) -> BizPilotPayment:
row = _get_or_raise(db, payment_id=payment_id, tenant_no=tenant_no)
if row.pay_status != "jfqrz":
if row.pay_status == "yjf":
raise ValueError("该记录已确认收款")
raise ValueError("当前状态不可确认收款")
row.pay_status = "yjf"
if finance_confirm_attachments is not None:
row.finance_confirm_attachments = finance_confirm_attachments.strip() or None
if finance_remark is not None:
row.finance_remark = finance_remark.strip() or None
row.finance_confirm_time = datetime.now()
row.update_by = user_id
row.update_time = datetime.now()
db.add(row)
db.commit()
db.refresh(row)
return row
def reject_receipt(
db: Session,
*,
payment_id: str,
tenant_no: str,
user_id: Optional[str],
) -> BizPilotPayment:
row = _get_or_raise(db, payment_id=payment_id, tenant_no=tenant_no)
if row.pay_status != "jfqrz":
raise ValueError("当前状态不可退回")
row.pay_status = "dfj"
row.update_by = user_id
row.update_time = datetime.now()
db.add(row)
db.commit()
db.refresh(row)
return row
# ─── submit refund (yjf → tfqrz) ─────────────────────────
def submit_refund(
db: Session,
*,
payment_id: str,
tenant_no: str,
user_id: Optional[str],
refund_amount: Any,
refund_remark: Optional[str] = None,
refund_attachments: Optional[str] = None,
) -> BizPilotPayment:
row = _get_or_raise(db, payment_id=payment_id, tenant_no=tenant_no)
if row.pay_status != "yjf":
raise ValueError("仅付款完成状态可发起退费")
if refund_amount is None or Decimal(str(refund_amount)) <= 0:
raise ValueError("请填写有效的退费金额")
row.pay_status = "tfqrz"
row.refund_amount = Decimal(str(refund_amount))
row.refund_remark = (refund_remark or "").strip() or None
row.refund_attachments = (refund_attachments or "").strip() or None
row.refund_apply_by = user_id
row.refund_apply_time = datetime.now()
row.update_by = user_id
row.update_time = datetime.now()
db.add(row)
db.commit()
db.refresh(row)
return row
def batch_submit_refund(
db: Session,
*,
payment_ids: List[str],
tenant_no: str,
user_id: Optional[str],
refund_amount: Any,
refund_remark: Optional[str] = None,
refund_attachments: Optional[str] = None,
) -> int:
ids = [str(i).strip() for i in (payment_ids or []) if str(i).strip()]
if not ids:
raise ValueError("请选择缴费记录")
for pid in ids:
row = _get_or_raise(db, payment_id=pid, tenant_no=tenant_no)
if row.pay_status != "yjf":
continue
if refund_amount is None or Decimal(str(refund_amount)) <= 0:
continue
row.pay_status = "tfqrz"
row.refund_amount = Decimal(str(refund_amount))
row.refund_remark = (refund_remark or "").strip() or None
row.refund_attachments = (refund_attachments or "").strip() or None
row.refund_apply_by = user_id
row.refund_apply_time = datetime.now()
row.update_by = user_id
row.update_time = datetime.now()
db.add(row)
db.commit()
return len(ids)
# ─── confirm refund (tfqrz → ytf) ───────────────────────
def confirm_refund(
db: Session,
*,
payment_id: str,
tenant_no: str,
user_id: Optional[str],
refund_finance_confirm_attachments: Optional[str] = None,
refund_finance_remark: Optional[str] = None,
) -> BizPilotPayment:
row = _get_or_raise(db, payment_id=payment_id, tenant_no=tenant_no)
if row.pay_status != "tfqrz":
if row.pay_status == "ytf":
raise ValueError("该记录已确认退费")
raise ValueError("当前状态不可确认退费")
row.pay_status = "ytf"
if refund_finance_confirm_attachments is not None:
row.refund_finance_confirm_attachments = refund_finance_confirm_attachments.strip() or None
if refund_finance_remark is not None:
row.refund_finance_remark = refund_finance_remark.strip() or None
row.refund_finance_confirm_time = datetime.now()
row.update_by = user_id
row.update_time = datetime.now()
db.add(row)
db.commit()
db.refresh(row)
return row
def reject_refund(
db: Session,
*,
payment_id: str,
tenant_no: str,
user_id: Optional[str],
) -> BizPilotPayment:
row = _get_or_raise(db, payment_id=payment_id, tenant_no=tenant_no)
if row.pay_status != "tfqrz":
raise ValueError("当前状态不可退回")
row.pay_status = "yjf"
row.update_by = user_id
row.update_time = datetime.now()
db.add(row)
db.commit()
db.refresh(row)
return row
# ─── detail ───────────────────────────────────────────────
def get_payment_detail(db: Session, *, payment_id: str, tenant_no: str) -> Optional[Dict[str, Any]]:
row = _get_or_raise(db, payment_id=payment_id, tenant_no=tenant_no)
return _enrich_payment(db, row)
def get_payment_by_schedule(db: Session, *, schedule_id: str, tenant_no: str) -> Optional[Dict[str, Any]]:
row = (
db.query(BizPilotPayment)
.filter(
BizPilotPayment.infusion_schedule_id == schedule_id,
BizPilotPayment.tenant_no == tenant_no,
BizPilotPayment.del_flag == 0,
)
.first()
)
if not row:
return None
return _enrich_payment(db, row)