import { useEffect, useState } from 'react'; import { Button, Card, DatePicker, Descriptions, Drawer, Form, Input, Modal, Select, Space, Statistic, Table, Tag, Typography, message, } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import type { Dayjs } from 'dayjs'; import { WINERY_SETTLEMENT_RATE, type SystemConfigFormResponse, } from '@dukang/shared-types'; import { fmtTime } from '../lib/constants'; import { downloadExcelCsv } from '../lib/exportExcel'; import { request, type HqProfile } from '../lib/api'; import { useAdminList } from '../lib/useAdminList'; type Row = { id: string; billNo: string; billDate: string; orderCount: number; orderAmount: number; wineryRate: number; wineryAmount: number; status: string; paidAt?: string | null; }; type BillItem = { id: string; orderNo: string; deliveryType: string; payAmount: number; wineryAmount: number; paidAt: string; }; const STATUS_LABELS: Record = { UNPAID: '未打款', PAID: '已打款', NO_PAYMENT_NEEDED: '无需打款', }; const STATUS_COLORS: Record = { UNPAID: 'red', PAID: 'green', NO_PAYMENT_NEEDED: 'default', }; function isZeroPayable(amount: number | string | null | undefined) { return Number(amount ?? 0) === 0; } /** 应付为 0 时展示「无需打款」(灰),否则按 DB 打款状态 */ function displayWineryStatus(status: string, wineryAmount: number | string) { if (isZeroPayable(wineryAmount)) { return { key: 'NO_PAYMENT_NEEDED', label: STATUS_LABELS.NO_PAYMENT_NEEDED, color: STATUS_COLORS.NO_PAYMENT_NEEDED }; } return { key: status, label: STATUS_LABELS[status] || status, color: STATUS_COLORS[status] || 'default', }; } function canConfirmWineryPay(row: { status: string; wineryAmount: number | string }) { return row.status === 'UNPAID' && !isZeroPayable(row.wineryAmount); } const DELIVERY_LABELS: Record = { LOCAL: '同城', CROSS_CITY: '跨城', }; const WINERY_BANK_KEYS = [ 'WINERY_BANK_ACCOUNT_NAME', 'WINERY_BANK_NAME', 'WINERY_BANK_BRANCH', 'WINERY_BANK_ACCOUNT_NO', ] as const; export default function WineryBillsPage() { const [form] = Form.useForm(); const [bankForm] = Form.useForm>(); const [filters, setFilters] = useState>({}); const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList( '/admin/winery-bills', () => { const qs = new URLSearchParams(); if (filters.status) qs.set('status', filters.status); if (filters.year) qs.set('year', filters.year); if (filters.month) qs.set('month', filters.month); if (filters.dateFrom) qs.set('dateFrom', filters.dateFrom); if (filters.dateTo) qs.set('dateTo', filters.dateTo); return qs; }, [filters], ); const [exporting, setExporting] = useState(false); const [selectedKeys, setSelectedKeys] = useState([]); const [detail, setDetail] = useState<(Row & { items?: BillItem[] }) | null>(null); const [drawerOpen, setDrawerOpen] = useState(false); const [profile, setProfile] = useState(null); const [bankOpen, setBankOpen] = useState(false); const [bankLoading, setBankLoading] = useState(false); const [bankSaving, setBankSaving] = useState(false); useEffect(() => { request('/admin/auth/me').then(setProfile).catch(() => {}); }, []); const canEditWineryBank = profile?.adminRole === 'SUPER_ADMIN' || (profile?.permissionKeys ?? []).includes('system_settings_winery_bank'); function confirmPay(ids: string[], amountHint?: number) { Modal.confirm({ title: '确认打款?', content: `将确认 ${ids.length} 笔酒厂对账单${amountHint != null ? `,合计约 ¥${amountHint.toFixed(2)}` : ''}。此操作不可撤销。`, okText: '确认打款', cancelText: '取消', onOk: async () => { if (ids.length === 1) { await request(`/admin/winery-bills/${ids[0]}/confirm`, { method: 'POST' }); } else { await request('/admin/winery-bills/batch-confirm', { method: 'POST', body: JSON.stringify({ ids }), }); } message.success('已确认打款'); setSelectedKeys([]); reload(); }, }); } async function openDetail(id: string) { const d = await request(`/admin/winery-bills/${id}`); setDetail(d); setDrawerOpen(true); } async function exportExcel() { setExporting(true); try { const qs = new URLSearchParams(); if (filters.status) qs.set('status', filters.status); if (filters.year) qs.set('year', filters.year); if (filters.month) qs.set('month', filters.month); if (filters.dateFrom) qs.set('dateFrom', filters.dateFrom); if (filters.dateTo) qs.set('dateTo', filters.dateTo); const result = await request<{ csv: string; count: number }>(`/admin/winery-bills/export?${qs}`); const suffix = filters.year && filters.month ? `${filters.year}-${filters.month}` : 'all'; downloadExcelCsv(result.csv, `酒厂对账单_${suffix}.csv`); message.success(`已导出 ${result.count} 条`); } finally { setExporting(false); } } async function openBankModal() { setBankOpen(true); setBankLoading(true); try { const cfg = await request('/admin/system-config'); const values: Record = {}; for (const key of WINERY_BANK_KEYS) { values[key] = cfg.values[key] ?? ''; } bankForm.setFieldsValue(values); } catch (e) { message.error(e instanceof Error ? e.message : '加载失败'); setBankOpen(false); } finally { setBankLoading(false); } } async function saveBank() { const values = await bankForm.validateFields(); setBankSaving(true); try { await request('/admin/system-config', { method: 'PUT', body: JSON.stringify({ values }), }); message.success('酒厂银行账户已保存'); setBankOpen(false); } catch (e) { message.error(e instanceof Error ? e.message : '保存失败'); } finally { setBankSaving(false); } } const summary = data?.summary; const ratePct = Math.round(WINERY_SETTLEMENT_RATE * 100); const selectedRows = (data?.items ?? []).filter((r) => selectedKeys.includes(r.id)); const selectedAmount = selectedRows.reduce((s, r) => s + Number(r.wineryAmount), 0); const columns: ColumnsType = [ { title: '账单号', dataIndex: 'billNo', width: 170, ellipsis: true }, { title: '账单日', dataIndex: 'billDate', width: 110, render: (v) => String(v || '').slice(0, 10), }, { title: '订单数', dataIndex: 'orderCount', width: 80 }, { title: '酒单实付合计', dataIndex: 'orderAmount', width: 120, render: (v) => `¥${Number(v).toFixed(2)}`, }, { title: '酒厂比例', dataIndex: 'wineryRate', width: 90, render: (v) => `${Math.round(Number(v) * 100)}%`, }, { title: '应付', dataIndex: 'wineryAmount', width: 110, render: (v) => `¥${Number(v).toFixed(2)}`, }, { title: '状态', dataIndex: 'status', width: 100, render: (s, row) => { const d = displayWineryStatus(s, row.wineryAmount); return {d.label}; }, }, { title: '操作', width: 160, fixed: 'right', render: (_, row) => ( {canConfirmWineryPay(row) && ( )} ), }, ]; return (
酒厂对账单 T+3:每日 8:00 汇总 3 天前(自然日)已完成的同城/跨城订单(实付 × {ratePct}%);未打款红色、已打款绿色、应付为 0 无需打款(灰),可展开订单明细 {canEditWineryBank ? ( ) : null}
{summary && ( )}
{ setFilters({ status: v.status || '', year: v.month ? String(v.month.year()) : '', month: v.month ? String(v.month.month() + 1) : '', dateFrom: v.range?.[0] ? v.range[0].format('YYYY-MM-DD') : '', dateTo: v.range?.[1] ? v.range[1].format('YYYY-MM-DD') : '', }); setPage(1); }} >
); }