优化采购单

This commit is contained in:
liu
2026-08-13 10:53:11 +08:00
parent 79e3ed4a8a
commit 099e92b1ff
9 changed files with 221 additions and 133 deletions
+139 -107
View File
@@ -20,7 +20,7 @@ import {
Tag,
Typography,
} from 'antd';
import { CheckOutlined, DownloadOutlined, EditOutlined } from '@ant-design/icons';
import {DownloadOutlined, EditOutlined, UnorderedListOutlined} from '@ant-design/icons';
import type { TableProps } from 'antd';
import XinTable from '@/components/XinTable';
import type {
@@ -242,15 +242,11 @@ const PurchaseOrderPage: React.FC = () => {
}
};
const handleComplete = async () => {
if (!detail) {
return;
}
const handleComplete = async (id: number) => {
setCompleting(true);
try {
await Update(`/purchase/order/${detail.purchase.id}`, { status: 3 });
await Update(`/purchase/order/${id}/finish`, { status: 3 });
message.success('采购单已标记完成');
await loadDetail(detail.purchase.id!);
await tableRef.current?.reload();
} finally {
setCompleting(false);
@@ -310,16 +306,20 @@ const PurchaseOrderPage: React.FC = () => {
fixed: 'right',
align: 'center',
render: (_, row) => (
<AuthButton auth="purchase.order.update">
<Button
size="small"
type="link"
icon={<EditOutlined />}
onClick={() => openEdit(row)}
>
</Button>
</AuthButton>
<>
{ detail?.purchase.status === 0 ? (
<AuthButton auth="purchase.order.update">
<Button
size="small"
type="link"
icon={<EditOutlined />}
onClick={() => openEdit(row)}
>
</Button>
</AuthButton>
) : '-' }
</>
),
},
];
@@ -426,14 +426,16 @@ const PurchaseOrderPage: React.FC = () => {
dataIndex: 'purchase_no',
valueType: 'text',
hideInForm: true,
align: 'center',
width: 260,
render: (_, record) => <Text copyable={{ text: record.purchase_no }}>{record.purchase_no}</Text>,
},
{
title: '采购日期',
dataIndex: 'purchase_date',
valueType: 'dateRange',
hideInForm: true,
valueType: 'date',
align: 'center',
hideInForm: true,
render: (_, record) => record.purchase_date,
},
{
@@ -441,15 +443,20 @@ const PurchaseOrderPage: React.FC = () => {
dataIndex: 'estimate_amount',
hideInForm: true,
hideInSearch: true,
align: 'right',
align: 'center',
render: (_, record) => `¥${record.estimate_amount}`,
},
{
title: '实际成本',
dataIndex: 'actual_amount',
hideInForm: true,
hideInSearch: true,
align: 'right',
valueType: "digit",
fieldProps: {
min: 0,
precision: 3,
placeholder: "请输入单价"
},
align: 'center',
render: (_, record) =>
Number(record.actual_amount) > 0 ? (
<Text strong>¥{record.actual_amount}</Text>
@@ -457,6 +464,28 @@ const PurchaseOrderPage: React.FC = () => {
<Text type="secondary"></Text>
),
},
{
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',
@@ -484,24 +513,18 @@ const PurchaseOrderPage: React.FC = () => {
},
];
const operateRender: XinTableProps<IPurchaseOrder>['operateRender'] = (record) => [
<Button key="detail" size="small" onClick={() => openDetail(record.id!)}>
</Button>,
<AuthButton key="export" auth="purchase.order.export">
<Dropdown
menu={{
items: [
{ key: 'all', label: '导出全品类', onClick: () => exportPurchase(record.id!, 'all') },
{ key: 'category', label: '仅导出蔬果分类', onClick: () => exportPurchase(record.id!, 'category') },
],
}}
>
<Button size="small" type="primary" ghost icon={<DownloadOutlined />}>
</Button>
</Dropdown>
</AuthButton>,
const operateRender: XinTableProps<IPurchaseOrder>['operateRender'] = (record, dom) => [
record.status === 0 ? dom.edit : null,
<Button key="detail" size="small" icon={<UnorderedListOutlined />} type={'primary'} onClick={() => openDetail(record.id!)} />,
record.status === 0 ? (
<AuthButton auth="purchase.order.update">
<Popconfirm title="确认该采购单已完成?" description="已完成采购单将锁定,不能再修改订单信息" onConfirm={() => handleComplete(record.id!)}>
<Button type={'primary'} size="small" variant={'solid'} color={'green'} loading={completing}>
</Button>
</Popconfirm>
</AuthButton>
) : null
];
const tableProps: XinTableProps<IPurchaseOrder> = {
@@ -511,6 +534,9 @@ const PurchaseOrderPage: React.FC = () => {
accessName: 'purchase.order',
tableRef,
operateRender,
operateProps: {
width: 300
},
formProps: false,
};
@@ -531,19 +557,10 @@ const PurchaseOrderPage: React.FC = () => {
onClose={() => setDetailOpen(false)}
size={1200}
loading={detailLoading}
extra={detail?.purchase.status === 0 && (
<AuthButton auth="purchase.order.update">
<Popconfirm title="确认标记该采购单为已完成?" onConfirm={handleComplete}>
<Button size="small" icon={<CheckOutlined />} loading={completing}>
</Button>
</Popconfirm>
</AuthButton>
)}
>
{detail && (
<>
<Title level={5} className="mt-5! mb-3!">
<Title level={5} className="mb-3!">
</Title>
<Descriptions column={3} size="small" bordered>
@@ -556,9 +573,12 @@ const PurchaseOrderPage: React.FC = () => {
<Descriptions.Item label="制单人">
{detail.purchase.operator?.nickname ?? '-'}
</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.estimate_amount}</Descriptions.Item>
<Descriptions.Item label="实际成本">¥{detail.purchase.actual_amount}</Descriptions.Item>
<Descriptions.Item label="总件数">{detail.purchase.total_quantity}</Descriptions.Item>
<Descriptions.Item label="总重量">{detail.purchase.total_weight}</Descriptions.Item>
<Descriptions.Item label="创建时间" span={2}>{detail.purchase.created_at}</Descriptions.Item>
<Descriptions.Item label="备注" span={3}>{detail.purchase.remark}</Descriptions.Item>
</Descriptions>
<Tabs
@@ -566,65 +586,77 @@ const PurchaseOrderPage: React.FC = () => {
onChange={setDetailTab}
className="mt-3!"
items={[
{
key: 'items',
label: '商品明细',
children: (
<Table<IPurchaseDetailRow>
{ key: 'items', label: '商品明细' },
{ key: 'stores', label: '门店购买详情' },
]}
/>
{ detailTab === 'stores' ? (
<>
<div className="mb-3 flex items-center gap-2">
<Text></Text>
<Select
value={storeId || undefined}
onChange={(value) => setStoreId(value)}
placeholder="选择门店"
className="w-60!"
showSearch
optionFilterProp="label"
options={detail.stores.map((s) => ({ value: s.id, label: s.name }))}
/>
</div>
<Spin spinning={storeLoading}>
{storeSummary && storeSummary.items.length > 0 ? (
<Table<IPurchaseStoreItem>
rowKey="product_id"
size="small"
bordered
columns={buildItemColumns()}
dataSource={detail.items}
columns={storeColumns}
dataSource={storeSummary.items}
pagination={false}
scroll={{ x: 'max-content' }}
summary={renderSummary}
summary={renderStoreTotal}
/>
),
},
{
key: 'stores',
label: '门店购买详情',
children: (
<>
<div className="mb-3 flex items-center gap-2">
<Text></Text>
<Select
value={storeId || undefined}
onChange={(value) => setStoreId(value)}
placeholder="选择门店"
className="w-60!"
showSearch
optionFilterProp="label"
options={detail.stores.map((s) => ({ value: s.id, label: s.name }))}
/>
</div>
<Spin spinning={storeLoading}>
{storeSummary && storeSummary.items.length > 0 ? (
<Table<IPurchaseStoreItem>
rowKey="product_id"
size="small"
bordered
columns={storeColumns}
dataSource={storeSummary.items}
pagination={false}
summary={renderStoreTotal}
/>
) : (
!storeLoading && (
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
description="该门店在此采购单中无采购商品"
className="py-8!"
/>
)
)}
</Spin>
</>
),
},
]}
/>
) : (
!storeLoading && (
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
description="该门店在此采购单中无采购商品"
className="py-8!"
/>
)
)}
</Spin>
</>
) : (
<>
<Space style={{ marginBottom: 20 }}>
<AuthButton key="export" auth="purchase.order.export">
<Dropdown
menu={{
items: [
{ key: 'all', label: '导出全品类', onClick: () => detail && exportPurchase(detail.purchase.id!, 'all') },
{ key: 'category', label: '仅导出蔬果分类', onClick: () => detail && exportPurchase(detail.purchase.id!, 'category') },
],
}}
>
<Button type="primary" ghost icon={<DownloadOutlined />}>
</Button>
</Dropdown>
</AuthButton>
</Space>
<Table<IPurchaseDetailRow>
rowKey="product_id"
size="small"
bordered
columns={buildItemColumns()}
dataSource={detail.items}
pagination={false}
scroll={{ x: 'max-content' }}
summary={renderSummary}
/>
</>
) }
</>
)}
</Drawer>
@@ -766,7 +798,7 @@ const PurchaseOrderPage: React.FC = () => {
{item.weight ?? '-'}
</div>
<div className="w-40 shrink-0 text-center">
{item.editable ? (
{detail?.purchase.status === 0 ? (
<Space size={0}>
<AuthButton auth="purchase.order.update">
<Button