feat(web-admin): finance confirm by transfer and remove refund confirm tab

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-10-03 11:17:19 +08:00
1 parent 4e71a13165
commit c404c168fe
7 files changed
+133 -81

No files matched your search

+3 -3
View File
@@ -65,7 +65,7 @@ STATIC_MENU_TREE = [
"name": "CellStoragePaymentConfirm",
"path": "payment-confirm",
"component": "/cell-storage/payment-confirm/index",
"meta": {"title": "缴退费财务确认及开票", "keepAlive": True, "menuCode": "biz:cellStoragePaymentConfirm:list"},
"meta": {"title": "缴费财务确认及开票", "keepAlive": True, "menuCode": "biz:cellStoragePaymentConfirm:list"},
},
# 市场侧入库放行审核(总览)暂下线:改由生产/质检入口审核
# {
@@ -149,7 +149,7 @@ STATIC_MENU_TREE = [
"name": "BasicInfoInsuranceCompany",
"path": "insurance-company",
"component": "/basic-info/insurance-company/index",
"meta": {"title": "保险公司及险种价格", "keepAlive": True, "menuCode": "basic-info:insurance-company:list"},
"meta": {"title": "保险公司及险种价格", "keepAlive": True, "isHide": True, "menuCode": "basic-info:insurance-company:list"},
},
{
@@ -193,7 +193,7 @@ STATIC_MENU_TREE = [
"name": "CheckupInstitution",
"path": "checkup-institution",
"component": "/pilot-service/checkup-institution/index",
"meta": {"title": "体检机构及项目价格", "keepAlive": True, "menuCode": "pilot-service:checkup-institution:list"},
"meta": {"title": "体检机构及项目价格", "keepAlive": True, "isHide": True, "menuCode": "pilot-service:checkup-institution:list"},
},
{
+10 -2
View File
@@ -334,8 +334,10 @@ async def submit_payment_transfer_finance(
@router.get("/payment-transfers/receipt-confirm")
async def list_receipt_confirm(
page: int = Query(1, ge=1),
page_size: int = Query(10, ge=1, le=100),
page_size: int = Query(10, ge=1, le=100, alias="pageSize"),
keyword: Optional[str] = None,
installmentDueDateStart: Optional[str] = Query(None, alias="installmentDueDateStart"),
installmentDueDateEnd: Optional[str] = Query(None, alias="installmentDueDateEnd"),
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
@@ -349,6 +351,8 @@ async def list_receipt_confirm(
page=page,
page_size=page_size,
user_info=user,
due_date_start=installmentDueDateStart,
due_date_end=installmentDueDateEnd,
)
return success_response({"list": items, "total": total, "page": page, "pageSize": page_size})
except PermissionError as e:
@@ -358,8 +362,10 @@ async def list_receipt_confirm(
@router.get("/payment-transfers/receipt-confirmed")
async def list_receipt_confirmed(
page: int = Query(1, ge=1),
page_size: int = Query(10, ge=1, le=100),
page_size: int = Query(10, ge=1, le=100, alias="pageSize"),
keyword: Optional[str] = None,
installmentDueDateStart: Optional[str] = Query(None, alias="installmentDueDateStart"),
installmentDueDateEnd: Optional[str] = Query(None, alias="installmentDueDateEnd"),
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
@@ -373,6 +379,8 @@ async def list_receipt_confirmed(
page=page,
page_size=page_size,
user_info=user,
due_date_start=installmentDueDateStart,
due_date_end=installmentDueDateEnd,
)
return success_response({"list": items, "total": total, "page": page, "pageSize": page_size})
except PermissionError as e:
+44 -2
View File
@@ -829,12 +829,20 @@ def list_transfers_for_finance(
page: int = 1,
page_size: int = 10,
user_info=None,
due_date_start: Optional[str] = None,
due_date_end: Optional[str] = None,
) -> Tuple[List[Dict[str, Any]], int]:
if not tenant_no:
return [], 0
st = (pay_status or "").strip()
q = (
db.query(BizProtocolPaymentTransfer, BizCollectionProtocol, BizProtocolPartyCustomer)
db.query(
BizProtocolPaymentTransfer,
BizCollectionProtocol,
BizProtocolPartyCustomer,
BizProtocolPaymentSchedule,
BizProtocolSamplePayment,
)
.join(
BizCollectionProtocol,
and_(
@@ -852,6 +860,21 @@ def list_transfers_for_finance(
BizProtocolPartyCustomer.del_flag == 0,
),
)
.outerjoin(
BizProtocolPaymentSchedule,
and_(
BizProtocolPaymentSchedule.id == BizProtocolPaymentTransfer.schedule_id,
BizProtocolPaymentSchedule.del_flag == 0,
),
)
.outerjoin(
BizProtocolSamplePayment,
and_(
BizProtocolSamplePayment.id
== BizProtocolPaymentTransfer.sample_payment_id,
BizProtocolSamplePayment.del_flag == 0,
),
)
.filter(
BizProtocolPaymentTransfer.tenant_no == tenant_no,
BizProtocolPaymentTransfer.del_flag == 0,
@@ -867,8 +890,16 @@ def list_transfers_for_finance(
BizProtocolPaymentTransfer.sample_code.like(like),
BizProtocolPartyCustomer.mother_name.like(like),
BizProtocolPartyCustomer.mother_id_card.like(like),
BizProtocolPartyCustomer.newborn_name.like(like),
BizProtocolPartyCustomer.newborn_id_card.like(like),
)
)
ds = (due_date_start or "").strip().replace("-", "")[:8]
de = (due_date_end or "").strip().replace("-", "")[:8]
if ds:
q = q.filter(BizProtocolPaymentSchedule.due_date >= ds)
if de:
q = q.filter(BizProtocolPaymentSchedule.due_date <= de)
if user_info:
from app.core.data_scope import apply_data_scope
@@ -885,9 +916,20 @@ def list_transfers_for_finance(
.all()
)
out: List[Dict[str, Any]] = []
for t, proto, party in rows:
for t, proto, party, sch, sp in rows:
item = _transfer_to_dict(db, t, tenant_no)
item["protocolNo"] = (proto.protocol_no if proto else "") or ""
item["protocolStatus"] = (proto.protocol_status if proto else "") or ""
item["voidType"] = (proto.void_type if proto else "") or ""
item["motherName"] = (party.mother_name if party else "") or ""
item["motherIdCard"] = (party.mother_id_card if party else "") or ""
item["newbornName"] = (party.newborn_name if party else "") or ""
item["newbornIdCard"] = (party.newborn_id_card if party else "") or ""
item["installmentNo"] = sch.schedule_no if sch else None
item["installmentDueDate"] = (sch.due_date if sch else "") or ""
item["dueAmount"] = (
float(sch.due_amount) if sch and sch.due_amount is not None else None
)
item["payMode"] = (sp.pay_mode if sp else "") or ""
out.append(item)
return out, int(total)
@@ -52,6 +52,8 @@ export function fetchPaymentTransferReceiptConfirmList(params?: {
page?: number
pageSize?: number
keyword?: string
installmentDueDateStart?: string
installmentDueDateEnd?: string
}) {
return request.get<PageList<Record<string, unknown>>>({
url: '/web/admin/payment-transfers/receipt-confirm',
@@ -63,6 +65,8 @@ export function fetchPaymentTransferReceiptConfirmedList(params?: {
page?: number
pageSize?: number
keyword?: string
installmentDueDateStart?: string
installmentDueDateEnd?: string
}) {
return request.get<PageList<Record<string, unknown>>>({
url: '/web/admin/payment-transfers/receipt-confirmed',
+3 -1
View File
@@ -89,7 +89,7 @@ export const sqxpAdminRoutes: AppRouteRecord[] = [
path: 'payment-confirm',
component: '/cell-storage/payment-confirm/index',
meta: {
title: '缴退费财务确认及开票',
title: '缴费财务确认及开票',
keepAlive: true,
menuCode: 'biz:cellStoragePaymentConfirm:list'
}
@@ -290,6 +290,7 @@ export const sqxpAdminRoutes: AppRouteRecord[] = [
meta: {
title: '保险公司及险种价格',
keepAlive: true,
isHide: true,
menuCode: 'basic-info:insurance-company:list'
}
},
@@ -398,6 +399,7 @@ export const sqxpAdminRoutes: AppRouteRecord[] = [
meta: {
title: '体检机构及项目价格',
keepAlive: true,
isHide: true,
menuCode: 'pilot-service:checkup-institution:list'
}
},
@@ -1,4 +1,4 @@
<!-- 缴退费财务确认 · 退费 Tab -->
<!-- 已停用:按样本缴费后退费改为协议级登记,财务页不再挂载本 Tab(保留文件供旧版对照) -->
<!-- eslint-disable vue/no-v-model-argument -->
<template>
<div class="refund-confirm-tab">
@@ -1,4 +1,4 @@
<!-- 缴退费财务确认 -->
<!-- 缴费财务确认及开票(按转账笔;退费确认 Tab 已下线) -->
<!-- eslint-disable vue/no-v-model-argument -->
<template>
<div class="page-container">
@@ -18,19 +18,7 @@
formatTabBadgeCount(paymentPendingBadge)
}}</span>
</button>
<button
type="button"
role="tab"
class="panel-pill-tabs__btn"
:class="{ 'is-active': mainTab === 'refund' }"
:aria-selected="mainTab === 'refund'"
@click="mainTab = 'refund'"
>
退费确认
<span v-if="refundPendingBadge > 0" class="panel-pill-tabs__badge">{{
formatTabBadgeCount(refundPendingBadge)
}}</span>
</button>
<!-- TODO(P1): 开票改挂 transfer;当前仍走旧 payment_detail 开票 API -->
<button
type="button"
role="tab"
@@ -68,7 +56,7 @@
<el-form-item label="关键词">
<el-input
v-model="pendingSearchForm.keyword"
placeholder="客户姓名/身份证号/新生儿姓名/新生儿身份证号"
placeholder="协议号/样本编码/客户姓名/身份证号"
clearable
@keyup.enter="handlePendingSearch"
/>
@@ -105,20 +93,23 @@
<div class="pending-card__identity">
<div class="pending-card__name">{{ row.motherName || '-' }}</div>
<div class="pending-card__sub">
{{ maskIdCard(row.motherIdCard) }}
<template v-if="row.newbornName"> · 新生儿 {{ row.newbornName }} </template>
{{ row.protocolNo || '-' }}
<template v-if="row.sampleCode"> · {{ row.sampleCode }}</template>
<template v-if="row.motherIdCard">
· {{ maskIdCard(row.motherIdCard) }}
</template>
</div>
</div>
<div class="pending-card__amount">
<div class="pending-card__amount-val">
{{ formatPaymentAmount(row.transferAmount) }}
</div>
<div class="pending-card__amount-label">实缴金额</div>
<div class="pending-card__amount-label">转账金额</div>
</div>
</div>
<div class="pending-card__grid">
<div class="pending-card__field">
<label>期数</label>
<label>期次</label>
<span>第 {{ row.installmentNo ?? '-' }} 期</span>
</div>
<div class="pending-card__field">
@@ -126,7 +117,7 @@
<span>{{ formatPaymentDueDate(row.installmentDueDate) }}</span>
</div>
<div class="pending-card__field">
<label>应缴金额</label>
<label>批次应缴</label>
<span>{{ formatPaymentAmount(row.dueAmount) }}</span>
</div>
<div class="pending-card__field">
@@ -141,6 +132,10 @@
<label>转账时间</label>
<span>{{ formatDisplayOrDash(row.transferTime) }}</span>
</div>
<div class="pending-card__field">
<label>来源</label>
<span>{{ row.source === 'presign' ? '预签约意向金' : '正式协议' }}</span>
</div>
</div>
<div class="pending-card__foot">
<div class="pending-card__meta">
@@ -195,7 +190,7 @@
<el-form-item label="关键词">
<el-input
v-model="confirmedSearchForm.keyword"
placeholder="客户姓名/身份证号/新生儿姓名/新生儿身份证号"
placeholder="协议号/样本编码/客户姓名/身份证号"
clearable
@keyup.enter="handleConfirmedSearch"
/>
@@ -244,6 +239,24 @@
(index) => (confirmedPagination.page - 1) * confirmedPagination.pageSize + index + 1
"
/>
<el-table-column
prop="protocolNo"
label="协议号"
min-width="140"
align="center"
header-align="center"
>
<template #default="{ row }">{{ row.protocolNo || '-' }}</template>
</el-table-column>
<el-table-column
prop="sampleCode"
label="样本编码"
min-width="120"
align="center"
header-align="center"
>
<template #default="{ row }">{{ row.sampleCode || '-' }}</template>
</el-table-column>
<el-table-column
prop="motherName"
label="客户姓名"
@@ -263,22 +276,17 @@
<template #default="{ row }">{{ maskIdCard(row.motherIdCard) }}</template>
</el-table-column>
<el-table-column
prop="newbornName"
label="新生儿姓名"
min-width="110"
prop="source"
label="来源"
min-width="100"
align="center"
header-align="center"
>
<template #default="{ row }">{{ row.newbornName || '-' }}</template>
</el-table-column>
<el-table-column
prop="newbornIdCard"
label="新生儿身份证号"
min-width="170"
align="center"
header-align="center"
>
<template #default="{ row }">{{ maskIdCard(row.newbornIdCard) }}</template>
<template #default="{ row }">
<el-tag size="small" :type="row.source === 'presign' ? 'warning' : 'primary'">
{{ row.source === 'presign' ? '预签约' : '正式' }}
</el-tag>
</template>
</el-table-column>
<el-table-column
prop="protocolStatus"
@@ -291,7 +299,12 @@
<el-tag
:type="protocolStatusTagType(String(row.protocolStatus || ''))"
size="small"
>{{ protocolStatusDisplayLabel(String(row.protocolStatus || ''), String(row.voidType || '')) }}</el-tag
>{{
protocolStatusDisplayLabel(
String(row.protocolStatus || ''),
String(row.voidType || '')
)
}}</el-tag
>
</template>
</el-table-column>
@@ -378,9 +391,10 @@
label="确认时间"
min-width="170"
align="center"
header-align="center">
<template #default="{ row }">{{ formatDisplayOrDash(row.updateTime) }}</template>
</el-table-column>
header-align="center"
>
<template #default="{ row }">{{ formatDisplayOrDash(row.updateTime) }}</template>
</el-table-column>
<el-table-column
prop="financeRemark"
label="财务备注"
@@ -457,11 +471,7 @@
<el-icon><Close /></el-icon>
取消
</el-button>
<el-button
:loading="rejectLoading"
:disabled="submitLoading"
@click="submitReject"
>
<el-button :loading="rejectLoading" :disabled="submitLoading" @click="submitReject">
<el-icon><RefreshLeft /></el-icon>
退回
</el-button>
@@ -503,7 +513,6 @@
</el-dialog>
</div>
<RefundConfirmTab v-if="mainTab === 'refund'" @action-done="loadTabPendingCounts" />
<InvoiceRequestTab v-if="mainTab === 'invoice'" @action-done="loadTabPendingCounts" />
</section>
</div>
@@ -514,14 +523,13 @@
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Close, Check, RefreshLeft, Search, View } from '@element-plus/icons-vue'
import { fetchProtocolInvoicePendingCount } from '@/api/collection-protocol'
import {
fetchReceiptConfirmList,
fetchReceiptConfirmedList,
fetchReceiptConfirmSubmit,
fetchReceiptConfirmReject,
fetchRefundConfirmList,
fetchProtocolInvoicePendingCount
} from '@/api/collection-protocol'
fetchPaymentTransferReceiptConfirmList,
fetchPaymentTransferReceiptConfirmedList,
fetchConfirmPaymentTransfer,
fetchRejectPaymentTransfer
} from '@/api/protocol-sample-payment'
import ProtocolFinanceConfirmFields from '@/components/business/protocol-finance-confirm-fields/index.vue'
import ClinicalPaymentConfirmTimeline from '@/components/business/clinical-payment-confirm-timeline/index.vue'
import {
@@ -533,14 +541,12 @@
import {
formatPaymentDueDate,
payModeLabel,
protocolStatusLabel,
protocolStatusDisplayLabel,
protocolStatusTagType,
loadTransferTypeDict,
transferTypeLabel
} from '@/utils/protocol-payment-labels'
import { formatMoneyDisplay } from '@/utils/money-format'
import RefundConfirmTab from './RefundConfirmTab.vue'
import InvoiceRequestTab from './InvoiceRequestTab.vue'
defineOptions({ name: 'CellStoragePaymentConfirm' })
@@ -548,14 +554,12 @@
const route = useRoute()
const mainTab = ref('payment')
const paymentPendingBadge = ref(0)
const refundPendingBadge = ref(0)
const invoicePendingBadge = ref(0)
function formatTabBadgeCount(count: number): string {
return count > 99 ? '99+' : String(count)
}
function formatPaymentAmount(v: unknown): string {
if (v == null || v === '') return '-'
return '¥' + formatMoneyDisplay(v)
@@ -591,12 +595,6 @@
return minioBaseUrl ? `${minioBaseUrl}/${url.replace(/^\//, '')}` : url
}
function handleAttachmentPreview(file: Record<string, unknown>) {
const url = String(file.fileUrl || '')
if (!url) return
window.open(fullFileUrl(url), '_blank')
}
function handleAttachmentDownload(file: Record<string, unknown>) {
downloadAttachmentFile(file, { fullFileUrl })
}
@@ -623,13 +621,11 @@
async function loadTabPendingCounts() {
try {
const [paymentRes, refundRes, invRes] = await Promise.all([
fetchReceiptConfirmList({ page: 1, pageSize: 1 }),
fetchRefundConfirmList({ page: 1, pageSize: 1 }),
const [paymentRes, invRes] = await Promise.all([
fetchPaymentTransferReceiptConfirmList({ page: 1, pageSize: 1 }),
fetchProtocolInvoicePendingCount()
])
paymentPendingBadge.value = paymentRes.total ?? 0
refundPendingBadge.value = refundRes.total ?? 0
invoicePendingBadge.value = Number(invRes?.count ?? 0)
} catch {
// 角标加载失败时保留上次数值,避免干扰主流程
@@ -639,7 +635,7 @@
async function loadPending() {
pendingLoading.value = true
try {
const res = await fetchReceiptConfirmList(buildPendingParams())
const res = await fetchPaymentTransferReceiptConfirmList(buildPendingParams())
pendingData.value = res.list || []
pendingPagination.total = res.total ?? 0
} finally {
@@ -650,7 +646,7 @@
async function loadConfirmed() {
confirmedLoading.value = true
try {
const res = await fetchReceiptConfirmedList(buildConfirmedParams())
const res = await fetchPaymentTransferReceiptConfirmedList(buildConfirmedParams())
confirmedData.value = res.list || []
confirmedPagination.total = res.total ?? 0
} finally {
@@ -707,7 +703,7 @@
}
submitLoading.value = true
try {
await fetchReceiptConfirmSubmit(currentDetailId.value, {
await fetchConfirmPaymentTransfer(currentDetailId.value, {
financeConfirmAttachments: financeAttachmentsToUrlString(attachments.value) || undefined,
financeRemark: financeRemark.value.trim() || undefined
})
@@ -730,7 +726,7 @@
}
rejectLoading.value = true
try {
await fetchReceiptConfirmReject(currentDetailId.value)
await fetchRejectPaymentTransfer(currentDetailId.value)
dialogVisible.value = false
await loadAll()
await loadTabPendingCounts()
@@ -760,10 +756,10 @@
onMounted(async () => {
await loadTransferTypeDict()
const tab = route.query.tab
if (tab === 'refund') {
mainTab.value = 'refund'
} else if (tab === 'invoice') {
if (tab === 'invoice') {
mainTab.value = 'invoice'
} else {
mainTab.value = 'payment'
}
loadTabPendingCounts()
if (mainTab.value === 'payment') {