528 lines
17 KiB
Python
528 lines
17 KiB
Python
"""临床应用缴费 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)
|