import React, { useEffect, useRef, useState } from 'react'; import { Button, Descriptions, Drawer, Form, Input, InputNumber, message, Modal, Popconfirm, Select, Table, Tag, Typography, } from 'antd'; import { CheckOutlined, EditOutlined } 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 { IPurchaseDetail, IPurchaseDetailRow, } from '@/domain/iPurchaseOrder.ts'; import { PURCHASE_STATUS_MAP } from '@/domain/iPurchaseOrder.ts'; import { getPurchaseDetail, updatePurchaseRow, } from '@/api/purchase/order.ts'; import { Update } from '@/api/common/table.ts'; import { getSupplierOptions } from '@/api/customer/supplier.ts'; import type ISupplier from '@/domain/iSupplier.ts'; import AuthButton from '@/components/AuthButton'; const { Title, Text } = Typography; /** 行修改表单:品名/供应商/包规/单位/成本 */ interface RowEditForm { product_name: string; supplier_id?: number; product_spec: string; unit: string; cost_price: number; } /** 单价 = 成本 / 包规数值(包规解析不出正数时按 1 处理,与后端同口径) */ const calcUnitCost = (cost: number, spec: string): number => { const pack = parseFloat(spec); return Number.isFinite(pack) && pack > 0 ? cost / pack : cost; }; /** * 采购单管理(C1 在门店订单页合并生成 / C2-C3 导出 / C4 明细矩阵修改) * 采购单无独立明细表,明细直接溯源门店订货明细:商品行 × 门店列 */ const PurchaseOrderPage: React.FC = () => { const tableRef = useRef>(null); // 详情抽屉 const [detailOpen, setDetailOpen] = useState(false); const [detail, setDetail] = useState(null); const [detailLoading, setDetailLoading] = useState(false); const [completing, setCompleting] = useState(false); // 行修改弹窗 const [editingRow, setEditingRow] = useState(null); const [rowSaving, setRowSaving] = useState(false); const [syncTarget, setSyncTarget] = useState<'purchase' | 'product'>('purchase'); const [editForm] = Form.useForm(); const [suppliers, setSuppliers] = useState([]); useEffect(() => { getSupplierOptions().then((res) => setSuppliers(res.data.data ?? [])); }, []); 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) => { setDetailOpen(true); await loadDetail(id); }; const openEdit = (row: IPurchaseDetailRow) => { setEditingRow(row); editForm.setFieldsValue({ product_name: row.product_name, supplier_id: row.supplier_id > 0 ? row.supplier_id : undefined, product_spec: row.product_spec, unit: row.unit, cost_price: Number(row.cost_price), }); }; /** 提交行修改:同步该商品全部订货明细;syncTarget=product 时追加同步商品档案 */ const handleEditSave = async (values: RowEditForm) => { if (!detail || !editingRow) { return; } setRowSaving(true); try { const res = await updatePurchaseRow(detail.purchase.id!, editingRow.product_id, { ...values, supplier_id: values.supplier_id ?? 0, sync_product: syncTarget === 'product', }); message.success( syncTarget === 'product' ? `已同步 ${res.data.data?.count ?? 0} 条订货明细,并更新商品档案` : `已同步 ${res.data.data?.count ?? 0} 条订货明细`, ); setEditingRow(null); await loadDetail(detail.purchase.id!); await tableRef.current?.reload(); } finally { setRowSaving(false); } }; const handleComplete = async () => { if (!detail) { return; } setCompleting(true); try { await Update(`/purchase/order/${detail.purchase.id}`, { status: 3 }); message.success('采购单已标记完成'); await loadDetail(detail.purchase.id!); await tableRef.current?.reload(); } finally { setCompleting(false); } }; /** 明细矩阵列:品名/供应商/单价/包规/单位/成本 + 每门店一列(数量)+ 合计 + 操作 */ const buildItemColumns = (): TableProps['columns'] => { const fixed: NonNullable['columns']> = [ { title: '品名', dataIndex: 'product_name', width: 120, align: 'center' }, { title: '供应商', dataIndex: 'supplier', width: 110, align: 'center', render: (_, row) => row.supplier?.name ?? '-', }, { title: '单价', key: 'unit_cost', width: 90, align: 'center', render: (_, row) => `¥${calcUnitCost(Number(row.cost_price), row.product_spec ?? '').toFixed(2)}`, }, { title: '包规', dataIndex: 'product_spec', align: 'center', width: 90, render: (v) => v || '-' }, { title: '单位', dataIndex: 'unit', width: 70, align: 'center', render: (v) => v || '-' }, { title: '成本', dataIndex: 'cost_price', width: 90, align: 'center', render: (v) => `¥${Number(v).toFixed(2)}` }, ]; const storeColumns = (detail?.stores ?? []).map((store) => ({ title: {store.name}, key: `store-${store.id}`, width: 100, align: 'center' as const, render: (_: unknown, row: IPurchaseDetailRow) => { const quantity = row.cells[store.id]; return quantity !== undefined ? {quantity} : -; }, })); const tail: NonNullable['columns']> = [ { title: '合计数量', key: 'total_quantity', width: 90, align: 'center', render: (_, row) => {row.quantity}, }, { title: '操作', key: 'action', width: 90, fixed: 'right', align: 'center', render: (_, row) => ( ), }, ]; return [...fixed, ...storeColumns, ...tail]; }; /** 底部统计行:按门店统计金额(Σ 门店数量 × 行单价)+ 合计 */ const renderSummary = () => { const stores = detail?.stores ?? []; const items = detail?.items ?? []; 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 ( 成本统计(按门店) {storeTotals.map((amount, index) => ( ¥{amount.toFixed(2)} ))} {totalQuantity} ¥{totalAmount.toFixed(2)} ); }; const columns: XinTableColumn[] = [ { title: '采购单号', dataIndex: 'purchase_no', valueType: 'text', hideInForm: true, render: (_, record) => {record.purchase_no}, }, { title: '采购日期', dataIndex: 'purchase_date', valueType: 'dateRange', hideInForm: true, align: 'center', render: (_, record) => record.purchase_date, }, { title: '预估金额', dataIndex: 'estimate_amount', hideInForm: true, hideInSearch: true, align: 'right', render: (_, record) => `¥${record.estimate_amount}`, }, { title: '实际金额', dataIndex: 'actual_amount', hideInForm: true, hideInSearch: true, align: 'right', render: (_, record) => Number(record.actual_amount) > 0 ? ( ¥{record.actual_amount} ) : ( 未录入 ), }, { 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) => [ ]; const tableProps: XinTableProps = { api: '/purchase/order', columns, rowKey: 'id', accessName: 'purchase.order', tableRef, operateRender, formProps: false, }; return ( <>
采购单 在「门店订单」页勾选已接单订单合并生成采购单;采购单支持修改订单信息。
{...tableProps} /> {/* 采购单详情:商品行 × 门店列矩阵 */} setDetailOpen(false)} size={1200} loading={detailLoading} extra={detail?.purchase.status === 0 && ( )} > {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_weight} 商品明细 rowKey="product_id" size="small" bordered columns={buildItemColumns()} dataSource={detail.items} pagination={false} scroll={{ x: 'max-content' }} summary={renderSummary} /> )} {/* 行修改:品名/供应商/包规/单位/成本,同步采购单明细 / 追加同步商品档案 */} setEditingRow(null)} destroyOnHidden footer={[ , , , ]} >
「保存」仅修改本采购单中该商品的所有订单项;「保存并同步」在此基础上同时更新商品档案(后续新订单生效)。
); }; export default PurchaseOrderPage;