From 550697c3464a65993288bc5586ed231fdf4aab5d Mon Sep 17 00:00:00 2001 From: jacy <18049821889@163.com> Date: Sat, 26 Sep 2026 13:04:03 +0800 Subject: [PATCH] =?UTF-8?q?feat(settlement):=20=E5=90=88=E4=BC=99=E4=BA=BA?= =?UTF-8?q?=E8=B4=A6=E5=8D=95=E7=94=9F=E6=88=90=E5=90=8E=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E5=B9=B6=E6=94=AF=E6=8C=81=E6=89=93=E6=AC=BE?= =?UTF-8?q?=E5=87=AD=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 去掉总部发送与合伙人确认,财务打款后上传凭证,合伙人即可查看状态。 Co-authored-by: Cursor --- apps/admin-web/src/pages/DashboardPage.tsx | 6 +- apps/admin-web/src/pages/PartnerBillsPage.tsx | 202 ++++------------ apps/h5-partner/src/pages/BillsPage.tsx | 215 ++++++----------- apps/h5-partner/src/pages/CenterPage.tsx | 23 +- apps/h5-partner/src/pages/SettlementPage.tsx | 29 +-- docs/杜康好客-v3编码手册.md | 4 +- docs/杜康好客-v4-PRD.md | 6 +- docs/杜康好客-v4-现状对照.md | 3 +- packages/shared-types/src/settlement.ts | 4 + server/dukang-api/prisma/schema.prisma | 1 + server/dukang-api/prisma/seed-finance-mock.ts | 6 +- .../prisma/sql/publish-partner-bills.sql | 13 ++ .../modules/ops/admin-dashboard.service.ts | 1 + .../settlement/settlement.controller.ts | 64 +---- .../modules/settlement/settlement.service.ts | 220 +++++++----------- 15 files changed, 256 insertions(+), 541 deletions(-) create mode 100644 server/dukang-api/prisma/sql/publish-partner-bills.sql diff --git a/apps/admin-web/src/pages/DashboardPage.tsx b/apps/admin-web/src/pages/DashboardPage.tsx index ae118fe..2fd293a 100644 --- a/apps/admin-web/src/pages/DashboardPage.tsx +++ b/apps/admin-web/src/pages/DashboardPage.tsx @@ -575,7 +575,7 @@ export default function DashboardPage() { 去处理} > 0 ? '#fa8c16' : undefined }} /> - 合伙人已确认申请,待总部通过或驳回 + 账单已同步合伙人,待财务打款 {(stats?.pendingPartnerDraftBills ?? 0) > 0 - ? `(另有 ${stats?.pendingPartnerDraftBills} 笔待合伙人确认)` + ? `(另有 ${stats?.pendingPartnerDraftBills} 笔零元账单未同步)` : ''} diff --git a/apps/admin-web/src/pages/PartnerBillsPage.tsx b/apps/admin-web/src/pages/PartnerBillsPage.tsx index 515b5e2..1ce1333 100644 --- a/apps/admin-web/src/pages/PartnerBillsPage.tsx +++ b/apps/admin-web/src/pages/PartnerBillsPage.tsx @@ -7,7 +7,6 @@ import { Descriptions, Drawer, Form, - Input, Modal, Select, Space, @@ -29,6 +28,7 @@ import { useAdminList } from '../lib/useAdminList'; import { useAdminListColumns } from '../lib/useAdminListColumns'; import { AdminListHeader } from '../components/AdminListHeader'; import { AdminPrimaryLink } from '../components/AdminPrimaryLink'; +import { FinancePayProofModal, PaymentProofGallery } from '../components/FinancePayProof'; type BillItemRow = { @@ -56,6 +56,7 @@ type Row = { periodEnd: string; rejectReason?: string | null; paymentRef?: string | null; + paymentProofUrls?: string[]; paidAt?: string | null; orderItems?: BillItemRow[]; redeemItems?: BillItemRow[]; @@ -80,7 +81,7 @@ type Row = { type PartnerOption = { id: string; companyName?: string | null; name?: string | null; phone?: string }; const STATUS_LABELS: Record = { - PENDING_REVIEW: '待审核', + PENDING_REVIEW: '零元未同步', AWAITING_CONFIRM: '待合伙人确认', UNPAID: '未打款', PAID: '已打款', @@ -131,13 +132,11 @@ export default function PartnerBillsPage() { const [genOpen, setGenOpen] = useState(false); const [genForm] = Form.useForm(); const [exporting, setExporting] = useState(false); - const [rejectOpen, setRejectOpen] = useState(false); - const [rejectTarget, setRejectTarget] = useState(null); - const [rejectForm] = Form.useForm(); - const [rejecting, setRejecting] = useState(false); const [selectedKeys, setSelectedKeys] = useState([]); const [detail, setDetail] = useState(null); const [drawerOpen, setDrawerOpen] = useState(false); + const [payModal, setPayModal] = useState<{ ids: string[]; amountHint?: number } | null>(null); + const [paySubmitting, setPaySubmitting] = useState(false); useEffect(() => { void request>(`/admin/partners?pageSize=${ADMIN_OPTIONS_PAGE_SIZE}`) @@ -158,101 +157,14 @@ export default function PartnerBillsPage() { method: 'POST', body: JSON.stringify({ partnerId: values.partnerId, weekStartYmd }), }); - message.success('账单已生成(待审核)'); + message.success('账单已生成并同步到合伙人'); } setGenOpen(false); reload(); } - function sendBills(ids: string[]) { - Modal.confirm({ - title: '发送给合伙人?', - content: `将发送 ${ids.length} 笔账单到合伙人端,状态变为「待合伙人确认」。`, - okText: '确认发送', - cancelText: '取消', - onOk: async () => { - if (ids.length === 1) { - await request(`/admin/partner-bills/${ids[0]}/send`, { method: 'POST' }); - } else { - await request('/admin/partner-bills/batch-send', { - method: 'POST', - body: JSON.stringify({ ids }), - }); - } - message.success('已发送'); - setSelectedKeys([]); - reload(); - }, - }); - } - function markPaid(ids: string[], amountHint?: number) { - let paymentRef = ''; - Modal.confirm({ - title: '确认打款?', - content: ( -
-
- 将标记 {ids.length} 笔账单为已打款 - {amountHint != null ? `,合计约 ¥${amountHint.toFixed(2)}` : ''}。 -
- { - paymentRef = e.target.value; - }} - /> -
- ), - okText: '确认打款', - cancelText: '取消', - onOk: async () => { - const body = JSON.stringify({ paymentRef: paymentRef.trim() || undefined }); - if (ids.length === 1) { - await request(`/admin/partner-bills/${ids[0]}/mark-paid`, { method: 'POST', body }); - } else { - await request('/admin/partner-bills/batch-mark-paid', { - method: 'POST', - body: JSON.stringify({ ids }), - }); - } - message.success('已标记打款'); - setSelectedKeys([]); - reload(); - }, - }); - } - - function openReject(row: Row) { - Modal.confirm({ - title: '驳回该账单?', - content: '驳回后合伙人需重新等待总部发送。请在下一步填写理由。', - okText: '继续填写理由', - cancelText: '取消', - onOk: () => { - setRejectTarget(row); - rejectForm.resetFields(); - setRejectOpen(true); - }, - }); - } - - async function submitReject(values: { reason: string }) { - if (!rejectTarget) return; - setRejecting(true); - try { - await request(`/admin/partner-bills/${rejectTarget.id}/reject`, { - method: 'POST', - body: JSON.stringify({ reason: values.reason.trim() }), - }); - message.success('已驳回'); - setRejectOpen(false); - setRejectTarget(null); - reload(); - } finally { - setRejecting(false); - } + setPayModal({ ids, amountHint }); } async function openDetail(id: string) { @@ -282,10 +194,7 @@ export default function PartnerBillsPage() { const partnerName = (r: Row) => partnerDashLabel(r.partnerAccount ?? r.partner); const partnerBank = (r: Row) => r.partnerAccount ?? r.partner; const selectedRows = (data?.items ?? []).filter((r) => selectedKeys.includes(r.id)); - const canSend = selectedRows.filter( - (r) => (r.status === 'PENDING_REVIEW' || r.status === 'REJECTED') && Number(r.totalAmount) > 0, - ); - const canPay = selectedRows.filter((r) => r.status === 'UNPAID'); + const canPay = selectedRows.filter((r) => r.status === 'UNPAID' && Number(r.totalAmount) > 0); const payAmount = canPay.reduce((s, r) => s + Number(r.totalAmount), 0); const baseColumns: ColumnsType = [ @@ -381,24 +290,9 @@ export default function PartnerBillsPage() { - {(row.status === 'PENDING_REVIEW' || row.status === 'REJECTED') && Number(row.totalAmount) > 0 && ( - - )} - {row.status === 'UNPAID' && ( - <> - - - - )} - {row.status === 'AWAITING_CONFIRM' && ( - )} @@ -414,7 +308,7 @@ export default function PartnerBillsPage() { {summary && ( @@ -490,11 +384,6 @@ export default function PartnerBillsPage() { 导出 Excel - - - - { - setRejectOpen(false); - setRejectTarget(null); + setPayModal(null)} + onOk={async (payload) => { + if (!payModal) return; + const { ids } = payModal; + setPaySubmitting(true); + try { + const body = JSON.stringify({ ...payload, ...(ids.length > 1 ? { ids } : {}) }); + if (ids.length === 1) { + await request(`/admin/partner-bills/${ids[0]}/mark-paid`, { method: 'POST', body }); + } else { + await request('/admin/partner-bills/batch-mark-paid', { method: 'POST', body }); + } + message.success('已标记打款'); + setPayModal(null); + setSelectedKeys([]); + if (detail && ids.includes(detail.id)) { + const d = await request(`/admin/partner-bills/${detail.id}`); + setDetail(d); + } + reload(); + } finally { + setPaySubmitting(false); + } }} - footer={null} - destroyOnClose - > - - 账单 {rejectTarget?.billNo} · {rejectTarget ? partnerName(rejectTarget) : ''} - -
void submitReject(v)}> - - - - -
-
+ /> {detail.paidAt ? fmtTime(detail.paidAt) : '—'} - + {detail.paymentRef ? String(detail.paymentRef) : '—'} + + + +

打款凭证

+ {paymentRef ?

凭证号 {paymentRef}

: null} + {list.length > 0 && ( +
+ {list.map((url) => ( + + 打款凭证 + + ))} +
+ )} + + ); } export default function BillsPage() { @@ -31,10 +48,7 @@ export default function BillsPage() { const navigate = useNavigate(); const [searchParams] = useSearchParams(); const [bills, setBills] = useState([]); - const [confirmed, setConfirmed] = useState(false); - const [submitting, setSubmitting] = useState(false); const [loading, setLoading] = useState(true); - const [selectedIds, setSelectedIds] = useState([]); const [detail, setDetail] = useState(null); async function loadBills() { @@ -55,18 +69,12 @@ export default function BillsPage() { }, [navigate]); const billId = searchParams.get('id'); - const actionable = useMemo( - () => bills.filter((b) => canConfirm(b.status)), - [bills], - ); const bill = billId - ? bills.find((b) => String(b.id) === billId) ?? actionable[0] ?? bills[0] - : actionable[0] ?? bills[0]; + ? bills.find((b) => String(b.id) === billId) ?? bills[0] + : bills.find((b) => b.status === 'UNPAID') ?? bills[0]; const status = String(bill?.status || ''); - const showApplyForm = !!bill && canConfirm(status); const isUnpaid = status === 'UNPAID'; - const isRejected = status === 'REJECTED'; const isPaid = status === 'PAID'; useEffect(() => { @@ -107,80 +115,42 @@ export default function BillsPage() { ); } - function askConfirm(ids: string[]) { - if (!window.confirm(`确认 ${ids.length} 笔账单无误并提交?确认后状态将变为「未打款」,等待总部打款。`)) { - return; - } - void doConfirm(ids); - } - - async function doConfirm(ids: string[]) { - setSubmitting(true); - try { - if (ids.length === 1) { - await request('PARTNER_H5', `/partner/settlement/bills/${ids[0]}/confirm`, { method: 'POST' }); - } else { - await request('PARTNER_H5', '/partner/settlement/bills/batch-confirm', { - method: 'POST', - body: JSON.stringify({ ids }), - }); - } - toastSuccess('已确认,等待总部打款'); - setConfirmed(false); - setSelectedIds([]); - await loadBills(); - } catch (e) { - toastError(e instanceof Error ? e.message : '提交失败'); - } finally { - setSubmitting(false); - } - } - - function toggleSelect(id: string) { - setSelectedIds((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id])); - } + const proofUrls = detail?.paymentProofUrls ?? bill?.paymentProofUrls; + const paymentRef = detail?.paymentRef ?? bill?.paymentRef; return ( - navigate('/center/settlement')} /> + navigate('/center/settlement')} />
check
- 数据核算 + 账单出具
-
- {showApplyForm && !confirmed ? '2' : ( - check - )} -
- 账单确认 -
-
-
+
{isPaid ? ( check - ) : '3'} + ) : '2'}
- - {isRejected ? '已驳回' : isPaid ? '已打款' : '总部打款'} + + {isPaid ? '已打款' : '财务打款'}
{loading &&
加载中…
} - {!loading && !bill &&
暂无待确认账单
} + {!loading && !bill &&
暂无账单
} {bill && (
@@ -192,26 +162,25 @@ export default function BillsPage() {

{String(bill.billNo || '周结算账单')}

{formatBillPeriod(bill.periodStart, bill.periodEnd)}

- + {billStatusLabel(status)}
- {isRejected && bill.rejectReason && ( -
- error -
-

账单已驳回

-

{bill.rejectReason}

-
-
- )} - {isUnpaid && (
hourglass_top

- 您已确认账单,总部打款中,请耐心等待。 + 账单已同步,等待财务打款。 +

+
+ )} + + {isPaid && ( +
+ check_circle +

+ 财务已打款{bill.paidAt ? `(${String(bill.paidAt).slice(0, 10)})` : ''}。

)} @@ -226,94 +195,42 @@ export default function BillsPage() { {renderItemList('酒订单', detail?.orderItems, Number(detail?.orderCommission ?? bill.orderCommission ?? 0))} {renderItemList('核销订单', detail?.redeemItems, Number(detail?.redeemCommission ?? bill.redeemCommission ?? 0))} + {isPaid && } )} {bills.length > 0 && ( -
-
-

全部账单

- {actionable.length > 1 && ( - - )} -
+
+

全部账单

{bills.map((b) => { const id = String(b.id); - const selectable = canConfirm(b.status); + const active = bill && String(bill.id) === id; return ( -
navigate(`/center/bills?id=${id}`)} > - {selectable ? ( - toggleSelect(id)} - style={{ marginTop: 4 }} - /> - ) : ( - - )} - -
+ ¥{Number(b.totalAmount).toFixed(2)} +
+ ); })}
)} - - {showApplyForm && ( - <> -
- info -

- 确认后账单变为「未打款」,由总部完成打款。如有异议请先联系城市运营经理。 -

-
- -
- - -
- - )}
); } diff --git a/apps/h5-partner/src/pages/CenterPage.tsx b/apps/h5-partner/src/pages/CenterPage.tsx index cead5e3..7d96cad 100644 --- a/apps/h5-partner/src/pages/CenterPage.tsx +++ b/apps/h5-partner/src/pages/CenterPage.tsx @@ -62,19 +62,14 @@ export default function CenterPage({ variant = 'primary', roleLabel }: CenterPag }, [loadCenter]); const finance = useMemo(() => { - const pending = bills.filter((b) => b.status === 'AWAITING_CONFIRM'); + const unpaid = bills.filter((b) => b.status === 'UNPAID'); const settled = bills.filter((b) => b.status === 'PAID'); - const paidCount = settled.length; - const rejectedCount = bills.filter((b) => b.status === 'REJECTED').length; return { - pendingBillCount: pending.length, - rejectedCount, - pendingTotal: bills - .filter((b) => b.status === 'AWAITING_CONFIRM' || b.status === 'UNPAID' || b.status === 'REJECTED') - .reduce((s, b) => s + Number(b.totalAmount || 0), 0), + unpaidCount: unpaid.length, + pendingTotal: unpaid.reduce((s, b) => s + Number(b.totalAmount || 0), 0), settledTotal: settled.reduce((s, b) => s + Number(b.totalAmount || 0), 0), balance: settled.reduce((s, b) => s + Number(b.totalAmount || 0), 0), - paidCount, + paidCount: settled.length, }; }, [bills]); @@ -167,20 +162,16 @@ export default function CenterPage({ variant = 'primary', roleLabel }: CenterPag - {finance.pendingBillCount > 0 && ( + {finance.unpaidCount > 0 && (
receipt_long
-

- {finance.rejectedCount > 0 ? '待处理账单' : '待确认账单'} -

+

待打款账单

- {finance.rejectedCount > 0 - ? `您有 ${finance.pendingBillCount} 笔账单待处理(含 ${finance.rejectedCount} 笔已驳回)` - : `您有 ${finance.pendingBillCount} 笔账单待确认`} + 您有 {finance.unpaidCount} 笔账单待财务打款

diff --git a/apps/h5-partner/src/pages/SettlementPage.tsx b/apps/h5-partner/src/pages/SettlementPage.tsx index 3736726..bc23384 100644 --- a/apps/h5-partner/src/pages/SettlementPage.tsx +++ b/apps/h5-partner/src/pages/SettlementPage.tsx @@ -6,7 +6,7 @@ import type { PartnerBillDto, PartnerSettlementPreviewDto } from '@dukang/shared import { isLoggedIn, request } from '../lib/api'; import { formatBillPeriod, formatIssueLabel } from '../lib/settlement'; -type StatusFilter = 'all' | 'pending' | 'settled' | 'reviewing' | 'rejected'; +type StatusFilter = 'all' | 'unpaid' | 'paid'; function fmtMoney(n: number) { return n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); @@ -14,20 +14,16 @@ function fmtMoney(n: number) { function billStatusLabel(status: string) { switch (status) { - case 'AWAITING_CONFIRM': return '待确认'; case 'UNPAID': return '未打款'; case 'PAID': return '已打款'; - case 'REJECTED': return '已驳回'; default: return status; } } function billStatusClass(status: string) { switch (status) { - case 'AWAITING_CONFIRM': return 'partner-settlement-status--pending'; case 'UNPAID': return 'partner-settlement-status--reviewing'; case 'PAID': return 'partner-settlement-status--settled'; - case 'REJECTED': return 'partner-settlement-status--rejected'; default: return ''; } } @@ -57,10 +53,10 @@ export default function SettlementPage() { }, [navigate, loadBills]); const summary = useMemo(() => { - const pending = bills.filter((b) => b.status === 'AWAITING_CONFIRM'); + const unpaid = bills.filter((b) => b.status === 'UNPAID'); const settled = bills.filter((b) => b.status === 'PAID'); return { - pendingTotal: pending.reduce((s, b) => s + Number(b.totalAmount || 0), 0), + pendingTotal: unpaid.reduce((s, b) => s + Number(b.totalAmount || 0), 0), settledTotal: settled.reduce((s, b) => s + Number(b.totalAmount || 0), 0), }; }, [bills]); @@ -68,10 +64,8 @@ export default function SettlementPage() { const filteredBills = useMemo(() => { return bills.filter((b) => { if (statusFilter === 'all') return true; - if (statusFilter === 'pending') return b.status === 'AWAITING_CONFIRM'; - if (statusFilter === 'settled') return b.status === 'PAID'; - if (statusFilter === 'reviewing') return b.status === 'UNPAID'; - if (statusFilter === 'rejected') return b.status === 'REJECTED'; + if (statusFilter === 'unpaid') return b.status === 'UNPAID'; + if (statusFilter === 'paid') return b.status === 'PAID'; return true; }); }, [bills, statusFilter]); @@ -82,10 +76,8 @@ export default function SettlementPage() { const statusTabs: Array<{ key: StatusFilter; label: string }> = [ { key: 'all', label: '全部' }, - { key: 'pending', label: '待确认' }, - { key: 'reviewing', label: '未打款' }, - { key: 'rejected', label: '已驳回' }, - { key: 'settled', label: '已打款' }, + { key: 'unpaid', label: '未打款' }, + { key: 'paid', label: '已打款' }, ]; return ( @@ -164,12 +156,7 @@ export default function SettlementPage() { 订单分佣 ¥{fmtMoney(Number(bill.orderCommission || 0))}

- {bill.status === 'REJECTED' && bill.rejectReason ? ( -

- 驳回:{bill.rejectReason} -

- ) : null} -

+

¥{fmtMoney(Number(bill.totalAmount || 0))}

diff --git a/docs/杜康好客-v3编码手册.md b/docs/杜康好客-v3编码手册.md index 6135a91..d6b81cd 100644 --- a/docs/杜康好客-v3编码手册.md +++ b/docs/杜康好客-v3编码手册.md @@ -72,14 +72,14 @@ C 端购酒核销 · 门店扫码核销+打款 · 合伙人拓店履约 · WebAd **推广码渠道负责人 / 关联合伙人(v4.0.20)**:规则见 v4-PRD §2.1 与 [`v4.0.20 开发文档`](./杜康好客-v4.0.20-开发文档.md)。表 `promo_code_channel_owner`(多对多主合伙人)+ `common_promo_code.assoc_partner_account_id`。HQ `POST/PUT /admin/promo-codes` 字段 `channelOwnerPartnerIds`、`assocPartnerAccountId`(须主账号 ACTIVE)。`POST /promo/touch` 登录后对关联合伙人 `PartnerCityService.tryBindIfUnbound`(已绑他人静默跳过,不回刷历史;Promo 不 import StoreModule)。合伙人主账号 `GET /partner/promo-codes` 仅返回 `scanCount` / `attributionCount` / `orderCount`(已完成订单),禁止用户/事件/订单明细。H5 入口:合伙人中心 → 运营管理 → 推广码数据。验收:只配渠道负责人不绑用户;只配关联合伙人会进「关联用户」且能看三项汇总;已关联他人扫码流程不中断。 -**合伙人周结算(v4.0.9)**:每周一 08:00 生成上一自然周账单。`GET /partner/settlement/cycle` 账期与出账日;`GET /partner/settlement/preview` 本周一至今预付款预估。零元账单 HQ 可见待审核、不可发送、合伙人端不可见。历史月账不回刷。 +**合伙人周结算(v4.0.9,同步规则已改)**:每周一 08:00 生成上一自然周账单。应付大于 0 即「未打款」并同步合伙人端,无发送/确认/驳回。财务 `POST /admin/partner-bills/:id/mark-paid`(及 batch)标记已打款,body 可带 `paymentRef`、`paymentProofUrls`。已打款不可重算。`GET /partner/settlement/cycle` 账期与出账日;`GET /partner/settlement/preview` 本周一至今预付款预估。零元账单 HQ 可见待审核、合伙人端不可见。历史月账不回刷。 ## 5. 验收用例(必过) **主链路 15 项**:登录、4 SKU、起购、支付+权益、双通道核销、payout、关店不可见、拓店审核、配送完成、退款、T+1/T+30… **后台 8 项**:商品/门店/订单/权益/核销/工单/日志/财务。 **开发计划任务**:状态 `TODO` 待开发 / `IN_PROGRESS` 开发中 / `DEVELOPED` 已开发 / `RELEASED` 已上线 / `STOPPED` 已停止;版本状态含 `STOPPED` 已停止,关联任务随版本同步(仅当全部关联版本已停止时任务才为已停止)。任务列表「来源工单」链到 `/tickets/support?id=`。 -**财务账单日**:出账当天北京日历日(门店/酒厂 `billDate`;物流账期按北京自然月;合伙人周账按北京自然周,周一 08:00 出上周)。禁止 `toISOString().slice(0,10)` 或服务器本地 `Date` 午夜当账单日。酒厂应付为 0 仍出账(展示无需打款)。合伙人零元账单不同步给合伙人端。 +**财务账单日**:出账当天北京日历日(门店/酒厂 `billDate`;物流账期按北京自然月;合伙人周账按北京自然周,周一 08:00 出上周)。禁止 `toISOString().slice(0,10)` 或服务器本地 `Date` 午夜当账单日。酒厂应付为 0 仍出账(展示无需打款)。合伙人应付大于 0 的账单生成即同步;零元账单不同步给合伙人端。财务打款上传凭证后合伙人可见状态与凭证。 ## 6. 技术债(摘要) diff --git a/docs/杜康好客-v4-PRD.md b/docs/杜康好客-v4-PRD.md index 2e74fb7..357b757 100644 --- a/docs/杜康好客-v4-PRD.md +++ b/docs/杜康好客-v4-PRD.md @@ -84,9 +84,11 @@ | 酒订单 | 账期内已付且 `partner_account_id_at_pay` = 本主合伙人 | 实付 × 支付快照费率 | | 核销订单 | 账期内名下门店核销 | 核销额 × 出账时核销费率 | -HQ 财务详情与合伙人确认页均展示两段列表。不再「无快照则全城已付单 × 当前费率」。 +HQ 财务详情与合伙人账单页均展示两段列表。不再「无快照则全城已付单 × 当前费率」。 -- **零元账单**:佣金合计为 0 时 HQ 仍可生成并留在待审核,**不得发送**给合伙人;合伙人端列表/详情不可见。 +- **同步**:应付大于 0 的账单生成后即为「未打款」,直接出现在合伙人端。无总部发送、无合伙人确认、无驳回。 +- **打款**:财务打款后在总部标记已打款,可填凭证号并上传打款凭证照片。合伙人可查看状态与凭证。已打款不可重算;未打款可重算。 +- **零元账单**:佣金合计为 0 时 HQ 仍可生成并留在待审核,不同步给合伙人;合伙人端列表/详情不可见。 - 合伙人 H5 展示当前账期(本周一~本周日)与下次出账时间(下周一 08:00,若尚未过本周一 08:00 则为当日 08:00)。 - **预付款**:当前未出账周期(本周一 00:00 至今)酒单 + 核销按佣金比例算出的预估合计。展示在首页与合伙人中心。与实际打款无关,实际以账单为准。 - 上线前已生成的月账保留,不回刷。 diff --git a/docs/杜康好客-v4-现状对照.md b/docs/杜康好客-v4-现状对照.md index d60f32c..e829c2c 100644 --- a/docs/杜康好客-v4-现状对照.md +++ b/docs/杜康好客-v4-现状对照.md @@ -9,7 +9,7 @@ |------|------| | 版本线 | **v4.0.21** 接口访问(含 v4.0.20 推广码渠道负责人 + 关联合伙人) | | 订单佣金 | 区县归属已删除;只认关联 / 代下单选择 | -| 账单 | 酒订单 / 核销订单分列;合伙人改为周账(周一 08:00);零元不同步合伙人;酒厂含现场提货,零应付仍出账(无需打款) | +| 账单 | 酒订单 / 核销订单分列;合伙人周账(周一 08:00)生成后应付大于 0 直接同步(未打款),财务打款并上传凭证;零元不同步;酒厂含现场提货,零应付仍出账(无需打款) | | 活动图 | HQ 上传底图/码栏/文案;**v4.0.15 上传超限自动压缩并提示尺寸**;合伙人选择写入库;HQ 可指定一张图为勾选主合伙人合成下载;子账号不可看活动图 | ## 1. 版本交付 @@ -47,3 +47,4 @@ | 2026-09-16 | v4.0.20:推广码渠道负责人改为可多选主合伙人(H5 只看扫码/归因/订单数);关联合伙人扫码 first-lock,不回刷、已关联他人静默跳过 | | 2026-09-17 | v4.0.20:PromoModule 改走 CityScope 避免 Nest 循环依赖;本地 Vite 代理默认 `127.0.0.1:3010` | | 2026-09-26 | v4.0.21:HQ 接口访问。登录/商品加载/门店加载/门店提交按成功百分比放行;审核通知按百分比丢弃企微;订单/核销/账单/门店审核/套餐修改只做通知总开关。总部登录不参与 | +| 2026-09-26 | 合伙人账单:应付大于 0 生成即同步合伙人端;去掉总部发送、合伙人确认与驳回;财务标记已打款并上传凭证,合伙人可见状态与凭证 | diff --git a/packages/shared-types/src/settlement.ts b/packages/shared-types/src/settlement.ts index fb3b146..910dc64 100644 --- a/packages/shared-types/src/settlement.ts +++ b/packages/shared-types/src/settlement.ts @@ -146,9 +146,11 @@ export type PartnerBillStatus = export const PARTNER_BILL_STATUS_LABELS: Record = { PENDING_REVIEW: '待审核', + /** 历史状态:新账单不再进入 */ AWAITING_CONFIRM: '待合伙人确认', UNPAID: '未打款', PAID: '已打款', + /** 历史状态:新账单不再进入 */ REJECTED: '已驳回', }; @@ -184,6 +186,8 @@ export interface PartnerBillDto { confirmedAt?: string | null; sentAt?: string | null; paidAt?: string | null; + paymentRef?: string | null; + paymentProofUrls?: string[]; rejectReason?: string | null; } diff --git a/server/dukang-api/prisma/schema.prisma b/server/dukang-api/prisma/schema.prisma index 6ba6eb6..7713539 100644 --- a/server/dukang-api/prisma/schema.prisma +++ b/server/dukang-api/prisma/schema.prisma @@ -1387,6 +1387,7 @@ model PartnerBill { totalAmount Decimal @map("total_amount") @db.Decimal(10, 2) status PartnerBillStatus @default(PENDING_REVIEW) paymentRef String? @map("payment_ref") @db.VarChar(128) + paymentProofUrls Json? @map("payment_proof_urls") confirmedAt DateTime? @map("confirmed_at") @db.DateTime(3) sentAt DateTime? @map("sent_at") @db.DateTime(3) paidAt DateTime? @map("paid_at") @db.DateTime(3) diff --git a/server/dukang-api/prisma/seed-finance-mock.ts b/server/dukang-api/prisma/seed-finance-mock.ts index 24c205d..a027670 100644 --- a/server/dukang-api/prisma/seed-finance-mock.ts +++ b/server/dukang-api/prisma/seed-finance-mock.ts @@ -118,7 +118,7 @@ async function main() { }, }); - // ─── 2. 合伙人月账单(上月待确认 + 本月已打款样例)─── + // ─── 2. 合伙人月账单(上月未打款 + 更早已打款样例)─── const partnerBillAwait = await prisma.partnerBill.upsert({ where: { partnerAccountId_periodStart: { @@ -134,7 +134,7 @@ async function main() { orderCommission: 860, redeemCommission: 420, totalAmount: 1280, - status: 'AWAITING_CONFIRM', + status: 'UNPAID', sentAt: yesterday, }, update: { @@ -142,7 +142,7 @@ async function main() { orderCommission: 860, redeemCommission: 420, totalAmount: 1280, - status: 'AWAITING_CONFIRM', + status: 'UNPAID', sentAt: yesterday, paidAt: null, rejectReason: null, diff --git a/server/dukang-api/prisma/sql/publish-partner-bills.sql b/server/dukang-api/prisma/sql/publish-partner-bills.sql new file mode 100644 index 0000000..60a4016 --- /dev/null +++ b/server/dukang-api/prisma/sql/publish-partner-bills.sql @@ -0,0 +1,13 @@ +-- 应付大于 0 的合伙人账单直接同步为未打款(去掉发送 / 合伙人确认 / 驳回)。 +-- 零元账单保持 PENDING_REVIEW,不同步给合伙人。 +-- 打款凭证照片列与门店账单一致。 + +ALTER TABLE `partner_bill` + ADD COLUMN `payment_proof_urls` JSON NULL AFTER `payment_ref`; + +UPDATE `partner_bill` +SET `status` = 'UNPAID', + `reject_reason` = NULL, + `sent_at` = COALESCE(`sent_at`, CURRENT_TIMESTAMP(3)) +WHERE `total_amount` > 0 + AND `status` IN ('PENDING_REVIEW', 'AWAITING_CONFIRM', 'REJECTED'); diff --git a/server/dukang-api/src/modules/ops/admin-dashboard.service.ts b/server/dukang-api/src/modules/ops/admin-dashboard.service.ts index 975ea95..9f79092 100644 --- a/server/dukang-api/src/modules/ops/admin-dashboard.service.ts +++ b/server/dukang-api/src/modules/ops/admin-dashboard.service.ts @@ -185,6 +185,7 @@ export class AdminDashboardService { ? this.prisma.partnerBill.count({ where: { status: 'PENDING_REVIEW', + totalAmount: { lte: 0 }, ...(cityFilter ? { partnerAccount: { cityId: cityFilter } } : {}), }, }) diff --git a/server/dukang-api/src/modules/settlement/settlement.controller.ts b/server/dukang-api/src/modules/settlement/settlement.controller.ts index 88efd24..989ccab 100644 --- a/server/dukang-api/src/modules/settlement/settlement.controller.ts +++ b/server/dukang-api/src/modules/settlement/settlement.controller.ts @@ -97,16 +97,6 @@ export class SettlementController { billDetail(@CurrentUser() user: AuthUser, @Param('id') id: string) { return this.settlementService.getPartnerBill(user.actorId, BigInt(id)); } - - @Post('bills/batch-confirm') - batchConfirm(@CurrentUser() user: AuthUser, @Body() body: { ids: string[] }) { - return this.settlementService.batchPartnerConfirmBills(user.actorId, body.ids ?? []); - } - - @Post('bills/:id/confirm') - confirm(@CurrentUser() user: AuthUser, @Param('id') id: string) { - return this.settlementService.partnerConfirmBill(user.actorId, BigInt(id)); - } } @Controller('shop/payouts') @@ -432,17 +422,6 @@ export class AdminPartnerBillController { }); } - @Post('batch-send') - @HqOperation({ - action: HqOperationAction.PARTNER_BILL_BATCH_SEND, - refType: 'PARTNER_BILL', - batch: true, - includeBody: true, - }) - batchSend(@Body() body: { ids: string[] }) { - return this.settlementService.batchSendPartnerBills(body.ids ?? []); - } - @Post('batch-mark-paid') @HqOperation({ action: HqOperationAction.PARTNER_BILL_BATCH_MARK_PAID, @@ -450,8 +429,8 @@ export class AdminPartnerBillController { batch: true, includeBody: true, }) - batchMarkPaid(@Body() body: { ids: string[] }) { - return this.settlementService.batchMarkPartnerBillsPaid(body.ids ?? []); + batchMarkPaid(@Body() body: { ids: string[]; paymentRef?: string; paymentProofUrls?: string[] }) { + return this.settlementService.batchMarkPartnerBillsPaid(body.ids ?? [], body); } @Get(':id') @@ -459,26 +438,6 @@ export class AdminPartnerBillController { return this.settlementService.getAdminPartnerBill(BigInt(id)); } - @Post(':id/send') - @HqOperation({ - action: HqOperationAction.PARTNER_BILL_SEND, - refType: 'PARTNER_BILL', - refIdParam: 'id', - }) - send(@Param('id') id: string) { - return this.settlementService.sendPartnerBill(BigInt(id)); - } - - @Post(':id/confirm') - @HqOperation({ - action: HqOperationAction.PARTNER_BILL_CONFIRM, - refType: 'PARTNER_BILL', - refIdParam: 'id', - }) - confirm(@Param('id') id: string) { - return this.settlementService.confirmPartnerBill(BigInt(id)); - } - @Post(':id/mark-paid') @HqOperation({ action: HqOperationAction.PARTNER_BILL_MARK_PAID, @@ -486,23 +445,12 @@ export class AdminPartnerBillController { refIdParam: 'id', includeBody: true, }) - markPaid(@Param('id') id: string, @Body() body: { paymentRef?: string }) { + markPaid( + @Param('id') id: string, + @Body() body: { paymentRef?: string; paymentProofUrls?: string[] }, + ) { return this.settlementService.markPartnerBillPaid(BigInt(id), body); } - - @Post(':id/reject') - @HqOperation({ - action: HqOperationAction.PARTNER_BILL_REJECT, - refType: 'PARTNER_BILL', - refIdParam: 'id', - includeBody: true, - }) - reject(@Param('id') id: string, @Body() body: { reason?: string }) { - if (!body?.reason?.trim()) { - throw new BadRequestException('请填写驳回理由'); - } - return this.settlementService.rejectPartnerBill(BigInt(id), body.reason); - } } @Controller('admin/winery-bills') diff --git a/server/dukang-api/src/modules/settlement/settlement.service.ts b/server/dukang-api/src/modules/settlement/settlement.service.ts index bc566f1..49bc7ad 100644 --- a/server/dukang-api/src/modules/settlement/settlement.service.ts +++ b/server/dukang-api/src/modules/settlement/settlement.service.ts @@ -119,6 +119,16 @@ function withPartnerBillDates( }; } +function mapPartnerBillView(row: T) { + const { paymentProofUrls, ...rest } = row; + return { + ...withPartnerBillDates(rest), + paymentProofUrls: parsePaymentProofUrls(paymentProofUrls), + }; +} + +const PARTNER_BILL_PUBLISH_STATUSES = ['PENDING_REVIEW', 'AWAITING_CONFIRM', 'REJECTED'] as const; + function resolvePartnerWeekPeriod(weekStartYmd: string): { periodStart: Date; periodEnd: Date; @@ -284,6 +294,12 @@ export class SettlementService implements OnModuleInit { } catch (e) { this.logger.warn(`Winery bill date align skipped: ${e instanceof Error ? e.message : e}`); } + try { + const published = await this.publishOutstandingPartnerBills(); + if (published > 0) this.logger.log(`Partner bills published to partners: ${published}`); + } catch (e) { + this.logger.warn(`Partner bill publish skipped: ${e instanceof Error ? e.message : e}`); + } } /** @@ -1722,12 +1738,33 @@ export class SettlementService implements OnModuleInit { // ─── Partner bills ─────────────────────────────────── + /** 应付大于 0 且仍停在审核/待确认/驳回的账单,直接改为未打款并同步合伙人。 */ + private async publishOutstandingPartnerBills(): Promise { + const alreadySent = await this.prisma.partnerBill.updateMany({ + where: { + totalAmount: { gt: 0 }, + status: { in: [...PARTNER_BILL_PUBLISH_STATUSES] }, + sentAt: { not: null }, + }, + data: { status: 'UNPAID', rejectReason: null }, + }); + const fresh = await this.prisma.partnerBill.updateMany({ + where: { + totalAmount: { gt: 0 }, + status: { in: [...PARTNER_BILL_PUBLISH_STATUSES] }, + }, + data: { status: 'UNPAID', sentAt: new Date(), rejectReason: null }, + }); + return alreadySent.count + fresh.count; + } + async listPartnerBills(partnerAccountId: bigint) { + await this.publishOutstandingPartnerBills(); const primary = await this.partnerCityService.resolvePrimaryAccount(partnerAccountId); const bills = await this.prisma.partnerBill.findMany({ where: { partnerAccountId: primary.id, - status: { in: ['AWAITING_CONFIRM', 'UNPAID', 'PAID', 'REJECTED'] }, + status: { in: ['UNPAID', 'PAID'] }, totalAmount: { gt: 0 }, }, orderBy: { createdAt: 'desc' }, @@ -1737,7 +1774,7 @@ export class SettlementService implements OnModuleInit { eventName: 'partner_bill_view', extraJson: { count: bills.length }, }); - return serializeBigInt(bills.map(withPartnerBillDates)); + return serializeBigInt(bills.map(mapPartnerBillView)); } getPartnerSettlementCycle(anchor = new Date()) { @@ -1793,6 +1830,7 @@ export class SettlementService implements OnModuleInit { } async getPartnerBill(partnerAccountId: bigint, billId: bigint) { + await this.publishOutstandingPartnerBills(); const primary = await this.partnerCityService.resolvePrimaryAccount(partnerAccountId); const bill = await this.prisma.partnerBill.findFirst({ where: { id: billId, partnerAccountId: primary.id }, @@ -1800,6 +1838,9 @@ export class SettlementService implements OnModuleInit { }); if (!bill) throw new NotFoundException('账单不存在'); if (Number(bill.totalAmount) <= 0) throw new NotFoundException('账单不存在'); + if (bill.status !== 'UNPAID' && bill.status !== 'PAID') { + throw new NotFoundException('账单不存在'); + } this.analyticsService.trackPartnerOneSafe(partnerAccountId, 'PARTNER_H5', { partnerAccountId: primary.id, eventName: 'partner_bill_detail_view', @@ -1809,7 +1850,7 @@ export class SettlementService implements OnModuleInit { }); const { items, ...header } = bill; return serializeBigInt({ - ...withPartnerBillDates(header), + ...mapPartnerBillView(header), partnerId: header.partnerAccountId.toString(), ...splitPartnerBillItems(items), }); @@ -1824,6 +1865,7 @@ export class SettlementService implements OnModuleInit { month?: number; weekStartYmd?: string; }) { + await this.publishOutstandingPartnerBills(); const page = query.page ?? 1; const pageSize = query.pageSize ?? 20; const where = this.buildPartnerBillWhere(query); @@ -1857,7 +1899,7 @@ export class SettlementService implements OnModuleInit { ]); const items = rawItems.map((b) => ({ - ...withPartnerBillDates(b), + ...mapPartnerBillView(b), partner: b.partnerAccount, })); @@ -1973,6 +2015,7 @@ export class SettlementService implements OnModuleInit { } async getAdminPartnerBill(id: bigint) { + await this.publishOutstandingPartnerBills(); const bill = await this.prisma.partnerBill.findUnique({ where: { id }, include: { partnerAccount: true, items: { orderBy: { occurredAt: 'asc' } } }, @@ -1980,7 +2023,7 @@ export class SettlementService implements OnModuleInit { if (!bill) throw new NotFoundException('账单不存在'); const { items, ...header } = bill; return serializeBigInt({ - ...withPartnerBillDates(header), + ...mapPartnerBillView(header), partnerId: header.partnerAccountId.toString(), ...splitPartnerBillItems(items), }); @@ -2000,8 +2043,8 @@ export class SettlementService implements OnModuleInit { periodStart: { gte: periodStart, lt: endExclusive }, }, }); - if (existing && existing.status !== 'PENDING_REVIEW') { - throw new BadRequestException('该账期账单已进入审核流程,不可重复生成'); + if (existing?.status === 'PAID') { + throw new BadRequestException('该账期账单已打款,不可重复生成'); } if (!primary.cityId) { @@ -2070,6 +2113,8 @@ export class SettlementService implements OnModuleInit { const totalAmount = round2(orderCommission + redeemCommission); const itemRows = [...orderRows, ...redeemRows]; + const publish = totalAmount > 0; + const sentAt = publish ? (existing?.sentAt ?? new Date()) : null; const bill = await this.prisma.$transaction(async (tx) => { const header = existing @@ -2081,7 +2126,9 @@ export class SettlementService implements OnModuleInit { totalAmount, periodStart, periodEnd, - status: 'PENDING_REVIEW', + status: publish ? 'UNPAID' : 'PENDING_REVIEW', + sentAt, + rejectReason: null, }, }) : await tx.partnerBill.create({ @@ -2093,7 +2140,8 @@ export class SettlementService implements OnModuleInit { orderCommission: round2(orderCommission), redeemCommission: round2(redeemCommission), totalAmount, - status: 'PENDING_REVIEW', + status: publish ? 'UNPAID' : 'PENDING_REVIEW', + sentAt, }, }); @@ -2155,128 +2203,14 @@ export class SettlementService implements OnModuleInit { }); } - /** HQ:发送给合伙人(待审核 → 待合伙人确认) */ - async sendPartnerBill(id: bigint) { - const bill = await this.prisma.partnerBill.findUnique({ where: { id } }); - if (!bill) throw new NotFoundException('账单不存在'); - if (Number(bill.totalAmount) <= 0) { - throw new BadRequestException('零元账单不同步给合伙人'); - } - if (bill.status !== 'PENDING_REVIEW' && bill.status !== 'REJECTED') { - throw new BadRequestException('仅待审核或已驳回账单可发送'); - } - const updated = await this.prisma.partnerBill.update({ - where: { id }, - data: { - status: 'AWAITING_CONFIRM', - sentAt: new Date(), - rejectReason: null, - }, - }); - return serializeBigInt(updated); - } - - async batchSendPartnerBills(ids: string[]) { - const results: Array<{ id: string; ok: boolean; message?: string }> = []; - for (const id of ids) { - try { - await this.sendPartnerBill(BigInt(id)); - results.push({ id, ok: true }); - } catch (e) { - results.push({ id, ok: false, message: e instanceof Error ? e.message : '失败' }); - } - } - return results; - } - - /** @deprecated HQ 代确认:待审核/待确认 → 未打款 */ - async confirmPartnerBill(id: bigint) { - const bill = await this.prisma.partnerBill.findUnique({ where: { id } }); - if (!bill) throw new NotFoundException('账单不存在'); - if (bill.status !== 'PENDING_REVIEW' && bill.status !== 'AWAITING_CONFIRM' && bill.status !== 'REJECTED') { - throw new BadRequestException('当前状态不可确认'); - } - const updated = await this.prisma.partnerBill.update({ - where: { id }, - data: { - status: 'UNPAID', - confirmedAt: new Date(), - sentAt: bill.sentAt ?? new Date(), - rejectReason: null, - }, - }); - return serializeBigInt(updated); - } - - /** 合伙人确认:待合伙人确认 → 未打款 */ - async partnerConfirmBill(partnerAccountId: bigint, billId: bigint) { - const primary = await this.partnerCityService.resolvePrimaryAccount(partnerAccountId); - const bill = await this.prisma.partnerBill.findUnique({ where: { id: billId } }); - if (!bill || bill.partnerAccountId !== primary.id) { - throw new NotFoundException('账单不存在'); - } - if (bill.status !== 'AWAITING_CONFIRM') { - throw new BadRequestException('当前账单不可确认'); - } - - const updated = await this.prisma.partnerBill.update({ - where: { id: billId }, - data: { - status: 'UNPAID', - confirmedAt: new Date(), - rejectReason: null, - }, - }); - - this.analyticsService.trackPartnerOneSafe(partnerAccountId, 'PARTNER_H5', { - partnerAccountId: primary.id, - eventName: 'partner_bill_confirm', - refType: 'PARTNER_BILL', - refId: billId, - extraJson: { billNo: bill.billNo }, - }); - - return serializeBigInt(updated); - } - - async batchPartnerConfirmBills(partnerAccountId: bigint, ids: string[]) { - const results: Array<{ id: string; ok: boolean; message?: string }> = []; - for (const id of ids) { - try { - await this.partnerConfirmBill(partnerAccountId, BigInt(id)); - results.push({ id, ok: true }); - } catch (e) { - results.push({ id, ok: false, message: e instanceof Error ? e.message : '失败' }); - } - } - return results; - } - - async rejectPartnerBill(id: bigint, reason: string) { - const rejectReason = reason.trim(); - if (!rejectReason) throw new BadRequestException('请填写驳回理由'); - if (rejectReason.length > 500) throw new BadRequestException('驳回理由不能超过 500 字'); - - const bill = await this.prisma.partnerBill.findUnique({ where: { id } }); - if (!bill) throw new NotFoundException('账单不存在'); - if (bill.status !== 'AWAITING_CONFIRM' && bill.status !== 'UNPAID') { - throw new BadRequestException('仅待合伙人确认或未打款账单可驳回'); - } - - const updated = await this.prisma.partnerBill.update({ - where: { id }, - data: { - status: 'REJECTED', - rejectReason, - }, - }); - - return serializeBigInt(updated); - } - - async markPartnerBillPaid(id: bigint, dto: { paymentRef?: string } = {}) { + async markPartnerBillPaid( + id: bigint, + dto: { paymentRef?: string; paymentProofUrls?: string[] } = {}, + ) { + await this.publishOutstandingPartnerBills(); const bill = await this.prisma.partnerBill.findUnique({ where: { id } }); if (!bill) throw new NotFoundException('账单不存在'); + if (Number(bill.totalAmount) <= 0) throw new BadRequestException('零元账单无需打款'); if (bill.status !== 'UNPAID') throw new BadRequestException('仅未打款账单可标记打款'); const updated = await this.prisma.partnerBill.update({ @@ -2286,17 +2220,21 @@ export class SettlementService implements OnModuleInit { paidAt: new Date(), rejectReason: null, paymentRef: dto.paymentRef?.trim() || null, + paymentProofUrls: paymentProofUrlsInput(dto.paymentProofUrls), }, }); - return serializeBigInt(updated); + return serializeBigInt(mapPartnerBillView(updated)); } - async batchMarkPartnerBillsPaid(ids: string[]) { + async batchMarkPartnerBillsPaid( + ids: string[], + dto: { paymentRef?: string; paymentProofUrls?: string[] } = {}, + ) { const results: Array<{ id: string; ok: boolean; message?: string }> = []; for (const id of ids) { try { - await this.markPartnerBillPaid(BigInt(id)); + await this.markPartnerBillPaid(BigInt(id), dto); results.push({ id, ok: true }); } catch (e) { results.push({ id, ok: false, message: e instanceof Error ? e.message : '失败' }); @@ -2363,7 +2301,18 @@ export class SettlementService implements OnModuleInit { { key: '合计应付', header: '合计应付', value: (r) => Number(r.totalAmount) }, { key: '收款户名', header: '收款户名', value: (r) => r.partnerAccount.bankAccountName ?? '' }, { key: '收款账号', header: '收款账号', value: (r) => r.partnerAccount.bankAccountNo ?? '' }, - { key: '状态', header: '状态', value: (r) => r.status }, + { + key: '状态', + header: '状态', + value: (r) => + ({ + PENDING_REVIEW: '待审核', + AWAITING_CONFIRM: '待合伙人确认', + UNPAID: '未打款', + PAID: '已打款', + REJECTED: '已驳回', + })[r.status] ?? r.status, + }, { key: '登录手机', header: '登录手机', value: (r) => r.partnerAccount.phone ?? '' }, { key: '开户行', header: '开户行', value: (r) => r.partnerAccount.bankBranch ?? '' }, { @@ -2382,6 +2331,11 @@ export class SettlementService implements OnModuleInit { value: (r) => (r.paidAt ? shanghaiYmd(r.paidAt) : ''), }, { key: '打款凭证', header: '打款凭证', value: (r) => r.paymentRef ?? '' }, + { + key: '打款凭证照片', + header: '打款凭证照片', + value: (r) => parsePaymentProofUrls(r.paymentProofUrls).join(' '), + }, { key: '驳回理由', header: '驳回理由', value: (r) => r.rejectReason ?? '' }, ];