import React, { useEffect, useMemo, useRef, useState } from 'react'; import { Button, Descriptions, Drawer, Empty, Form, Input, InputNumber, message, Modal, Popconfirm, Radio, Select, Space, Spin, Table, Tabs, Tag, Typography, } from 'antd'; import {AccountBookOutlined, DeleteOutlined, DownloadOutlined, EditOutlined, PlusOutlined, UnorderedListOutlined} from '@ant-design/icons'; import type { TableProps } from 'antd'; import XinTable from '@/components/XinTable'; import type { XinTableColumn, XinTableInstance, XinTableProps, } from '@/components/XinTable/typings.ts'; import type IPurchaseOrder from '@/domain/iPurchaseOrder.ts'; import type { IBill, IBillPrepare, IPurchaseDetail, IPurchaseDetailRow, IPurchaseStoreItem, IPurchaseStoreSummary, IPurchaseSupplierItem, PurchaseStoreItemAddParams, PurchaseStoreItemUpdateParams, } from '@/domain/iPurchaseOrder.ts'; import { PURCHASE_STATUS_MAP, BILL_STATUS_MAP } from '@/domain/iPurchaseOrder.ts'; import { addPurchaseStoreItem, exportPurchase, exportPurchaseStores, exportPurchaseSuppliers, generateBill, getBillPrepare, getPurchaseDetail, getPurchaseStoreSummary, removePurchaseStoreItem, type BillGenerateStoreParams, type PurchaseRowUpdateParams, updatePurchaseRow, updatePurchaseStoreItem, } from '@/api/purchase/order.ts'; import { Update } from '@/api/common/table.ts'; import { getSupplierOptions } from '@/api/customer/supplier.ts'; import { getProductOptions } from '@/api/product/goods.ts'; import type ISupplier from '@/domain/iSupplier.ts'; import type IProduct from '@/domain/iProduct.ts'; import AuthButton from '@/components/AuthButton'; import useAuth from '@/hooks/useAuth.ts'; const { Title, Text } = Typography; /** 商品明细可行内编辑的字段 */ type InlineEditField = 'product_name' | 'supplier_id' | 'product_spec' | 'unit' | 'cost_price'; /** 行内编辑字段中文名(校验提示用) */ const INLINE_FIELD_LABELS: Record = { product_name: '品名', supplier_id: '供应商', product_spec: '包规', unit: '单位', cost_price: '成本', }; /** 每单位参考价 = 整单价(成本/售价) ÷ 包规数值(包规解析不出正数时按 1 处理,与后端同口径;仅展示参考,不参与金额计算) */ const calcUnitRefPrice = (total: number, spec: string): number => { const pack = parseFloat(spec); return Number.isFinite(pack) && pack > 0 ? total / pack : total; }; /** * 采购单管理(C1 在门店订单页合并生成 / C2-C3 导出 / C4 明细矩阵修改) * 采购单无独立明细表,明细直接溯源门店订货明细:商品行 × 门店列 */ const PurchaseOrderPage: React.FC = () => { const tableRef = useRef>(null); const { auth } = useAuth(); // 详情抽屉 const [detailOpen, setDetailOpen] = useState(false); const [detailSize, setDetailSize] = useState(1200); const [detail, setDetail] = useState(null); const [detailLoading, setDetailLoading] = useState(false); const [completing, setCompleting] = useState(false); // 行内编辑:供应商选项(品名/供应商/包规/单位/成本与各门店订货量直接在表格中编辑) const [suppliers, setSuppliers] = useState([]); // 门店购买详情(按商品聚合的门店采购汇总) const [detailTab, setDetailTab] = useState('items'); const [storeId, setStoreId] = useState(0); const [storeSummary, setStoreSummary] = useState(null); const [storeLoading, setStoreLoading] = useState(false); // 门店购买详情:新增单品弹窗 const [addItemOpen, setAddItemOpen] = useState(false); const [addItemSaving, setAddItemSaving] = useState(false); const [addItemForm] = Form.useForm(); const [productOptions, setProductOptions] = useState([]); // 门店购买详情:单品编辑弹窗(数量/称重/单价) const [storeItemTarget, setStoreItemTarget] = useState(null); const [storeItemSaving, setStoreItemSaving] = useState(false); const [storeItemForm] = Form.useForm(); // 供应商采购明细页签 const [supplierId, setSupplierId] = useState(0); /** 市场筛选('' = 全部市场) */ const [marketFilter, setMarketFilter] = useState(''); // 商品明细页签:供应商/市场列筛选值(antd Table 受控筛选,用于底部统计行联动) const [itemColumnFilters, setItemColumnFilters] = useState>({}); // 导出弹窗:商品明细(供应商筛选)/ 门店购买详情 / 供应商采购明细 const [itemExportOpen, setItemExportOpen] = useState(false); const [itemExportSupplier, setItemExportSupplier] = useState(0); const [storeExportOpen, setStoreExportOpen] = useState(false); const [storeExportScope, setStoreExportScope] = useState<'current' | 'all'>('current'); const [supplierExportOpen, setSupplierExportOpen] = useState(false); const [supplierExportScope, setSupplierExportScope] = useState<'current' | 'all'>('current'); /** 采购单内出现的供应商(矩阵/供应商页签筛选与导出选项共用) */ const purchaseSuppliers = useMemo(() => { const map = new Map(); (detail?.items ?? []).forEach((row) => { if (row.supplier_id > 0 && row.supplier?.name) { map.set(row.supplier_id, row.supplier.name); } }); return Array.from(map, ([id, name]) => ({ id, name })); }, [detail?.items]); /** 当前供应商在采购单内出现的市场列表(市场筛选下拉选项) */ const supplierMarkets = useMemo(() => { const markets = new Set(); (detail?.items ?? []).forEach((row) => { if (row.supplier_id === supplierId && row.market) { markets.add(row.market); } }); return Array.from(markets); }, [detail?.items, supplierId]); /** 供应商采购明细行:按供应商+市场过滤矩阵行,金额=数量×成本价(成本口径) */ const supplierItems = useMemo(() => { if (!detail || supplierId <= 0) return []; return detail.items .filter((row) => row.supplier_id === supplierId) .filter((row) => marketFilter === '' || (row.market ?? '') === marketFilter) .map((row) => ({ product_id: row.product_id, product_name: row.product_name, product_spec: row.product_spec, unit: row.unit, market: row.market, cost_price: String(row.cost_price), quantity: row.quantity, weight: String(row.weight), amount: (row.quantity * Number(row.cost_price)).toFixed(2), })); }, [detail, supplierId, marketFilter]); /** 商品明细市场列筛选选项(采购单内出现的全部市场) */ const itemMarketOptions = useMemo(() => { const markets = new Set(); (detail?.items ?? []).forEach((row) => { if (row.market) { markets.add(row.market); } }); return Array.from(markets); }, [detail?.items]); /** 商品明细当前可见行:按列筛选值本地过滤,驱动底部成本统计与列筛选联动 */ const summaryItems = useMemo(() => { const supplierKeys = itemColumnFilters.supplier; const marketKeys = itemColumnFilters.market; return (detail?.items ?? []) .filter((row) => !supplierKeys?.length || supplierKeys.includes(row.supplier_id)) .filter((row) => !marketKeys?.length || marketKeys.includes(row.market ?? '')); }, [detail?.items, itemColumnFilters]); // 生成账单(已完成采购单按门店生成:填写配送费/周转筐/托盘数量,金额只读) const [billOpen, setBillOpen] = useState(false); const [billPrepare, setBillPrepare] = useState(null); const [billLoading, setBillLoading] = useState(false); const [billSaving, setBillSaving] = useState(false); const [billForm] = Form.useForm<{ stores: BillGenerateStoreParams[] }>(); // 弹窗内实时预览:附加金额 = 筐×筐单价 + 托盘×托盘单价;总额 = 商品 + 配送费 + 附加 + 售后金额(可正负) const watchBillStores = Form.useWatch('stores', billForm) ?? []; const billPreview = (billPrepare?.stores ?? []).map((row, index) => { const input = watchBillStores[index] ?? {}; const deliveryFee = Number(input.delivery_fee ?? 0); const afterSale = Number(input.after_sale ?? 0); const added = Number(input.box_num ?? 0) * Number(row.box_price) + Number(input.tray_num ?? 0) * Number(row.tray_price); return { added, total: Number(row.product_amount) + deliveryFee + added + afterSale, }; }); const billAllGenerated = (billPrepare?.stores ?? []).length > 0 && billPrepare!.stores.every((row) => row.bill !== null); useEffect(() => { getSupplierOptions().then((res) => setSuppliers(res.data.data ?? [])); }, []); // 详情加载后默认选中第一个门店(当前选中门店仍在采购单内则保留) useEffect(() => { if (detail && detail.stores.length > 0) { setStoreId((prev) => (detail.stores.some((s) => s.id === prev) ? prev : detail.stores[0].id)); } }, [detail]); // 详情加载后默认选中第一个供应商(当前选中供应商仍在采购单内则保留) useEffect(() => { if (purchaseSuppliers.length > 0) { setSupplierId((prev) => (purchaseSuppliers.some((s) => s.id === prev) ? prev : purchaseSuppliers[0].id)); } else { setSupplierId(0); } }, [purchaseSuppliers]); // 切到「门店购买详情」页签或切换门店时加载汇总 useEffect(() => { if (detailOpen && detailTab === 'stores' && detail && storeId > 0) { void loadStoreSummary(); } }, [detailOpen, detailTab, storeId, detail?.purchase.id]); const loadDetail = async (id: number) => { setDetailLoading(true); try { const res = await getPurchaseDetail(id); setDetail(res.data.data ?? null); } finally { setDetailLoading(false); } }; const openDetail = async (id: number) => { setDetailTab('items'); setStoreSummary(null); setItemColumnFilters({}); setDetailOpen(true); await loadDetail(id); }; /** 加载门店购买详情(门店采购汇总) */ const loadStoreSummary = async () => { if (!detail || storeId <= 0) { return; } setStoreLoading(true); try { const res = await getPurchaseStoreSummary(detail.purchase.id!, storeId); setStoreSummary(res.data.data ?? null); } finally { setStoreLoading(false); } }; /** 门店单品增删改后:刷新门店汇总、采购单详情与列表 */ const refreshAfterStoreItemChange = async () => { await loadStoreSummary(); if (detail) { await loadDetail(detail.purchase.id!); } await tableRef.current?.reload(); }; /** 打开新增单品弹窗(懒加载商品选项) */ const openAddItem = () => { addItemForm.resetFields(); setAddItemOpen(true); if (productOptions.length === 0) { getProductOptions().then((res) => setProductOptions(res.data.data ?? [])); } }; /** 提交新增单品 */ const handleAddItemSave = async (values: PurchaseStoreItemAddParams) => { if (!detail || storeId <= 0) { return; } setAddItemSaving(true); try { await addPurchaseStoreItem(detail.purchase.id!, storeId, values); message.success('已添加单品,订货单与采购单汇总已重算'); setAddItemOpen(false); await refreshAfterStoreItemChange(); } finally { setAddItemSaving(false); } }; /** 打开单品编辑弹窗(回显加权平均单价/数量/称重) */ const openStoreItemEdit = (row: IPurchaseStoreItem) => { setStoreItemTarget(row); storeItemForm.setFieldsValue({ quantity: row.quantity, price: Number(row.price ?? 0), weight: Number(row.weight ?? 0), }); }; /** 提交单品修改:同商品多笔订单明细合并到最早一条,级联重算 */ const handleStoreItemSave = async (values: PurchaseStoreItemUpdateParams) => { if (!detail || !storeItemTarget || storeId <= 0) { return; } setStoreItemSaving(true); try { await updatePurchaseStoreItem(detail.purchase.id!, storeId, storeItemTarget.product_id, values); message.success('单品已更新,订货单与采购单汇总已重算'); setStoreItemTarget(null); await refreshAfterStoreItemChange(); } finally { setStoreItemSaving(false); } }; /** 移除单品 */ const handleStoreItemRemove = async (row: IPurchaseStoreItem) => { if (!detail || storeId <= 0) { return; } await removePurchaseStoreItem(detail.purchase.id!, storeId, row.product_id); message.success('已移除单品,订货单与采购单汇总已重算'); await refreshAfterStoreItemChange(); }; /** 商品明细是否可行内编辑(进行中采购单 + 修改权限) */ const canUpdateRow = detail?.purchase.status === 0 && auth('purchase.order.update'); /** * 行内编辑保存:合并该行当前值整行提交(后端要求全字段);未变更 / 校验失败时不请求。 * Enter 或失焦触发保存,保存后刷新详情与列表。 */ const handleInlineSave = async (row: IPurchaseDetailRow, dataIndex: InlineEditField, rawValue: string | number) => { if (!detail) { return; } const params: PurchaseRowUpdateParams = { product_name: row.product_name, supplier_id: row.supplier_id, product_spec: row.product_spec, unit: row.unit, cost_price: Number(row.cost_price), }; if (dataIndex === 'cost_price') { const cost = Number(rawValue); if (rawValue === '' || !Number.isFinite(cost) || cost < 0) { message.warning('成本无效,已取消修改'); return; } if (cost === Number(row.cost_price)) { return; } params.cost_price = cost; } else if (dataIndex === 'supplier_id') { const supplierId = Number(rawValue); if (supplierId === row.supplier_id) { return; } params.supplier_id = supplierId; } else { const text = String(rawValue).trim(); if (!text) { message.warning(`${INLINE_FIELD_LABELS[dataIndex]}不能为空,已取消修改`); return; } if (text === row[dataIndex]) { return; } params[dataIndex] = text; } if (params.supplier_id <= 0) { message.warning('请先通过供应商单元格为该商品设置供应商'); return; } const res = await updatePurchaseRow(detail.purchase.id!, row.product_id, params); message.success(`已更新 ${res.data.data?.count ?? 0} 条订货明细`); const res1 = await getPurchaseDetail(detail.purchase.id!); setDetail(res1.data.data ?? null); await tableRef.current?.reload(); }; /** * 门店订货量保存:复用门店单品修改接口(仅提交订货量,单价/称重不改动), * 同商品多笔订单明细合并到最早一条并级联重算;未变更 / 校验失败时不请求。 */ const handleStoreCellSave = async (row: IPurchaseDetailRow, storeId: number, rawValue: string) => { if (!detail) { return; } const quantity = Number(rawValue); if (rawValue.trim() === '' || !Number.isInteger(quantity) || quantity < 0) { message.warning('订货量无效,已取消修改'); return; } if (quantity === row.cells[storeId]) { return; } await updatePurchaseStoreItem(detail.purchase.id!, storeId, row.product_id, { quantity }); message.success('订货量已更新,订货单与采购单汇总已重算'); await loadDetail(detail.purchase.id!); await tableRef.current?.reload(); }; /** 行内编辑单元格:直接渲染编辑组件(文本 Input / 供应商 Select / 成本 InputNumber),Enter 或失焦保存 */ const renderEditableCell = ( row: IPurchaseDetailRow, dataIndex: InlineEditField, display: React.ReactNode, ): React.ReactNode => { if (!canUpdateRow) { return display; } if (dataIndex === 'supplier_id') { return ( void handleInlineSave(row, dataIndex, (e.target as HTMLInputElement).value)} onBlur={(e) => void handleInlineSave(row, dataIndex, e.target.value)} /> ); }; /** 打开生成账单弹窗:拉取按门店汇总的商品金额(只读),初始化配送费/周转筐/托盘数量 */ const openBillGenerate = async (id: number) => { setBillOpen(true); setBillLoading(true); setBillPrepare(null); try { const res = await getBillPrepare(id); const data = res.data.data ?? null; setBillPrepare(data); billForm.setFieldsValue({ stores: (data?.stores ?? []).map((row) => ({ store_id: row.store_id, delivery_fee: row.bill ? Number(row.bill.delivery_fee) : 0, box_num: row.bill ? row.bill.box_num : 0, tray_num: row.bill ? row.bill.tray_num : 0, after_sale: row.bill ? Number(row.bill.after_sale) : 0, remark: row.bill?.remark ?? '', })), }); } finally { setBillLoading(false); } }; /** 提交生成账单:按门店各生成一张并关联采购单全部订单,金额由系统汇总不可修改 */ const handleBillSave = async (values: { stores: BillGenerateStoreParams[] }) => { if (!billPrepare) { return; } setBillSaving(true); try { const res = await generateBill(billPrepare.purchase.id, values.stores); message.success(`已生成 ${res.data.data?.count ?? 0} 张门店账单,采购单订单已关联`); setBillOpen(false); setBillPrepare(null); await tableRef.current?.reload(); if (detail && detail.purchase.id === billPrepare.purchase.id) { await loadDetail(detail.purchase.id!); } } finally { setBillSaving(false); } }; const handleComplete = async (id: number) => { setCompleting(true); try { await Update(`/purchase/order/${id}/finish`, { status: 3 }); message.success('采购单已标记完成'); await tableRef.current?.reload(); } finally { setCompleting(false); } }; /** 明细矩阵列:品名/供应商/单价/包规/单位/成本(可点击行内编辑)+ 每门店一列(数量)+ 合计 */ const buildItemColumns = (): TableProps['columns'] => { const fixed: NonNullable['columns']> = [ { title: '品名', dataIndex: 'product_name', width: 160, align: 'center', fixed: 'left', ellipsis: true, render: (_, row) => renderEditableCell(row, 'product_name', row.product_name), }, { title: '供应商', dataIndex: 'supplier', width: 110, align: 'center', fixed: 'left', filters: purchaseSuppliers.map((s) => ({ text: s.name, value: s.id })), filteredValue: itemColumnFilters.supplier ?? null, onFilter: (value, row) => row.supplier_id === Number(value), render: (_, row) => renderEditableCell(row, 'supplier_id', row.supplier?.name ?? '-'), }, { title: '市场', fixed: 'left', dataIndex: 'market', width: 110, align: 'center', filters: itemMarketOptions.map((m) => ({ text: m, value: m })), filteredValue: itemColumnFilters.market ?? null, onFilter: (value, row) => (row.market ?? '') === value, render: (v) => v || '-', }, { title: '单价', key: 'retail_price', width: 110, align: 'center', render: (_, row) => { // 加权平均售价 ÷ 包规数值(无订货数量时无参考价) const quantity = Number(row.quantity ?? 0); if (quantity <= 0) return -; const weightedPrice = Number(row.amount ?? 0) / quantity; return `¥${calcUnitRefPrice(weightedPrice, row.product_spec ?? '').toFixed(2)}`; }, }, { title: '包规', dataIndex: 'product_spec', align: 'center', width: 110, render: (v, row) => renderEditableCell(row, 'product_spec', v || '-'), }, { title: '单位', dataIndex: 'unit', width: 110, align: 'center', render: (v, row) => renderEditableCell(row, 'unit', v || '-'), }, { title: '成本', dataIndex: 'cost_price', width: 110, align: 'center', render: (v, row) => renderEditableCell(row, 'cost_price', `¥${Number(v).toFixed(2)}`), }, { title: '合计数量', key: 'total_quantity', width: 110, align: 'center', render: (_, row) => {row.quantity}, }, ]; const storeColumns = (detail?.stores ?? []).map((store) => ({ title: {store.name}, key: `store-${store.id}`, align: 'center' as const, render: (_: unknown, row: IPurchaseDetailRow) => { const quantity = row.cells[store.id]; if (quantity === undefined) { return -; } if (!canUpdateRow) { return quantity; } // 订货量直接在单元格编辑:Enter 或失焦保存(单价/称重不改动) return ( void handleStoreCellSave(row, store.id, (e.target as HTMLInputElement).value)} onBlur={(e) => void handleStoreCellSave(row, store.id, e.target.value)} /> ); }, })); return [...fixed, ...storeColumns]; }; /** 底部统计行:按门店统计金额(Σ 门店数量 × 行单价)+ 合计(随供应商/市场列筛选联动) */ const renderSummary = () => { const stores = detail?.stores ?? []; const items = summaryItems; const storeTotals = stores.map((store) => items.reduce( (sum, row) => sum + (row.cells[store.id] ?? 0) * Number(row.cost_price), 0, ), ); const totalQuantity = items.reduce((sum, row) => sum + Number(row.quantity), 0); const totalAmount = storeTotals.reduce((sum, amount) => sum + amount, 0); return ( 成本统计(按门店) 总计 ¥{totalAmount.toFixed(2)} {totalQuantity} {storeTotals.map((amount, index) => ( ¥{amount.toFixed(2)} ))} ); }; /** 门店购买详情列:商品/市场/购买单价(单价÷包规)/包规/单位/单价/数量/预计金额 + 操作 */ const storeColumns: TableProps['columns'] = [ { title: '商品', dataIndex: 'product_name', width: 160, align: 'center' }, { title: '市场', dataIndex: 'market', width: 90, align: 'center', render: (v) => v || '-' }, { title: '单价', key: 'retail_price', width: 130, align: 'center', render: (_, row) => `¥${calcUnitRefPrice(Number(row.price), row.product_spec).toFixed(2)}`, }, { title: '包规', dataIndex: 'product_spec', width: 90, align: 'center', render: (v) => v || '-' }, { title: '单位', dataIndex: 'unit', width: 90, 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: 90, align: 'center', render: (v) => `${v || '-'}斤` }, { title: '预计金额', dataIndex: 'amount', width: 110, align: 'center', render: (v) => ¥{Number(v).toFixed(2)}, }, { title: '操作', key: 'action', width: 130, align: 'center', render: (_, row) => detail?.purchase.status === 0 ? ( handleStoreItemRemove(row)} > ) : ( - ), }, ]; /** 门店购买详情合计行:总数量 + 总预计金额 */ const renderStoreTotal = () => { const items = storeSummary?.items ?? []; const totalQuantity = items.reduce((sum, row) => sum + Number(row.quantity), 0); const totalAmount = items.reduce((sum, row) => sum + Number(row.amount), 0); const totalWeight = items.reduce((sum, row) => sum + Number(row.weight), 0); return ( 合计 {totalQuantity} {totalWeight.toFixed(3)}斤 ¥{totalAmount.toFixed(2)} - ); }; /** 供应商采购明细列:品名/市场/包规/单位/成本价/数量/重量/金额(成本口径) */ const supplierColumns: TableProps['columns'] = [ { title: '品名', dataIndex: 'product_name', width: 180, align: 'center' }, { title: '市场', dataIndex: 'market', width: 100, align: 'center', render: (v) => v || '-' }, { title: '包规', dataIndex: 'product_spec', width: 110, align: 'center', render: (v) => v || '-' }, { title: '单位', dataIndex: 'unit', width: 90, align: 'center', render: (v) => v || '-' }, { title: '成本价', dataIndex: 'cost_price', width: 110, align: 'center', render: (v) => `¥${Number(v).toFixed(2)}`, }, { title: '数量', dataIndex: 'quantity', width: 100, align: 'center', render: (v) => {v}, }, { title: '重量', dataIndex: 'weight', width: 110, align: 'center', render: (v) => `${Number(v).toFixed(3)}斤` }, { title: '金额', dataIndex: 'amount', width: 120, align: 'center', render: (v) => ¥{Number(v).toFixed(2)}, }, ]; /** 供应商采购明细合计行 */ const renderSupplierTotal = () => { const totalQuantity = supplierItems.reduce((sum, row) => sum + Number(row.quantity), 0); const totalWeight = supplierItems.reduce((sum, row) => sum + Number(row.weight), 0); const totalAmount = supplierItems.reduce((sum, row) => sum + Number(row.amount), 0); return ( 合计 {totalQuantity} {totalWeight.toFixed(3)}斤 ¥{totalAmount.toFixed(2)} ); }; /** 门店账单列:门店/账单号/账单日期/商品金额/配送费/周转筐/托盘/附加金额/总金额 */ const billColumns: TableProps['columns'] = [ { title: '门店', dataIndex: 'store_name', width: 140, align: 'center' }, { title: '账单号', dataIndex: 'bill_no', width: 180, align: 'center', render: (v) => {v}, }, { title: '账单日期', dataIndex: 'bill_date', width: 110, align: 'center' }, { title: '商品金额', dataIndex: 'product_amount', width: 110, align: 'center', render: (v) => `¥${Number(v).toFixed(2)}`, }, { title: '配送费', dataIndex: 'delivery_fee', width: 100, align: 'center', render: (v) => `¥${Number(v).toFixed(2)}`, }, { title: '周转筐', key: 'box', width: 110, align: 'center', render: (_, row) => `${row.box_num} × ¥${Number(row.box_price).toFixed(2)}`, }, { title: '周转托盘', key: 'tray', width: 110, align: 'center', render: (_, row) => `${row.tray_num} × ¥${Number(row.tray_price).toFixed(2)}`, }, { title: '附加金额', dataIndex: 'added_amount', width: 100, align: 'center', render: (v) => `¥${Number(v).toFixed(2)}`, }, { title: '账单总金额', dataIndex: 'total_amount', width: 120, align: 'center', render: (v) => ¥{Number(v).toFixed(2)}, }, { title: '支付状态', dataIndex: 'status', width: 100, align: 'center', render: (v) => { const item = BILL_STATUS_MAP[Number(v ?? 0)]; return {item?.text}; }, }, ]; /** 门店账单合计行 */ const renderBillSummary = () => { const bills = detail?.bills ?? []; const totalProduct = bills.reduce((sum, row) => sum + Number(row.product_amount), 0); const totalDelivery = bills.reduce((sum, row) => sum + Number(row.delivery_fee), 0); const totalAdded = bills.reduce((sum, row) => sum + Number(row.added_amount), 0); const totalAmount = bills.reduce((sum, row) => sum + Number(row.total_amount), 0); return ( 合计 ¥{totalProduct.toFixed(2)} ¥{totalDelivery.toFixed(2)} - ¥{totalAdded.toFixed(2)} ¥{totalAmount.toFixed(2)} - ); }; const columns: XinTableColumn[] = [ { title: '采购单号', dataIndex: 'purchase_no', valueType: 'text', hideInForm: true, align: 'center', width: 260, render: (_, record) => {record.purchase_no}, }, { title: '采购日期', dataIndex: 'purchase_date', valueType: 'date', align: 'center', hideInForm: true, render: (_, record) => record.purchase_date, }, { title: '预估成本', dataIndex: 'estimate_amount', hideInForm: true, hideInSearch: true, align: 'center', render: (_, record) => `¥${record.estimate_amount}`, }, { title: '实际成本', dataIndex: 'actual_amount', hideInSearch: true, valueType: "digit", fieldProps: { min: 0, precision: 3, placeholder: "请输入单价" }, align: 'center', render: (_, record) => Number(record.actual_amount) > 0 ? ( ¥{record.actual_amount} ) : ( 未录入 ), }, { title: '总件数', dataIndex: 'total_quantity', hideInForm: true, hideInSearch: true, align: 'center', }, { title: '备注', dataIndex: 'remark', valueType: "textarea", hideInTable: true, hideInSearch: true }, { title: '总重量', dataIndex: 'total_weight', hideInForm: true, hideInSearch: true, align: 'center', render: (_, record) => `${record.total_weight}斤`, }, { title: '状态', dataIndex: 'status', valueType: 'select', hideInForm: true, fieldProps: { options: Object.entries(PURCHASE_STATUS_MAP).map(([value, item]) => ({ value: Number(value), label: item.text, })), }, render: (_, record) => { const item = PURCHASE_STATUS_MAP[record.status ?? 0]; return {item?.text}; }, align: 'center', }, { title: '制单人', dataIndex: 'operator', hideInForm: true, hideInSearch: true, render: (_, record) => record.operator?.nickname ?? '-', align: 'center', }, ]; const operateRender: XinTableProps['operateRender'] = (record, dom) => [ record.status === 0 ? dom.edit : null, ) : ( ) ]; const tableProps: XinTableProps = { api: '/purchase/order', columns, rowKey: 'id', accessName: 'purchase.order', tableRef, operateRender, operateProps: { width: 300 }, formProps: false, }; return ( <>
采购单 在「门店订单」页勾选已接单订单合并生成采购单;采购单支持修改订单信息。
{...tableProps} /> {/* 采购单详情:商品行 × 门店列矩阵 */} setDetailOpen(false)} size={detailSize} resizable={{ onResize: (newSize) => setDetailSize(newSize), }} loading={detailLoading} > {detail && ( <> 采购单信息 {detail.purchase.purchase_date} {PURCHASE_STATUS_MAP[detail.purchase.status ?? 0]?.text} {detail.purchase.operator?.nickname ?? '-'} ¥{detail.purchase.estimate_amount} ¥{detail.purchase.actual_amount} {detail.purchase.total_quantity} {detail.purchase.total_weight} {detail.purchase.created_at} {detail.purchase.remark} { detailTab === 'stores' ? ( <>
选择门店: { setSupplierId(value); setMarketFilter(''); }} placeholder="选择供应商" className="w-60!" showSearch optionFilterProp="label" options={purchaseSuppliers.map((s) => ({ value: s.id, label: s.name }))} /> 市场:
¥{(billed ? Number(row.bill!.added_amount) : billPreview[field.name]?.added ?? 0).toFixed(2)}
¥{(billed ? Number(row.bill!.total_amount) : billPreview[field.name]?.total ?? 0).toFixed(2)} {billed && (
{row.bill!.bill_no}
)}
); })}
)} )} {/* 新增单品:选择商品 + 采购数量 + 称重 */} setAddItemOpen(false)} onOk={() => addItemForm.submit()} confirmLoading={addItemSaving} okText="添加" destroyOnHidden >
单品将挂靠到该门店在此采购单中的最新一笔订单;单价按门店等级上浮比例自动换算。
({ value: s.id, label: s.name })), ]} /> {/* 门店购买详情导出:当前门店 / 全部门店(多工作表) */} setStoreExportOpen(false)} onOk={() => { if (detail) { void exportPurchaseStores( detail.purchase.id!, storeExportScope === 'current' && storeId > 0 ? storeId : undefined, ); } setStoreExportOpen(false); }} okText="导出" destroyOnHidden > setStoreExportScope(e.target.value)} options={[ { value: 'current', label: `当前门店(${detail?.stores.find((s) => s.id === storeId)?.name ?? '-'})` }, { value: 'all', label: '全部门店(合并为一个 XLSX,每门店一个工作表)' }, ]} /> {/* 供应商采购明细导出:当前供应商 / 全部供应商(按「供应商×市场」拆分工作表) */} setSupplierExportOpen(false)} onOk={() => { if (detail) { void exportPurchaseSuppliers( detail.purchase.id!, supplierExportScope === 'current' && supplierId > 0 ? supplierId : undefined, ); } setSupplierExportOpen(false); }} okText="导出" destroyOnHidden >
导出 XLSX 按「供应商 × 市场」拆分工作表,列序:品名、汇总、市场、各门店明细,有数据的单元格突出显示。
setSupplierExportScope(e.target.value)} options={[ { value: 'current', label: `当前供应商(${purchaseSuppliers.find((s) => s.id === supplierId)?.name ?? '-'})` }, { value: 'all', label: '全部供应商(合并为一个 XLSX,每供应商×市场一个工作表)' }, ]} />
); }; export default PurchaseOrderPage;