import React, { useEffect, useRef, useState } from 'react'; import { Button, DatePicker, Descriptions, Drawer, Form, Input, message, Modal, Table, Tag, Typography, } from 'antd'; import type { TableProps } from 'antd'; import { UnorderedListOutlined } from '@ant-design/icons'; import dayjs from 'dayjs'; import XinTable from '@/components/XinTable'; import type { XinTableColumn, XinTableInstance, XinTableProps, } from '@/components/XinTable/typings.ts'; import type { IBill, IBillDetail, IBillGoodsItem, IBillOrder } from '@/domain/iBill.ts'; import { BILL_STATUS_MAP } from '@/domain/iBill.ts'; import { STORE_ORDER_STATUS_MAP } from '@/domain/iStoreOrder.ts'; import { getBillDetail, payBill } from '@/api/recon/bill.ts'; import { getStoreOptions } from '@/api/customer/store.ts'; import type IStore from '@/domain/iStore.ts'; import AuthButton from '@/components/AuthButton'; const { Title, Text } = Typography; /** 确认收款表单 */ interface PayFormValues { paid_at: dayjs.Dayjs; pay_remark?: string; } /** * 门店账单(采购单完成后按门店生成;详情含合并后的商品明细与关联订单;线下收款手动登记) */ const BillPage: React.FC = () => { const tableRef = useRef>(null); const [stores, setStores] = useState([]); const [detailOpen, setDetailOpen] = useState(false); const [detail, setDetail] = useState(null); const [detailLoading, setDetailLoading] = useState(false); // 确认收款(线下收款手动登记) const [payTarget, setPayTarget] = useState(null); const [paySaving, setPaySaving] = useState(false); const [payForm] = Form.useForm(); useEffect(() => { getStoreOptions().then((res) => setStores(res.data.data ?? [])); }, []); const openDetail = async (id: number) => { setDetailOpen(true); setDetailLoading(true); try { const res = await getBillDetail(id); setDetail(res.data.data ?? null); } finally { setDetailLoading(false); } }; /** 打开确认收款弹窗(默认付款时间为当前) */ const openPay = (record: IBill) => { setPayTarget(record); payForm.setFieldsValue({ paid_at: dayjs(), pay_remark: '' }); }; /** 提交确认收款:登记付款信息并置为已支付 */ const handlePaySave = async (values: PayFormValues) => { if (!payTarget?.id) { return; } setPaySaving(true); try { await payBill(payTarget.id, { paid_at: values.paid_at.format('YYYY-MM-DD HH:mm:ss'), pay_remark: values.pay_remark ?? '', }); message.success('收款已登记,账单已置为已支付'); setPayTarget(null); await tableRef.current?.reload(); } finally { setPaySaving(false); } }; /** 合并商品明细列:品名/包规/单位/单价(加权平均)/数量/重量/金额 */ const itemColumns: TableProps['columns'] = [ { title: '品名', dataIndex: 'product_name', width: 160, align: 'center' }, { title: '包规', dataIndex: 'product_spec', width: 100, align: 'center', render: (v) => v || '-' }, { title: '单位', dataIndex: 'unit', width: 80, align: 'center', render: (v) => v || '-' }, { title: '单价', dataIndex: 'price', width: 100, align: 'center', render: (v) => `¥${Number(v).toFixed(2)}`, }, { title: '数量', dataIndex: 'quantity', width: 90, align: 'center', render: (v) => {v}, }, { title: '重量', dataIndex: 'weight', width: 100, align: 'center', render: (v) => `${v}斤` }, { title: '金额', dataIndex: 'amount', width: 110, align: 'center', render: (v) => ¥{Number(v).toFixed(2)}, }, ]; /** 合并商品明细合计行 */ const renderItemSummary = () => { const items = detail?.items ?? []; const totalQuantity = items.reduce((sum, row) => sum + Number(row.quantity), 0); const totalWeight = items.reduce((sum, row) => sum + Number(row.weight), 0); const totalAmount = items.reduce((sum, row) => sum + Number(row.amount), 0); return ( 合计 {totalQuantity} {totalWeight.toFixed(3)}斤 ¥{totalAmount.toFixed(2)} ); }; /** 关联订单列 */ const orderColumns: TableProps['columns'] = [ { title: '订单号', dataIndex: 'order_no', align: 'center', render: (v) => {v}, }, { title: '订货日期', dataIndex: 'order_date', align: 'center' }, { title: '订货数量', dataIndex: 'total_quantity', align: 'center' }, { title: '总重量', dataIndex: 'total_weight', align: 'center', render: (v) => `${v}斤` }, { title: '订单金额', dataIndex: 'total_amount', align: 'center', render: (v) => ¥{v}, }, { title: '状态', dataIndex: 'status', align: 'center', render: (v) => { const item = STORE_ORDER_STATUS_MAP[Number(v ?? 0)]; return {item?.text}; }, }, ]; const columns: XinTableColumn[] = [ { title: '账单号', dataIndex: 'bill_no', valueType: 'text', hideInForm: true, width: 210, render: (_, record) => {record.bill_no}, }, { title: '门店', dataIndex: 'store_id', valueType: 'select', hideInForm: true, fieldProps: { options: stores.map((s) => ({ label: s.name, value: s.id })), showSearch: true, optionFilterProp: 'label', }, render: (_, record) => record.store?.name ?? `门店#${record.store_id}`, }, { title: '采购单号', dataIndex: 'purchase_no', valueType: 'text', hideInForm: true, render: (_, record) => record.purchase?.purchase_no ?? '-', }, { title: '账单日期', dataIndex: 'bill_date', valueType: 'dateRange', hideInForm: true, align: 'center', render: (_, record) => record.bill_date, }, { title: '商品金额', dataIndex: 'product_amount', hideInForm: true, hideInSearch: true, align: 'center', render: (_, record) => `¥${record.product_amount}`, }, { title: '配送费', dataIndex: 'delivery_fee', hideInForm: true, hideInSearch: true, align: 'center', render: (_, record) => `¥${record.delivery_fee}`, }, { title: '附加金额', dataIndex: 'added_amount', hideInForm: true, hideInSearch: true, align: 'center', render: (_, record) => ( ¥{record.added_amount} ), }, { title: '账单总金额', dataIndex: 'total_amount', hideInForm: true, hideInSearch: true, align: 'center', render: (_, record) => ¥{record.total_amount}, }, { title: '支付状态', dataIndex: 'status', valueType: 'select', hideInForm: true, align: 'center', fieldProps: { options: Object.entries(BILL_STATUS_MAP).map(([value, item]) => ({ value: Number(value), label: item.text, })), }, render: (_, record) => { const item = BILL_STATUS_MAP[record.status ?? 0]; return {item?.text}; }, }, { title: '关联订单', dataIndex: 'orders_count', hideInForm: true, hideInSearch: true, align: 'center', render: (_, record) => `${record.orders_count ?? 0} 笔`, }, { title: '生成时间', dataIndex: 'created_at', hideInForm: true, hideInSearch: true, align: 'center', }, ]; const operateRender: XinTableProps['operateRender'] = (record) => [ ) : null, ]; const tableProps: XinTableProps = { api: '/recon/bill', columns, rowKey: 'id', accessName: 'recon.bill', tableRef, operateRender, formProps: false, actionBarRender: (dom) => [dom.search, dom.keywordSearch], }; return ( <>
门店账单 采购单完成后在「采购单」页生成,每个门店单独一张;账单总金额 = 商品金额 + 配送费 + 附加金额(周转筐/托盘)。
{...tableProps} /> setDetailOpen(false)} size={1000} loading={detailLoading} > {detail ? ( <> {detail.bill.store?.name ?? `门店#${detail.bill.store_id}`} {detail.bill.purchase?.purchase_no ?? '-'} {detail.bill.bill_date} ¥{detail.bill.product_amount} ¥{detail.bill.delivery_fee} ¥{detail.bill.added_amount} (筐 {detail.bill.box_num}×¥{detail.bill.box_price},托盘 {detail.bill.tray_num}×¥{detail.bill.tray_price}) ¥{detail.bill.total_amount} {BILL_STATUS_MAP[detail.bill.status ?? 0]?.text} {detail.bill.paid_at ?? '-'} {detail.bill.paid_operator?.nickname ?? '-'} {detail.bill.operator?.nickname ?? '-'} {detail.bill.created_at} {detail.bill.pay_remark ? ( {detail.bill.pay_remark} ) : null} {detail.bill.remark ? ( {detail.bill.remark} ) : null} 商品明细(按商品合并) rowKey="product_id" size="small" bordered columns={itemColumns} dataSource={detail.items} pagination={false} summary={renderItemSummary} /> 关联订单({detail.orders.length} 笔) rowKey="id" size="small" bordered columns={orderColumns} dataSource={detail.orders} pagination={false} /> ) : null} {/* 确认收款:线下收款后手动登记付款信息 */} setPayTarget(null)} onOk={() => payForm.submit()} confirmLoading={paySaving} okText="确认收款" destroyOnHidden >
应收金额 ¥{payTarget?.total_amount ?? '0.00'} (商品 ¥{payTarget?.product_amount ?? '0.00'} + 配送费 ¥{payTarget?.delivery_fee ?? '0.00'} + 附加 ¥{payTarget?.added_amount ?? '0.00'}); 线下收款完成后登记付款信息,账单支付状态将置为「已支付」。
); }; export default BillPage;