update ourchase
This commit is contained in:
parent
4626bf2771
commit
4ba4dc98bf
13
.umirc.ts
13
.umirc.ts
@ -65,6 +65,19 @@ export default defineConfig({
|
|||||||
{ name: '库存盘点', path: '/inventory/checks', component: './Inventory/Checks' },
|
{ name: '库存盘点', path: '/inventory/checks', component: './Inventory/Checks' },
|
||||||
{ name: '库存调整', path: '/inventory/adjusts', component: './Inventory/Adjusts' },
|
{ name: '库存调整', path: '/inventory/adjusts', component: './Inventory/Adjusts' },
|
||||||
{ name: 'Excel导入', path: '/inventory/import', component: './Inventory/Import' },
|
{ name: 'Excel导入', path: '/inventory/import', component: './Inventory/Import' },
|
||||||
|
{ name: '库存变动记录', path: '/inventory/logs', component: './Inventory/Logs' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '采购管理',
|
||||||
|
path: '/purchase',
|
||||||
|
icon: 'ShoppingCartOutlined',
|
||||||
|
routes: [
|
||||||
|
{ name: '供应商管理', path: '/purchase/suppliers', component: './Purchase/Suppliers' },
|
||||||
|
{ name: '采购订单', path: '/purchase/orders', component: './Purchase/Orders' },
|
||||||
|
{ name: '采购订单详情', path: '/purchase/orders/:id', component: './Purchase/Orders/detail', hideInMenu: true },
|
||||||
|
{ name: '入库记录', path: '/purchase/receipts', component: './Purchase/Receipts' },
|
||||||
|
{ name: '采购报表', path: '/purchase/stats', component: './Purchase/Stats' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -30,7 +30,11 @@ const LoginPage: React.FC = () => {
|
|||||||
value: t.id,
|
value: t.id,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
message.error('获取公司列表失败,请稍后重试');
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
message.error('获取公司列表失败,请检查网络连接');
|
||||||
} finally {
|
} finally {
|
||||||
setSearching(false);
|
setSearching(false);
|
||||||
}
|
}
|
||||||
|
|||||||
211
src/pages/Purchase/Orders/detail.tsx
Normal file
211
src/pages/Purchase/Orders/detail.tsx
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
import { ArrowLeftOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
|
import { ProDescriptions, ProTable } from '@ant-design/pro-components';
|
||||||
|
import { Button, Card, Col, Descriptions, Form, Input, Modal, Row, Space, Tag, Typography, message } from 'antd';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { history, useParams } from '@umijs/max';
|
||||||
|
import {
|
||||||
|
confirmPurchaseOrder,
|
||||||
|
createPurchasePayment,
|
||||||
|
createPurchaseReceipt,
|
||||||
|
getPurchaseOrder,
|
||||||
|
listPurchasePayments,
|
||||||
|
listPurchaseReceipts,
|
||||||
|
} from '@/services/api';
|
||||||
|
import type { PurchaseOrder, PurchasePayment, PurchaseReceipt } from '@/types/api';
|
||||||
|
|
||||||
|
const STATUS_LABELS: Record<number, string> = { 0: '草稿', 1: '已确认', 2: '已取消' };
|
||||||
|
const RECEIPT_STATUS_LABELS: Record<number, string> = { 0: '未入库', 1: '部分入库', 2: '已入库' };
|
||||||
|
const PAYMENT_STATUS_LABELS: Record<number, string> = { 0: '未付款', 1: '部分付款', 2: '已付清' };
|
||||||
|
|
||||||
|
const OrderDetailPage: React.FC = () => {
|
||||||
|
const { id } = useParams<{ id: string }>();
|
||||||
|
const [order, setOrder] = useState<PurchaseOrder | null>(null);
|
||||||
|
const [receipts, setReceipts] = useState<PurchaseReceipt[]>([]);
|
||||||
|
const [payments, setPayments] = useState<PurchasePayment[]>([]);
|
||||||
|
const [receiptModalOpen, setReceiptModalOpen] = useState(false);
|
||||||
|
const [paymentModalOpen, setPaymentModalOpen] = useState(false);
|
||||||
|
const [receiptForm] = Form.useForm();
|
||||||
|
const [paymentForm] = Form.useForm();
|
||||||
|
|
||||||
|
const loadOrder = async () => {
|
||||||
|
if (!id) return;
|
||||||
|
const [orderRes, receiptRes, paymentRes] = await Promise.all([
|
||||||
|
getPurchaseOrder(id),
|
||||||
|
listPurchaseReceipts({ orderId: id, pageSize: 100 }),
|
||||||
|
listPurchasePayments({ orderId: id, pageSize: 100 }),
|
||||||
|
]);
|
||||||
|
if (orderRes?.code === 0) setOrder(orderRes.data);
|
||||||
|
if (receiptRes?.code === 0) setReceipts(receiptRes.data?.list || []);
|
||||||
|
if (paymentRes?.code === 0) setPayments(paymentRes.data?.list || []);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => { loadOrder(); }, [id]);
|
||||||
|
|
||||||
|
const handleConfirmOrder = async () => {
|
||||||
|
await confirmPurchaseOrder(id!);
|
||||||
|
message.success('订单已确认');
|
||||||
|
loadOrder();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreateReceipt = async () => {
|
||||||
|
if (!order) return;
|
||||||
|
const values = await receiptForm.validateFields();
|
||||||
|
const details = order.details.map((d) => ({
|
||||||
|
orderDetailId: d.detailId,
|
||||||
|
productId: d.productId,
|
||||||
|
actualQty: values[`qty_${d.detailId}`] || '0',
|
||||||
|
unitCost: d.unitPrice,
|
||||||
|
}));
|
||||||
|
await createPurchaseReceipt({ orderId: id!, receiptDate: values.receiptDate, receivedBy: values.receivedBy, details });
|
||||||
|
message.success('入库单已创建');
|
||||||
|
setReceiptModalOpen(false);
|
||||||
|
loadOrder();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreatePayment = async () => {
|
||||||
|
const values = await paymentForm.validateFields();
|
||||||
|
await createPurchasePayment({ orderId: id!, ...values });
|
||||||
|
message.success('付款记录已创建');
|
||||||
|
setPaymentModalOpen(false);
|
||||||
|
loadOrder();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!order) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Space direction="vertical" style={{ width: '100%' }} size="large">
|
||||||
|
<Space>
|
||||||
|
<Button icon={<ArrowLeftOutlined />} onClick={() => history.back()}>返回</Button>
|
||||||
|
<Typography.Title level={4} style={{ margin: 0 }}>采购订单详情 — {order.orderNo}</Typography.Title>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<Descriptions column={3} bordered size="small">
|
||||||
|
<Descriptions.Item label="供应商">{order.supplierName}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="采购日期">{order.orderDate}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="采购员">{order.purchaseBy}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="合同金额">¥ {order.contractAmount}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="已入库数量">{order.receivedQty}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="已付款">¥ {order.paidAmount}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="入库状态">{RECEIPT_STATUS_LABELS[order.receiptStatus]}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="付款状态">{PAYMENT_STATUS_LABELS[order.paymentStatus]}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="订单状态">
|
||||||
|
<Tag color={order.status === 1 ? 'processing' : order.status === 2 ? 'error' : 'default'}>
|
||||||
|
{STATUS_LABELS[order.status]}
|
||||||
|
</Tag>
|
||||||
|
</Descriptions.Item>
|
||||||
|
{order.remark && <Descriptions.Item label="备注" span={3}>{order.remark}</Descriptions.Item>}
|
||||||
|
</Descriptions>
|
||||||
|
{order.status === 0 && (
|
||||||
|
<Button type="primary" style={{ marginTop: 16 }} onClick={() => Modal.confirm({ title: '确认此采购订单?', onOk: handleConfirmOrder })}>
|
||||||
|
确认订单
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card title="采购明细">
|
||||||
|
<ProTable
|
||||||
|
headerTitle={false}
|
||||||
|
rowKey="detailId"
|
||||||
|
search={false}
|
||||||
|
pagination={false}
|
||||||
|
dataSource={order.details}
|
||||||
|
columns={[
|
||||||
|
{ title: '产品', dataIndex: 'productName' },
|
||||||
|
{ title: '规格', dataIndex: 'spec' },
|
||||||
|
{ title: '颜色', dataIndex: 'color' },
|
||||||
|
{ title: '采购数量', dataIndex: 'quantity' },
|
||||||
|
{ title: '单价', dataIndex: 'unitPrice' },
|
||||||
|
{ title: '小计', dataIndex: 'amount' },
|
||||||
|
{ title: '已入库', dataIndex: 'receivedQty' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
title="入库记录"
|
||||||
|
extra={order.status === 1 && order.receiptStatus !== 2 && (
|
||||||
|
<Button icon={<PlusOutlined />} onClick={() => { receiptForm.resetFields(); setReceiptModalOpen(true); }}>新建入库单</Button>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ProTable
|
||||||
|
headerTitle={false}
|
||||||
|
rowKey="receiptId"
|
||||||
|
search={false}
|
||||||
|
pagination={false}
|
||||||
|
dataSource={receipts}
|
||||||
|
columns={[
|
||||||
|
{ title: '入库单号', dataIndex: 'receiptNo' },
|
||||||
|
{ title: '入库日期', dataIndex: 'receiptDate' },
|
||||||
|
{ title: '入库人', dataIndex: 'receivedBy' },
|
||||||
|
{ title: '状态', dataIndex: 'status', render: (_, r) => r.status === 1 ? <Tag color="success">已确认</Tag> : <Tag>草稿</Tag> },
|
||||||
|
{ title: '创建时间', dataIndex: 'createdAt', valueType: 'dateTime' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
title="付款记录"
|
||||||
|
extra={order.status === 1 && order.paymentStatus !== 2 && (
|
||||||
|
<Button icon={<PlusOutlined />} onClick={() => { paymentForm.resetFields(); setPaymentModalOpen(true); }}>新增付款</Button>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ProTable
|
||||||
|
headerTitle={false}
|
||||||
|
rowKey="paymentId"
|
||||||
|
search={false}
|
||||||
|
pagination={false}
|
||||||
|
dataSource={payments}
|
||||||
|
columns={[
|
||||||
|
{ title: '付款日期', dataIndex: 'paymentDate' },
|
||||||
|
{ title: '金额', dataIndex: 'amount', render: (v) => `¥ ${v}` },
|
||||||
|
{ title: '付款方式', dataIndex: 'paymentMethod' },
|
||||||
|
{ title: '操作人', dataIndex: 'operator' },
|
||||||
|
{ title: '创建时间', dataIndex: 'createdAt', valueType: 'dateTime' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Modal title="新建入库单" open={receiptModalOpen} onOk={handleCreateReceipt} onCancel={() => setReceiptModalOpen(false)} width={600} destroyOnClose>
|
||||||
|
<Form form={receiptForm} layout="vertical">
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item name="receiptDate" label="入库日期" rules={[{ required: true }]}>
|
||||||
|
<Input type="date" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item name="receivedBy" label="入库人">
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{order.details.map((d) => (
|
||||||
|
<Form.Item key={d.detailId} name={`qty_${d.detailId}`} label={`${d.productName} ${d.spec} ${d.color} 入库数量(采购数量: ${d.quantity})`}>
|
||||||
|
<Input type="number" min={0} />
|
||||||
|
</Form.Item>
|
||||||
|
))}
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<Modal title="新增付款" open={paymentModalOpen} onOk={handleCreatePayment} onCancel={() => setPaymentModalOpen(false)} destroyOnClose>
|
||||||
|
<Form form={paymentForm} layout="vertical">
|
||||||
|
<Form.Item name="paymentDate" label="付款日期" rules={[{ required: true }]}>
|
||||||
|
<Input type="date" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="amount" label="金额" rules={[{ required: true }]}>
|
||||||
|
<Input type="number" min={0} step="0.01" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="paymentMethod" label="付款方式">
|
||||||
|
<Input placeholder="现金/银行转账/微信/支付宝" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="remark" label="备注">
|
||||||
|
<Input.TextArea rows={2} />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OrderDetailPage;
|
||||||
101
src/pages/Purchase/Orders/index.tsx
Normal file
101
src/pages/Purchase/Orders/index.tsx
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
|
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
|
||||||
|
import { Button, Modal, Tag, message } from 'antd';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import { history } from '@umijs/max';
|
||||||
|
import { cancelPurchaseOrder, confirmPurchaseOrder, listPurchaseOrders } from '@/services/api';
|
||||||
|
import type { PurchaseOrder } from '@/types/api';
|
||||||
|
|
||||||
|
const STATUS_MAP: Record<number, { text: string; color: string }> = {
|
||||||
|
0: { text: '草稿', color: 'default' },
|
||||||
|
1: { text: '已确认', color: 'processing' },
|
||||||
|
2: { text: '已取消', color: 'error' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const RECEIPT_STATUS_MAP: Record<number, string> = {
|
||||||
|
0: '未入库',
|
||||||
|
1: '部分入库',
|
||||||
|
2: '已入库',
|
||||||
|
};
|
||||||
|
|
||||||
|
const PAYMENT_STATUS_MAP: Record<number, string> = {
|
||||||
|
0: '未付款',
|
||||||
|
1: '部分付款',
|
||||||
|
2: '已付清',
|
||||||
|
};
|
||||||
|
|
||||||
|
const OrdersPage: React.FC = () => {
|
||||||
|
const actionRef = useRef<ActionType>();
|
||||||
|
|
||||||
|
const handleConfirm = async (id: string) => {
|
||||||
|
await confirmPurchaseOrder(id);
|
||||||
|
message.success('确认成功');
|
||||||
|
actionRef.current?.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = async (id: string) => {
|
||||||
|
await cancelPurchaseOrder(id);
|
||||||
|
message.success('取消成功');
|
||||||
|
actionRef.current?.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns: ProColumns<PurchaseOrder>[] = [
|
||||||
|
{ title: '订单编号', dataIndex: 'orderNo', copyable: true },
|
||||||
|
{ title: '供应商', dataIndex: 'supplierName', search: false },
|
||||||
|
{ title: '采购日期', dataIndex: 'orderDate', search: false },
|
||||||
|
{ title: '合同金额', dataIndex: 'contractAmount', search: false },
|
||||||
|
{
|
||||||
|
title: '入库状态',
|
||||||
|
dataIndex: 'receiptStatus',
|
||||||
|
search: false,
|
||||||
|
render: (_, r) => RECEIPT_STATUS_MAP[r.receiptStatus] ?? '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '付款状态',
|
||||||
|
dataIndex: 'paymentStatus',
|
||||||
|
search: false,
|
||||||
|
render: (_, r) => PAYMENT_STATUS_MAP[r.paymentStatus] ?? '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
search: false,
|
||||||
|
render: (_, r) => {
|
||||||
|
const s = STATUS_MAP[r.status] ?? STATUS_MAP[0];
|
||||||
|
return <Tag color={s.color}>{s.text}</Tag>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ title: '创建时间', dataIndex: 'createdAt', valueType: 'dateTime', search: false },
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
valueType: 'option',
|
||||||
|
render: (_, r) => [
|
||||||
|
<a key="view" onClick={() => history.push(`/purchase/orders/${r.orderId}`)}>详情</a>,
|
||||||
|
r.status === 0 && (
|
||||||
|
<a key="confirm" onClick={() => Modal.confirm({ title: '确认采购订单?', onOk: () => handleConfirm(r.orderId) })}>确认</a>
|
||||||
|
),
|
||||||
|
r.status === 1 && r.receiptStatus === 0 && (
|
||||||
|
<a key="cancel" style={{ color: '#ff4d4f' }} onClick={() => Modal.confirm({ title: '确认取消?', onOk: () => handleCancel(r.orderId) })}>取消</a>
|
||||||
|
),
|
||||||
|
].filter(Boolean),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ProTable<PurchaseOrder>
|
||||||
|
actionRef={actionRef}
|
||||||
|
headerTitle="采购订单"
|
||||||
|
rowKey="orderId"
|
||||||
|
columns={columns}
|
||||||
|
request={async (params) => {
|
||||||
|
const res = await listPurchaseOrders({ page: params.current, pageSize: params.pageSize });
|
||||||
|
return { data: res?.data?.list || [], total: res?.data?.total || 0, success: res?.code === 0 };
|
||||||
|
}}
|
||||||
|
toolBarRender={() => [
|
||||||
|
<Button key="add" type="primary" icon={<PlusOutlined />} onClick={() => history.push('/purchase/orders/new')}>创建采购订单</Button>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OrdersPage;
|
||||||
55
src/pages/Purchase/Receipts/index.tsx
Normal file
55
src/pages/Purchase/Receipts/index.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { ProColumns, ProTable } from '@ant-design/pro-components';
|
||||||
|
import { Modal, Tag, message } from 'antd';
|
||||||
|
import { confirmPurchaseReceipt, listPurchaseReceipts } from '@/services/api';
|
||||||
|
import type { PurchaseReceipt } from '@/types/api';
|
||||||
|
import { ActionType } from '@ant-design/pro-components';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
|
||||||
|
const ReceiptsPage: React.FC = () => {
|
||||||
|
const actionRef = useRef<ActionType>();
|
||||||
|
|
||||||
|
const handleConfirm = async (id: string) => {
|
||||||
|
await confirmPurchaseReceipt(id);
|
||||||
|
message.success('入库单已确认');
|
||||||
|
actionRef.current?.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns: ProColumns<PurchaseReceipt>[] = [
|
||||||
|
{ title: '入库单号', dataIndex: 'receiptNo', copyable: true },
|
||||||
|
{ title: '采购订单ID', dataIndex: 'orderId', search: false },
|
||||||
|
{ title: '入库日期', dataIndex: 'receiptDate', search: false },
|
||||||
|
{ title: '入库人', dataIndex: 'receivedBy', search: false },
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
search: false,
|
||||||
|
render: (_, r) => r.status === 1 ? <Tag color="success">已确认</Tag> : <Tag>草稿</Tag>,
|
||||||
|
},
|
||||||
|
{ title: '创建时间', dataIndex: 'createdAt', valueType: 'dateTime', search: false },
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
valueType: 'option',
|
||||||
|
render: (_, r) => [
|
||||||
|
r.status === 0 && (
|
||||||
|
<a key="confirm" onClick={() => Modal.confirm({ title: '确认入库单? 确认后将写入库存变动记录。', onOk: () => handleConfirm(r.receiptId) })}>确认</a>
|
||||||
|
),
|
||||||
|
].filter(Boolean),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ProTable<PurchaseReceipt>
|
||||||
|
actionRef={actionRef}
|
||||||
|
headerTitle="入库记录"
|
||||||
|
rowKey="receiptId"
|
||||||
|
columns={columns}
|
||||||
|
search={false}
|
||||||
|
request={async (params) => {
|
||||||
|
const res = await listPurchaseReceipts({ page: params.current, pageSize: params.pageSize });
|
||||||
|
return { data: res?.data?.list || [], total: res?.data?.total || 0, success: res?.code === 0 };
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReceiptsPage;
|
||||||
112
src/pages/Purchase/Stats/index.tsx
Normal file
112
src/pages/Purchase/Stats/index.tsx
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { ProTable } from '@ant-design/pro-components';
|
||||||
|
import { Card, Col, DatePicker, Row, Statistic, Typography } from 'antd';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
getPurchaseStatsByProduct,
|
||||||
|
getPurchaseStatsBySupplier,
|
||||||
|
getPurchaseStatsSummary,
|
||||||
|
} from '@/services/api';
|
||||||
|
import type { PurchaseStatsByProductItem, PurchaseStatsBySupplierItem, PurchaseStatsSummary } from '@/types/api';
|
||||||
|
|
||||||
|
const { RangePicker } = DatePicker;
|
||||||
|
|
||||||
|
const StatsPage: React.FC = () => {
|
||||||
|
const [summary, setSummary] = useState<PurchaseStatsSummary | null>(null);
|
||||||
|
const [bySupplier, setBySupplier] = useState<PurchaseStatsBySupplierItem[]>([]);
|
||||||
|
const [byProduct, setByProduct] = useState<PurchaseStatsByProductItem[]>([]);
|
||||||
|
const [dateRange, setDateRange] = useState<[string, string]>(['', '']);
|
||||||
|
|
||||||
|
const loadData = async (startDate = '', endDate = '') => {
|
||||||
|
const [sumRes, supRes, proRes] = await Promise.all([
|
||||||
|
getPurchaseStatsSummary({ startDate, endDate }),
|
||||||
|
getPurchaseStatsBySupplier({ startDate, endDate }),
|
||||||
|
getPurchaseStatsByProduct({ startDate, endDate }),
|
||||||
|
]);
|
||||||
|
if (sumRes?.code === 0) setSummary(sumRes.data);
|
||||||
|
if (supRes?.code === 0) setBySupplier(supRes.data?.list || []);
|
||||||
|
if (proRes?.code === 0) setByProduct(proRes.data?.list || []);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => { loadData(); }, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
|
||||||
|
<Col span={24}>
|
||||||
|
<Typography.Title level={4}>采购报表</Typography.Title>
|
||||||
|
<RangePicker
|
||||||
|
onChange={(_, dateStrings) => {
|
||||||
|
const [start, end] = dateStrings;
|
||||||
|
setDateRange([start, end]);
|
||||||
|
loadData(start, end);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row gutter={16} style={{ marginBottom: 24 }}>
|
||||||
|
<Col span={6}>
|
||||||
|
<Card>
|
||||||
|
<Statistic title="采购订单数" value={summary?.totalOrders ?? 0} />
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Card>
|
||||||
|
<Statistic title="采购总额" value={summary?.totalAmount ?? '0.00'} prefix="¥" />
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Card>
|
||||||
|
<Statistic title="已付款" value={summary?.totalPaidAmount ?? '0.00'} prefix="¥" />
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Card>
|
||||||
|
<Statistic title="未付款" value={summary?.unpaidAmount ?? '0.00'} prefix="¥" valueStyle={{ color: '#cf1322' }} />
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Card title="按供应商统计">
|
||||||
|
<ProTable<PurchaseStatsBySupplierItem>
|
||||||
|
headerTitle={false}
|
||||||
|
rowKey="supplierId"
|
||||||
|
search={false}
|
||||||
|
pagination={false}
|
||||||
|
dataSource={bySupplier}
|
||||||
|
columns={[
|
||||||
|
{ title: '供应商', dataIndex: 'supplierName' },
|
||||||
|
{ title: '订单数', dataIndex: 'orderCount' },
|
||||||
|
{ title: '采购总额', dataIndex: 'totalAmount', render: (v) => `¥ ${v}` },
|
||||||
|
{ title: '已付款', dataIndex: 'paidAmount', render: (v) => `¥ ${v}` },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Card title="按产品统计">
|
||||||
|
<ProTable<PurchaseStatsByProductItem>
|
||||||
|
headerTitle={false}
|
||||||
|
rowKey="productId"
|
||||||
|
search={false}
|
||||||
|
pagination={false}
|
||||||
|
dataSource={byProduct}
|
||||||
|
columns={[
|
||||||
|
{ title: '产品', dataIndex: 'productName' },
|
||||||
|
{ title: '规格', dataIndex: 'spec' },
|
||||||
|
{ title: '颜色', dataIndex: 'color' },
|
||||||
|
{ title: '采购数量', dataIndex: 'totalQty' },
|
||||||
|
{ title: '均价', dataIndex: 'avgUnitPrice', render: (v) => `¥ ${v}` },
|
||||||
|
{ title: '采购额', dataIndex: 'totalAmount', render: (v) => `¥ ${v}` },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StatsPage;
|
||||||
124
src/pages/Purchase/Suppliers/index.tsx
Normal file
124
src/pages/Purchase/Suppliers/index.tsx
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
|
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
|
||||||
|
import { Button, Form, Input, Modal, Space, Tag, message } from 'antd';
|
||||||
|
import { useRef, useState } from 'react';
|
||||||
|
import { createSupplier, deleteSupplier, listSuppliers, updateSupplier } from '@/services/api';
|
||||||
|
import type { Supplier } from '@/types/api';
|
||||||
|
|
||||||
|
const STATUS_MAP: Record<number, { text: string; color: string }> = {
|
||||||
|
0: { text: '停用', color: 'default' },
|
||||||
|
1: { text: '正常', color: 'success' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const SuppliersPage: React.FC = () => {
|
||||||
|
const actionRef = useRef<ActionType>();
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
const [editing, setEditing] = useState<Supplier | null>(null);
|
||||||
|
|
||||||
|
const openCreate = () => {
|
||||||
|
setEditing(null);
|
||||||
|
form.resetFields();
|
||||||
|
setModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openEdit = (record: Supplier) => {
|
||||||
|
setEditing(record);
|
||||||
|
form.setFieldsValue({ supplierName: record.supplierName, phone: record.phone, remark: record.remark });
|
||||||
|
setModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
if (editing) {
|
||||||
|
await updateSupplier(editing.supplierId, values);
|
||||||
|
message.success('更新成功');
|
||||||
|
} else {
|
||||||
|
await createSupplier(values);
|
||||||
|
message.success('创建成功');
|
||||||
|
}
|
||||||
|
setModalOpen(false);
|
||||||
|
actionRef.current?.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (id: string) => {
|
||||||
|
await deleteSupplier(id);
|
||||||
|
message.success('删除成功');
|
||||||
|
actionRef.current?.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleToggleStatus = async (record: Supplier) => {
|
||||||
|
await updateSupplier(record.supplierId, { status: record.status === 1 ? 0 : 1 });
|
||||||
|
message.success('状态已更新');
|
||||||
|
actionRef.current?.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns: ProColumns<Supplier>[] = [
|
||||||
|
{ title: '供应商名称', dataIndex: 'supplierName' },
|
||||||
|
{ title: '联系电话', dataIndex: 'phone', search: false },
|
||||||
|
{ title: '采购次数', dataIndex: 'purchaseCount', search: false },
|
||||||
|
{ title: '累计入库数量', dataIndex: 'deliveredQty', search: false },
|
||||||
|
{ title: '应结金额', dataIndex: 'payableAmount', search: false },
|
||||||
|
{ title: '实结金额', dataIndex: 'paidAmount', search: false },
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
search: false,
|
||||||
|
render: (_, r) => {
|
||||||
|
const s = STATUS_MAP[r.status] ?? STATUS_MAP[1];
|
||||||
|
return <Tag color={s.color}>{s.text}</Tag>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ title: '创建时间', dataIndex: 'createdAt', valueType: 'dateTime', search: false },
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
valueType: 'option',
|
||||||
|
render: (_, r) => [
|
||||||
|
<a key="edit" onClick={() => openEdit(r)}>编辑</a>,
|
||||||
|
<a key="toggle" onClick={() => handleToggleStatus(r)}>
|
||||||
|
{r.status === 1 ? '停用' : '启用'}
|
||||||
|
</a>,
|
||||||
|
<a key="del" style={{ color: '#ff4d4f' }} onClick={() => Modal.confirm({ title: '确认删除?', onOk: () => handleDelete(r.supplierId) })}>删除</a>,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ProTable<Supplier>
|
||||||
|
actionRef={actionRef}
|
||||||
|
headerTitle="供应商管理"
|
||||||
|
rowKey="supplierId"
|
||||||
|
columns={columns}
|
||||||
|
request={async (params) => {
|
||||||
|
const res = await listSuppliers({ page: params.current, pageSize: params.pageSize, supplierName: params.supplierName });
|
||||||
|
return { data: res?.data?.list || [], total: res?.data?.total || 0, success: res?.code === 0 };
|
||||||
|
}}
|
||||||
|
toolBarRender={() => [
|
||||||
|
<Button key="add" type="primary" icon={<PlusOutlined />} onClick={openCreate}>新增供应商</Button>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Modal
|
||||||
|
title={editing ? '编辑供应商' : '新增供应商'}
|
||||||
|
open={modalOpen}
|
||||||
|
onOk={handleSubmit}
|
||||||
|
onCancel={() => setModalOpen(false)}
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
<Form form={form} layout="vertical">
|
||||||
|
<Form.Item name="supplierName" label="供应商名称" rules={[{ required: true, message: '请输入供应商名称' }]}>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="phone" label="联系电话">
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="remark" label="备注">
|
||||||
|
<Input.TextArea rows={2} />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SuppliersPage;
|
||||||
@ -4,6 +4,7 @@ import type {
|
|||||||
BoltInfo,
|
BoltInfo,
|
||||||
ColorDetailItem,
|
ColorDetailItem,
|
||||||
CurrentUser,
|
CurrentUser,
|
||||||
|
InventoryLog,
|
||||||
LoginParams,
|
LoginParams,
|
||||||
LoginResult,
|
LoginResult,
|
||||||
Menu,
|
Menu,
|
||||||
@ -15,6 +16,12 @@ import type {
|
|||||||
Product,
|
Product,
|
||||||
ProductSummaryItem,
|
ProductSummaryItem,
|
||||||
ProductTree,
|
ProductTree,
|
||||||
|
PurchaseOrder,
|
||||||
|
PurchasePayment,
|
||||||
|
PurchaseReceipt,
|
||||||
|
PurchaseStatsByProductItem,
|
||||||
|
PurchaseStatsBySupplierItem,
|
||||||
|
PurchaseStatsSummary,
|
||||||
Relation,
|
Relation,
|
||||||
RelationHistory,
|
RelationHistory,
|
||||||
Role,
|
Role,
|
||||||
@ -22,6 +29,7 @@ import type {
|
|||||||
StockCheck,
|
StockCheck,
|
||||||
StockGroup,
|
StockGroup,
|
||||||
StockSummary,
|
StockSummary,
|
||||||
|
Supplier,
|
||||||
SystemConfig,
|
SystemConfig,
|
||||||
User,
|
User,
|
||||||
} from '@/types/api';
|
} from '@/types/api';
|
||||||
@ -234,3 +242,96 @@ export const updateRelation = (id: string, data: { status: number; validFrom?: s
|
|||||||
|
|
||||||
export const listRelationHistory = (params?: PaginationParams) =>
|
export const listRelationHistory = (params?: PaginationParams) =>
|
||||||
request<ApiResponse<PaginatedData<RelationHistory>>>('/api/v1/crm/relationships/history', { method: 'GET', params });
|
request<ApiResponse<PaginatedData<RelationHistory>>>('/api/v1/crm/relationships/history', { method: 'GET', params });
|
||||||
|
|
||||||
|
// ── Suppliers ─────────────────────────────────────
|
||||||
|
|
||||||
|
export const listSuppliers = (params: PaginationParams & { supplierName?: string; status?: number }) =>
|
||||||
|
request<ApiResponse<PaginatedData<Supplier>>>('/api/v1/purchase/suppliers', { method: 'GET', params });
|
||||||
|
|
||||||
|
export const getSupplier = (id: string) =>
|
||||||
|
request<ApiResponse<Supplier>>(`/api/v1/purchase/suppliers/${id}`, { method: 'GET' });
|
||||||
|
|
||||||
|
export const createSupplier = (data: { supplierName: string; phone?: string; remark?: string }) =>
|
||||||
|
request<ApiResponse<{ id: string }>>('/api/v1/purchase/suppliers', { method: 'POST', data });
|
||||||
|
|
||||||
|
export const updateSupplier = (id: string, data: { supplierName?: string; phone?: string; status?: number; remark?: string }) =>
|
||||||
|
request<ApiResponse>(`/api/v1/purchase/suppliers/${id}`, { method: 'PUT', data });
|
||||||
|
|
||||||
|
export const deleteSupplier = (id: string) =>
|
||||||
|
request<ApiResponse>(`/api/v1/purchase/suppliers/${id}`, { method: 'DELETE' });
|
||||||
|
|
||||||
|
// ── Purchase Orders ───────────────────────────────
|
||||||
|
|
||||||
|
export const listPurchaseOrders = (params: PaginationParams & { supplierId?: string; status?: number; startDate?: string; endDate?: string }) =>
|
||||||
|
request<ApiResponse<PaginatedData<PurchaseOrder>>>('/api/v1/purchase/orders', { method: 'GET', params });
|
||||||
|
|
||||||
|
export const getPurchaseOrder = (id: string) =>
|
||||||
|
request<ApiResponse<PurchaseOrder>>(`/api/v1/purchase/orders/${id}`, { method: 'GET' });
|
||||||
|
|
||||||
|
export const createPurchaseOrder = (data: {
|
||||||
|
supplierId: string;
|
||||||
|
orderDate: string;
|
||||||
|
purchaseBy?: string;
|
||||||
|
remark?: string;
|
||||||
|
details: Array<{ productId: string; productName: string; spec?: string; color?: string; quantity: string; unitPrice: string; remark?: string }>;
|
||||||
|
}) =>
|
||||||
|
request<ApiResponse<{ id: string }>>('/api/v1/purchase/orders', { method: 'POST', data });
|
||||||
|
|
||||||
|
export const updatePurchaseOrder = (id: string, data: object) =>
|
||||||
|
request<ApiResponse>(`/api/v1/purchase/orders/${id}`, { method: 'PUT', data });
|
||||||
|
|
||||||
|
export const confirmPurchaseOrder = (id: string) =>
|
||||||
|
request<ApiResponse>(`/api/v1/purchase/orders/${id}/confirm`, { method: 'POST' });
|
||||||
|
|
||||||
|
export const cancelPurchaseOrder = (id: string) =>
|
||||||
|
request<ApiResponse>(`/api/v1/purchase/orders/${id}/cancel`, { method: 'POST' });
|
||||||
|
|
||||||
|
// ── Purchase Receipts ─────────────────────────────
|
||||||
|
|
||||||
|
export const listPurchaseReceipts = (params: PaginationParams & { orderId?: string; status?: number; startDate?: string; endDate?: string }) =>
|
||||||
|
request<ApiResponse<PaginatedData<PurchaseReceipt>>>('/api/v1/purchase/receipts', { method: 'GET', params });
|
||||||
|
|
||||||
|
export const getPurchaseReceipt = (id: string) =>
|
||||||
|
request<ApiResponse<PurchaseReceipt>>(`/api/v1/purchase/receipts/${id}`, { method: 'GET' });
|
||||||
|
|
||||||
|
export const createPurchaseReceipt = (data: {
|
||||||
|
orderId: string;
|
||||||
|
receiptDate: string;
|
||||||
|
receivedBy?: string;
|
||||||
|
remark?: string;
|
||||||
|
details: Array<{ orderDetailId: string; productId: string; actualQty: string; unitCost?: string; remark?: string }>;
|
||||||
|
}) =>
|
||||||
|
request<ApiResponse<{ id: string }>>('/api/v1/purchase/receipts', { method: 'POST', data });
|
||||||
|
|
||||||
|
export const confirmPurchaseReceipt = (id: string) =>
|
||||||
|
request<ApiResponse>(`/api/v1/purchase/receipts/${id}/confirm`, { method: 'POST' });
|
||||||
|
|
||||||
|
// ── Purchase Payments ─────────────────────────────
|
||||||
|
|
||||||
|
export const listPurchasePayments = (params: PaginationParams & { orderId?: string; startDate?: string; endDate?: string }) =>
|
||||||
|
request<ApiResponse<PaginatedData<PurchasePayment>>>('/api/v1/purchase/payments', { method: 'GET', params });
|
||||||
|
|
||||||
|
export const createPurchasePayment = (data: {
|
||||||
|
orderId: string;
|
||||||
|
paymentDate: string;
|
||||||
|
amount: string;
|
||||||
|
paymentMethod?: string;
|
||||||
|
remark?: string;
|
||||||
|
}) =>
|
||||||
|
request<ApiResponse<{ id: string }>>('/api/v1/purchase/payments', { method: 'POST', data });
|
||||||
|
|
||||||
|
// ── Purchase Stats ────────────────────────────────
|
||||||
|
|
||||||
|
export const getPurchaseStatsSummary = (params: { startDate?: string; endDate?: string }) =>
|
||||||
|
request<ApiResponse<PurchaseStatsSummary>>('/api/v1/purchase/stats/summary', { method: 'GET', params });
|
||||||
|
|
||||||
|
export const getPurchaseStatsBySupplier = (params: { startDate?: string; endDate?: string }) =>
|
||||||
|
request<ApiResponse<{ list: PurchaseStatsBySupplierItem[] }>>('/api/v1/purchase/stats/by-supplier', { method: 'GET', params });
|
||||||
|
|
||||||
|
export const getPurchaseStatsByProduct = (params: { startDate?: string; endDate?: string }) =>
|
||||||
|
request<ApiResponse<{ list: PurchaseStatsByProductItem[] }>>('/api/v1/purchase/stats/by-product', { method: 'GET', params });
|
||||||
|
|
||||||
|
// ── Inventory Logs ────────────────────────────────
|
||||||
|
|
||||||
|
export const listInventoryLogs = (params: PaginationParams & { productId?: string; changeType?: number; startDate?: string; endDate?: string }) =>
|
||||||
|
request<ApiResponse<PaginatedData<InventoryLog>>>('/api/v1/inventory/logs', { method: 'GET', params });
|
||||||
|
|||||||
123
src/types/api.ts
123
src/types/api.ts
@ -246,3 +246,126 @@ export interface PaginationParams {
|
|||||||
page?: number;
|
page?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Purchase ──────────────────────────────────────
|
||||||
|
|
||||||
|
export interface Supplier {
|
||||||
|
supplierId: string;
|
||||||
|
supplierName: string;
|
||||||
|
phone: string;
|
||||||
|
purchaseCount: number;
|
||||||
|
deliveredQty: string;
|
||||||
|
payableAmount: string;
|
||||||
|
paidAmount: string;
|
||||||
|
status: number;
|
||||||
|
remark?: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PurchaseOrderDetail {
|
||||||
|
detailId: string;
|
||||||
|
productId: string;
|
||||||
|
productName: string;
|
||||||
|
spec: string;
|
||||||
|
color: string;
|
||||||
|
quantity: string;
|
||||||
|
unitPrice: string;
|
||||||
|
amount: string;
|
||||||
|
receivedQty: string;
|
||||||
|
remark?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PurchaseOrder {
|
||||||
|
orderId: string;
|
||||||
|
orderNo: string;
|
||||||
|
supplierId: string;
|
||||||
|
supplierName: string;
|
||||||
|
orderDate: string;
|
||||||
|
contractAmount: string;
|
||||||
|
receivedQty: string;
|
||||||
|
paidAmount: string;
|
||||||
|
paymentStatus: number;
|
||||||
|
receiptStatus: number;
|
||||||
|
purchaseBy: string;
|
||||||
|
creator: string;
|
||||||
|
operator: string;
|
||||||
|
status: number;
|
||||||
|
remark?: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
details: PurchaseOrderDetail[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PurchaseReceiptDetail {
|
||||||
|
detailId: string;
|
||||||
|
orderDetailId: string;
|
||||||
|
productId: string;
|
||||||
|
actualQty: string;
|
||||||
|
unitCost: string;
|
||||||
|
remark?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PurchaseReceipt {
|
||||||
|
receiptId: string;
|
||||||
|
receiptNo: string;
|
||||||
|
orderId: string;
|
||||||
|
receiptDate: string;
|
||||||
|
receivedBy: string;
|
||||||
|
status: number;
|
||||||
|
remark?: string;
|
||||||
|
createdAt: string;
|
||||||
|
details: PurchaseReceiptDetail[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PurchasePayment {
|
||||||
|
paymentId: string;
|
||||||
|
orderId: string;
|
||||||
|
supplierId: string;
|
||||||
|
paymentDate: string;
|
||||||
|
amount: string;
|
||||||
|
paymentMethod: string;
|
||||||
|
operator: string;
|
||||||
|
remark?: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InventoryLog {
|
||||||
|
logId: string;
|
||||||
|
productId: string;
|
||||||
|
changeQty: string;
|
||||||
|
balanceAfter: string;
|
||||||
|
changeType: number;
|
||||||
|
refType: string;
|
||||||
|
refId: string;
|
||||||
|
contactId: string;
|
||||||
|
logDate: string;
|
||||||
|
operator: string;
|
||||||
|
remark?: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PurchaseStatsSummary {
|
||||||
|
totalOrders: number;
|
||||||
|
totalAmount: string;
|
||||||
|
totalPaidAmount: string;
|
||||||
|
unpaidAmount: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PurchaseStatsBySupplierItem {
|
||||||
|
supplierId: string;
|
||||||
|
supplierName: string;
|
||||||
|
orderCount: number;
|
||||||
|
totalAmount: string;
|
||||||
|
paidAmount: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PurchaseStatsByProductItem {
|
||||||
|
productId: string;
|
||||||
|
productName: string;
|
||||||
|
spec: string;
|
||||||
|
color: string;
|
||||||
|
totalQty: string;
|
||||||
|
avgUnitPrice: string;
|
||||||
|
totalAmount: string;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user