采购单优化

This commit is contained in:
liu
2026-08-12 14:38:20 +08:00
parent ce1f36b5b4
commit dadfdc1511
30 changed files with 1059 additions and 1441 deletions
+25 -37
View File
@@ -1,21 +1,11 @@
import createAxios from '@/utils/request';
import type {
ExportFormat,
IAllocationResult,
IPurchaseOrderItem,
IPurchaseDetail,
PurchaseExportType,
} from '@/domain/iPurchaseOrder.ts';
import { downloadBlob } from '@/api/common/download.ts';
export interface PurchaseItemUpdateParams {
product_name?: string;
product_spec?: string;
price: number | string;
quantity: number | string;
weight?: number | string;
remark?: string;
}
/** C1 合并「已接单」门店订单生成采购单(order_ids 为空 = 全部已接单;生成后源订单转采购中) */
export async function generatePurchase(purchase_date: string, order_ids?: number[]) {
return createAxios<{ id: number; purchase_no: string }>({
@@ -25,6 +15,14 @@ export async function generatePurchase(purchase_date: string, order_ids?: number
});
}
/** 采购单详情(商品行 × 门店列矩阵) */
export async function getPurchaseDetail(id: number) {
return createAxios<IPurchaseDetail>({
url: `/purchase/order/${id}`,
method: 'get',
});
}
/** C2/C3 导出采购单(blob 下载) */
export async function exportPurchase(id: number, type: PurchaseExportType, format: ExportFormat) {
return downloadBlob(
@@ -34,37 +32,27 @@ export async function exportPurchase(id: number, type: PurchaseExportType, forma
);
}
/** C4 修改采购明细(amount 由后端重算 */
export async function updatePurchaseItem(id: number, data: PurchaseItemUpdateParams) {
return createAxios<{ amount: string }>({
url: `/purchase/order/item/${id}`,
/** C4 门店单元格修改(数量/称重,同步订货明细并重算汇总 */
export async function updatePurchaseCell(
orderItemId: number,
data: { quantity: number; weight?: number },
) {
return createAxios<{ quantity: number; weight: number; amount: number }>({
url: `/purchase/order/cell/${orderItemId}`,
method: 'put',
data,
});
}
/** C5/C6 明细发送供应商 */
export async function sendPurchaseItem(id: number) {
return createAxios({
url: `/purchase/order/item/${id}/send`,
method: 'put',
});
}
/** D3 执行金额分摊 */
export async function allocatePurchase(id: number) {
/** C4 商品行修改(成本/实际称重,同步该商品全部订货明细) */
export async function updatePurchaseRow(
purchaseId: number,
productId: number,
data: { cost_price?: number; weight?: number },
) {
return createAxios<{ count: number }>({
url: `/purchase/order/${id}/allocate`,
method: 'post',
url: `/purchase/order/${purchaseId}/row/${productId}`,
method: 'put',
data,
});
}
/** 分摊结果(按门店 / 按商品聚合) */
export async function getAllocation(id: number) {
return createAxios<IAllocationResult>({
url: `/purchase/order/${id}/allocation`,
method: 'get',
});
}
export type { IPurchaseOrderItem };
+33 -54
View File
@@ -1,36 +1,29 @@
/** 采购分摊记录 */
export interface IPurchaseAllocation {
id?: number;
purchase_item_id?: number;
order_item_id?: number;
store_id?: number;
product_id?: number;
quantity?: string;
weight?: string;
amount?: string;
store?: { id: number; name: string };
product?: { id: number; name: string; unit: string };
/** 采购明细门店单元格(溯源订货明细) */
export interface IPurchaseStoreCell {
order_item_id: number;
store_id: number;
quantity: number;
weight: number;
/** 金额 = 称重>0 ? 称重×单价 : 数量×单价(单价 = 成本/包规) */
amount: number;
}
/** 采购明细 */
export interface IPurchaseOrderItem {
id?: number;
purchase_id?: number;
product_id?: number;
supplier_id?: number;
product_name?: string;
product_spec?: string;
price?: string;
quantity?: string;
weight?: string;
amount?: string;
sort?: number;
is_sent?: number;
sent_at?: string;
supplier_confirmed_at?: string;
remark?: string;
supplier?: { id: number; name: string };
allocations?: IPurchaseAllocation[];
/** 采购明细行(商品维度聚合,行 × 门店列矩阵) */
export interface IPurchaseDetailRow {
product_id: number;
product_name: string;
/** 规格/包规 */
product_spec: string;
unit: string;
supplier_id: number;
supplier?: { id: number; name: string } | null;
/** 成本 */
cost_price: number;
/** 合计数量 */
quantity: number;
/** 合计实际称重 */
weight: number;
cells: Record<number, number>;
}
/** 采购单 */
@@ -38,7 +31,7 @@ export default interface IPurchaseOrder {
id?: number;
purchase_no?: string;
purchase_date?: string;
/** 0待发送 1部分发送 2全部发送 3已完成 */
/** 0进行中 3已完成 */
status?: number;
total_quantity?: string;
total_weight?: string;
@@ -47,34 +40,20 @@ export default interface IPurchaseOrder {
operator_id?: number;
operator?: { id: number; nickname: string };
remark?: string;
items?: IPurchaseOrderItem[];
created_at?: string;
}
/** 采购单详情(矩阵数据) */
export interface IPurchaseDetail {
purchase: IPurchaseOrder;
stores: { id: number; name: string }[];
items: IPurchaseDetailRow[];
}
export const PURCHASE_STATUS_MAP: Record<number, { text: string; color: string }> = {
0: { text: '待发送', color: 'default' },
1: { text: '部分发送', color: 'processing' },
2: { text: '全部发送', color: 'cyan' },
0: { text: '进行中', color: 'processing' },
3: { text: '已完成', color: 'success' },
};
/** 分摊结果聚合行 */
export interface IAllocationAggRow {
store_id?: number;
store_name?: string;
product_id?: number;
product_name?: string;
unit?: string;
quantity: number;
weight: number;
amount: number;
}
export interface IAllocationResult {
by_store: IAllocationAggRow[];
by_product: IAllocationAggRow[];
total_amount: number;
}
export type PurchaseExportType = 'all' | 'category';
export type ExportFormat = 'xlsx' | 'pdf';
+1 -2
View File
@@ -3,7 +3,6 @@ export interface IReconciliationItem {
id?: number;
recon_id?: number;
store_id?: number;
purchase_item_id?: number;
order_item_id?: number;
product_id?: number;
product_name?: string;
@@ -11,7 +10,7 @@ export interface IReconciliationItem {
weight?: string;
/** 公布金额(订货金额) */
publish_amount?: string;
/** 实际金额(分摊金额 */
/** 实际金额(采购成本 */
actual_amount?: string;
/** 差额 = publish actual */
diff_amount?: string;
+179 -384
View File
@@ -1,30 +1,18 @@
import React, { useRef, useState } from 'react';
import {
Button,
DatePicker,
Descriptions,
Drawer,
Dropdown,
Empty,
Form,
InputNumber,
message,
Modal,
Popconfirm,
Space,
Table,
Tabs,
Tag,
Typography,
} from 'antd';
import {
DownloadOutlined,
PlusOutlined,
SendOutlined,
SplitCellsOutlined,
} from '@ant-design/icons';
import { CheckOutlined, DownloadOutlined } from '@ant-design/icons';
import type { TableProps } from 'antd';
import dayjs from 'dayjs';
import XinTable from '@/components/XinTable';
import type {
XinTableColumn,
@@ -33,305 +21,203 @@ import type {
} from '@/components/XinTable/typings.ts';
import type IPurchaseOrder from '@/domain/iPurchaseOrder.ts';
import type {
IAllocationAggRow,
IPurchaseOrderItem,
IPurchaseDetail,
IPurchaseDetailRow,
} from '@/domain/iPurchaseOrder.ts';
import { PURCHASE_STATUS_MAP } from '@/domain/iPurchaseOrder.ts';
import {
allocatePurchase,
exportPurchase,
generatePurchase,
getAllocation,
sendPurchaseItem,
updatePurchaseItem,
getPurchaseDetail,
} from '@/api/purchase/order.ts';
import { Get } from '@/api/common/table.ts';
import { Update } from '@/api/common/table.ts';
import AuthButton from '@/components/AuthButton';
const { Title, Text } = Typography;
/** 行内编辑中的明细值 */
interface EditingItem {
price: number;
quantity: number;
weight: number;
/** 行草稿:成本/称重 + 各门店单元格数量 */
interface RowDraft {
cost_price?: number;
weight?: number;
cells: Record<number, number>;
}
/**
* 采购单管理(C1 汇总生成 / C2-C3 导出 / C4 明细修改 / C5-C6 发送 / D3 分摊
* 采购单管理(C1 在门店订单页合并生成 / C2-C3 导出 / C4 明细矩阵修改)
* 采购单无独立明细表,明细直接溯源门店订货明细:商品行 × 门店列
*/
const PurchaseOrderPage: React.FC = () => {
const tableRef = useRef<XinTableInstance<IPurchaseOrder>>(null);
// 生成采购单
const [generateOpen, setGenerateOpen] = useState(false);
const [generateLoading, setGenerateLoading] = useState(false);
const [generateForm] = Form.useForm<{ purchase_date: dayjs.Dayjs }>();
// 详情抽屉
const [detailOpen, setDetailOpen] = useState(false);
const [detail, setDetail] = useState<IPurchaseOrder | null>(null);
const [detail, setDetail] = useState<IPurchaseDetail | null>(null);
const [detailLoading, setDetailLoading] = useState(false);
const [editing, setEditing] = useState<Record<number, EditingItem>>({});
const [savingItemId, setSavingItemId] = useState<number | null>(null);
// 分摊
const [allocating, setAllocating] = useState(false);
const [allocation, setAllocation] = useState<{
byStore: IAllocationAggRow[];
byProduct: IAllocationAggRow[];
total: number;
} | null>(null);
const [drafts, setDrafts] = useState<Record<number, RowDraft>>({});
const [savingProductId, setSavingProductId] = useState<number | null>(null);
const [completing, setCompleting] = useState(false);
const loadDetail = async (id: number) => {
setDetailLoading(true);
try {
const res = await Get<IPurchaseOrder>('/purchase/order', id);
const purchase = res.data.data ?? null;
setDetail(purchase);
const editingMap: Record<number, EditingItem> = {};
purchase?.items?.forEach((item) => {
if (item.id !== undefined) {
editingMap[item.id] = {
price: Number(item.price ?? 0),
quantity: Number(item.quantity ?? 0),
weight: Number(item.weight ?? 0),
};
}
});
setEditing(editingMap);
const res = await getPurchaseDetail(id);
setDetail(res.data.data ?? null);
setDrafts({});
} finally {
setDetailLoading(false);
}
};
const openDetail = async (id: number) => {
setAllocation(null);
setDetailOpen(true);
await loadDetail(id);
await loadAllocation(id);
};
const loadAllocation = async (id: number) => {
try {
const res = await getAllocation(id);
const data = res.data.data;
if (data) {
setAllocation({
byStore: data.by_store ?? [],
byProduct: data.by_product ?? [],
total: data.total_amount ?? 0,
});
}
} catch {
// 未分摊时忽略
}
const getDraft = (productId: number): RowDraft => drafts[productId] ?? { cells: {} };
const patchDraft = (productId: number, patch: Partial<RowDraft>) => {
setDrafts((prev) => {
const current = prev[productId] ?? { cells: {} };
return {
...prev,
[productId]: { ...current, ...patch, cells: { ...current.cells, ...patch.cells } },
};
});
};
const handleGenerate = async (values: { purchase_date: dayjs.Dayjs }) => {
setGenerateLoading(true);
try {
const res = await generatePurchase(values.purchase_date.format('YYYY-MM-DD'));
message.success(`采购单 ${res.data.data?.purchase_no} 已生成`);
setGenerateOpen(false);
await tableRef.current?.reload();
await openDetail(res.data.data!.id);
} finally {
setGenerateLoading(false);
}
/** 草稿成本(未修改取原值) */
const draftCost = (row: IPurchaseDetailRow): number =>
getDraft(row.product_id).cost_price ?? row.cost_price;
const isRowDirty = (row: IPurchaseDetailRow): boolean => {
// const draft = getDraft(row.product_id);
// if (draft.cost_price !== undefined && draft.cost_price !== row.cost_price) {
// return true;
// }
// if (draft.weight !== undefined && draft.weight !== row.weight) {
// return true;
// }
// return row.cells.some(
// (cell) => draft.cells[cell.order_item_id] !== undefined
// && draft.cells[cell.order_item_id] !== cell.quantity,
// );
};
const isItemDirty = (item: IPurchaseOrderItem): boolean => {
const edit = editing[item.id!];
if (!edit) {
return false;
}
return (
edit.price !== Number(item.price ?? 0) ||
edit.quantity !== Number(item.quantity ?? 0) ||
edit.weight !== Number(item.weight ?? 0)
);
};
const saveItem = async (item: IPurchaseOrderItem) => {
const edit = editing[item.id!];
if (!edit || !isItemDirty(item)) {
/** 保存一行:先落各门店单元格数量,再落行级成本/称重,最后刷新 */
const saveRow = async (row: IPurchaseDetailRow) => {
if (!detail || !isRowDirty(row)) {
return;
}
setSavingItemId(item.id!);
const draft = getDraft(row.product_id);
setSavingProductId(row.product_id);
try {
const res = await updatePurchaseItem(item.id!, {
price: edit.price,
quantity: edit.quantity,
weight: edit.weight,
});
message.success(`金额已重算:¥${res.data.data?.amount}`);
await loadDetail(detail!.id!);
// for (const cell of row.cells) {
// const qty = draft.cells[cell.order_item_id];
// if (qty !== undefined && qty !== cell.quantity) {
// await updatePurchaseCell(cell.order_item_id, { quantity: qty });
// }
// }
// const rowPatch: { cost_price?: number; weight?: number } = {};
// if (draft.cost_price !== undefined && draft.cost_price !== row.cost_price) {
// rowPatch.cost_price = draft.cost_price;
// }
// if (draft.weight !== undefined && draft.weight !== row.weight) {
// rowPatch.weight = draft.weight;
// }
// if (Object.keys(rowPatch).length > 0) {
// await updatePurchaseRow(detail.purchase.id!, row.product_id, rowPatch);
// }
message.success('已保存并同步订货明细');
await loadDetail(detail.purchase.id!);
await tableRef.current?.reload();
} finally {
setSavingItemId(null);
setSavingProductId(null);
}
};
const handleSend = async (item: IPurchaseOrderItem) => {
await sendPurchaseItem(item.id!);
message.success('已发送给供应商');
await loadDetail(detail!.id!);
await tableRef.current?.reload();
};
const handleAllocate = async () => {
setAllocating(true);
const handleComplete = async () => {
if (!detail) {
return;
}
setCompleting(true);
try {
const res = await allocatePurchase(detail!.id!);
message.success(`分摊完成,共 ${res.data.data?.count} 条记录`);
await loadAllocation(detail!.id!);
await Update(`/purchase/order/${detail.purchase.id}`, { status: 3 });
message.success('采购单已标记完成');
await loadDetail(detail.purchase.id!);
await tableRef.current?.reload();
} finally {
setAllocating(false);
setCompleting(false);
}
};
const itemColumns: TableProps<IPurchaseOrderItem>['columns'] = [
{ title: '序号', dataIndex: 'sort', width: 60, align: 'center' },
{ title: '品名', dataIndex: 'product_name', width: 130 },
{ title: '规格', dataIndex: 'product_spec', width: 110, render: (v) => v || '-' },
{
title: '供应商',
dataIndex: 'supplier',
width: 130,
render: (_, record) => record.supplier?.name ?? '-',
},
{
title: '单价',
dataIndex: 'price',
width: 130,
render: (_, record) => (
<InputNumber
size="small"
min={0}
precision={2}
prefix="¥"
value={editing[record.id!]?.price}
onChange={(v) =>
setEditing((prev) => ({
...prev,
[record.id!]: { ...prev[record.id!], price: v ?? 0 },
}))
}
className="!w-24"
/>
),
},
{
title: '数量',
dataIndex: 'quantity',
width: 120,
render: (_, record) => (
<InputNumber
size="small"
min={0}
precision={2}
value={editing[record.id!]?.quantity}
onChange={(v) =>
setEditing((prev) => ({
...prev,
[record.id!]: { ...prev[record.id!], quantity: v ?? 0 },
}))
}
className="!w-20"
/>
),
},
{
title: '实际称重',
dataIndex: 'weight',
width: 130,
render: (_, record) => (
<InputNumber
size="small"
min={0}
precision={3}
value={editing[record.id!]?.weight}
onChange={(v) =>
setEditing((prev) => ({
...prev,
[record.id!]: { ...prev[record.id!], weight: v ?? 0 },
}))
}
className="!w-24"
/>
),
},
{
title: '金额',
dataIndex: 'amount',
width: 100,
align: 'right',
render: (v) => <Text strong>¥{String(v)}</Text>,
},
{
title: '发送状态',
dataIndex: 'is_sent',
width: 150,
render: (_, record) =>
record.is_sent === 1 ? (
<Tag color="success">
{record.sent_at ? ` ${record.sent_at}` : ''}
</Tag>
) : (
<Tag></Tag>
),
},
{
title: '操作',
key: 'action',
width: 150,
fixed: 'right',
render: (_, record) => (
<Space size={4}>
/** 明细矩阵列:品名/供应商/包规/单位/成本/单价 + 每门店一组(数量/金额)+ 合计 + 操作 */
const buildItemColumns = (): TableProps<IPurchaseDetailRow>['columns'] => {
const fixed: NonNullable<TableProps<IPurchaseDetailRow>['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: 80,
align: 'center',
render: (_, row) => `¥${(Number(row.cost_price) / Number(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: 110, align: 'center', render: (v) => v || '-' }
];
const storeColumns = (detail?.stores ?? []).map((store) => ({
title: store.name,
key: `store-${store.id}`,
align: 'center' as const,
render: (_: unknown, row: IPurchaseDetailRow) => {
const cell = row.cells[store.id];
if (!cell) {
return <Text type="secondary">-</Text>;
}
return (
<Text>{ row.cells[store.id] }</Text>
);
},
}));
const tail: NonNullable<TableProps<IPurchaseDetailRow>['columns']> = [
{
title: '合计数量',
key: 'total_quantity',
width: 90,
align: 'center',
render: (_, row) => Object.values(row.cells).reduce((a, b) => a + b, 0),
},
{
title: '操作',
key: 'action',
width: 80,
fixed: 'right',
render: (_, row) => (
<AuthButton auth="purchase.order.update">
<Button
size="small"
type="link"
disabled={!isItemDirty(record)}
loading={savingItemId === record.id}
onClick={() => saveItem(record)}
disabled={!isRowDirty(row)}
loading={savingProductId === row.product_id}
onClick={() => saveRow(row)}
>
</Button>
</AuthButton>
{record.is_sent !== 1 ? (
<AuthButton auth="purchase.order.send">
<Popconfirm
title="确认发送该明细给供应商?"
onConfirm={() => handleSend(record)}
>
<Button size="small" type="link" icon={<SendOutlined />}>
</Button>
</Popconfirm>
</AuthButton>
) : null}
</Space>
),
},
];
),
},
];
const aggColumns = (nameTitle: string): TableProps<IAllocationAggRow>['columns'] => [
{
title: nameTitle,
key: 'name',
render: (_, row) => row.store_name ?? row.product_name ?? '-',
},
{ title: '数量', dataIndex: 'quantity', align: 'right' },
{ title: '重量', dataIndex: 'weight', align: 'right' },
{
title: '金额',
dataIndex: 'amount',
align: 'right',
render: (v) => `¥${v}`,
},
];
return [...fixed, ...storeColumns, ...tail];
};
const columns: XinTableColumn<IPurchaseOrder>[] = [
{
@@ -425,15 +311,6 @@ const PurchaseOrderPage: React.FC = () => {
tableRef,
operateRender,
formProps: false,
actionBarRender: (dom) => [
<AuthButton key="generate" auth="purchase.order.generate">
<Button type="primary" icon={<PlusOutlined />} onClick={() => setGenerateOpen(true)}>
</Button>
</AuthButton>,
dom.search,
dom.keywordSearch,
],
};
return (
@@ -441,144 +318,62 @@ const PurchaseOrderPage: React.FC = () => {
<div className="mb-5">
<Title level={3}></Title>
<Text type="secondary">
/
× //
</Text>
</div>
<XinTable<IPurchaseOrder> {...tableProps} />
{/* 生成采购单 */}
<Modal
title="生成采购单"
open={generateOpen}
onCancel={() => setGenerateOpen(false)}
onOk={() => generateForm.submit()}
confirmLoading={generateLoading}
okText="确认生成"
destroyOnHidden
>
<div className="py-2 text-gray-500">
</div>
<Form
form={generateForm}
layout="vertical"
onFinish={handleGenerate}
initialValues={{ purchase_date: dayjs() }}
>
<Form.Item
label="采购日期"
name="purchase_date"
rules={[{ required: true, message: '请选择采购日期' }]}
>
<DatePicker className="w-full" allowClear={false} />
</Form.Item>
</Form>
</Modal>
{/* 采购单详情 */}
{/* 采购单详情:商品行 × 门店列矩阵 */}
<Drawer
title={detail ? `采购单 ${detail.purchase_no}` : '采购单详情'}
title={detail ? `采购单 ${detail.purchase.purchase_no}` : '采购单详情'}
open={detailOpen}
onClose={() => setDetailOpen(false)}
size={1080}
size={1200}
loading={detailLoading}
>
{detail ? (
<>
<Descriptions column={3} size="small" bordered>
<Descriptions.Item label="采购日期">{detail.purchase_date}</Descriptions.Item>
<Descriptions
column={3}
size="small"
bordered
extra={
detail.purchase.status === 0 ? (
<AuthButton auth="purchase.order.update">
<Popconfirm title="确认标记该采购单为已完成?" onConfirm={handleComplete}>
<Button size="small" icon={<CheckOutlined />} loading={completing}>
</Button>
</Popconfirm>
</AuthButton>
) : undefined
}
>
<Descriptions.Item label="采购日期">{detail.purchase.purchase_date}</Descriptions.Item>
<Descriptions.Item label="状态">
<Tag color={PURCHASE_STATUS_MAP[detail.status ?? 0]?.color}>
{PURCHASE_STATUS_MAP[detail.status ?? 0]?.text}
<Tag color={PURCHASE_STATUS_MAP[detail.purchase.status ?? 0]?.color}>
{PURCHASE_STATUS_MAP[detail.purchase.status ?? 0]?.text}
</Tag>
</Descriptions.Item>
<Descriptions.Item label="制单人">
{detail.operator?.nickname ?? '-'}
{detail.purchase.operator?.nickname ?? '-'}
</Descriptions.Item>
<Descriptions.Item label="预估金额">¥{detail.estimate_amount}</Descriptions.Item>
<Descriptions.Item label="实际金额">¥{detail.actual_amount}</Descriptions.Item>
<Descriptions.Item label="总重量">{detail.total_weight}</Descriptions.Item>
<Descriptions.Item label="预估金额">¥{detail.purchase.estimate_amount}</Descriptions.Item>
<Descriptions.Item label="实际金额">¥{detail.purchase.actual_amount}</Descriptions.Item>
<Descriptions.Item label="总重量">{detail.purchase.total_weight}</Descriptions.Item>
</Descriptions>
<Tabs
className="mt-4"
items={[
{
key: 'items',
label: `采购明细(${detail.items?.length ?? 0}`,
children: (
<>
<div className="mb-2 text-gray-500">
&gt;0 × ×
</div>
<Table<IPurchaseOrderItem>
rowKey="id"
size="small"
columns={itemColumns}
dataSource={detail.items ?? []}
pagination={false}
scroll={{ x: 1200 }}
/>
</>
),
},
{
key: 'allocation',
label: '金额分摊',
children: (
<>
<Space className="mb-3">
<AuthButton auth="purchase.order.allocate">
<Popconfirm
title="执行金额分摊?"
description="按订货比例将实际金额摊到各门店单品,重复执行会先清空旧分摊记录。"
onConfirm={handleAllocate}
>
<Button
type="primary"
icon={<SplitCellsOutlined />}
loading={allocating}
>
</Button>
</Popconfirm>
</AuthButton>
{allocation ? (
<Text type="secondary">
¥{allocation.total}
</Text>
) : null}
</Space>
{allocation && (allocation.byStore.length > 0 || allocation.byProduct.length > 0) ? (
<div className="grid grid-cols-2 gap-4">
<div>
<Title level={5}></Title>
<Table<IAllocationAggRow>
rowKey={(row) => String(row.store_id)}
size="small"
columns={aggColumns('门店')}
dataSource={allocation.byStore}
pagination={false}
/>
</div>
<div>
<Title level={5}></Title>
<Table<IAllocationAggRow>
rowKey={(row) => String(row.product_id)}
size="small"
columns={aggColumns('商品')}
dataSource={allocation.byProduct}
pagination={false}
/>
</div>
</div>
) : (
<Empty description="暂无分摊记录,请先录入实际金额后执行分摊" />
)}
</>
),
},
]}
<div className="my-2 text-gray-500">
= ÷ = × &gt;0 ×
</div>
<Table<IPurchaseDetailRow>
rowKey="product_id"
size="small"
bordered
columns={buildItemColumns()}
dataSource={detail.items}
pagination={false}
scroll={{ x: 'max-content' }}
/>
</>
) : null}