diff --git a/.umirc.ts b/.umirc.ts index 9c1b0cd..ff2a59e 100644 --- a/.umirc.ts +++ b/.umirc.ts @@ -65,6 +65,19 @@ export default defineConfig({ { name: '库存盘点', path: '/inventory/checks', component: './Inventory/Checks' }, { name: '库存调整', path: '/inventory/adjusts', component: './Inventory/Adjusts' }, { 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' }, ], }, { diff --git a/src/pages/Login/index.tsx b/src/pages/Login/index.tsx index 55b777d..be8dcfe 100644 --- a/src/pages/Login/index.tsx +++ b/src/pages/Login/index.tsx @@ -30,7 +30,11 @@ const LoginPage: React.FC = () => { value: t.id, })), ); + } else { + message.error('获取公司列表失败,请稍后重试'); } + } catch { + message.error('获取公司列表失败,请检查网络连接'); } finally { setSearching(false); } diff --git a/src/pages/Purchase/Orders/detail.tsx b/src/pages/Purchase/Orders/detail.tsx new file mode 100644 index 0000000..a9b0c07 --- /dev/null +++ b/src/pages/Purchase/Orders/detail.tsx @@ -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 = { 0: '草稿', 1: '已确认', 2: '已取消' }; +const RECEIPT_STATUS_LABELS: Record = { 0: '未入库', 1: '部分入库', 2: '已入库' }; +const PAYMENT_STATUS_LABELS: Record = { 0: '未付款', 1: '部分付款', 2: '已付清' }; + +const OrderDetailPage: React.FC = () => { + const { id } = useParams<{ id: string }>(); + const [order, setOrder] = useState(null); + const [receipts, setReceipts] = useState([]); + const [payments, setPayments] = useState([]); + 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 ( + + + + 采购订单详情 — {order.orderNo} + + + + + {order.supplierName} + {order.orderDate} + {order.purchaseBy} + ¥ {order.contractAmount} + {order.receivedQty} + ¥ {order.paidAmount} + {RECEIPT_STATUS_LABELS[order.receiptStatus]} + {PAYMENT_STATUS_LABELS[order.paymentStatus]} + + + {STATUS_LABELS[order.status]} + + + {order.remark && {order.remark}} + + {order.status === 0 && ( + + )} + + + + + + + } onClick={() => { receiptForm.resetFields(); setReceiptModalOpen(true); }}>新建入库单 + )} + > + r.status === 1 ? 已确认 : 草稿 }, + { title: '创建时间', dataIndex: 'createdAt', valueType: 'dateTime' }, + ]} + /> + + + } onClick={() => { paymentForm.resetFields(); setPaymentModalOpen(true); }}>新增付款 + )} + > + `¥ ${v}` }, + { title: '付款方式', dataIndex: 'paymentMethod' }, + { title: '操作人', dataIndex: 'operator' }, + { title: '创建时间', dataIndex: 'createdAt', valueType: 'dateTime' }, + ]} + /> + + + setReceiptModalOpen(false)} width={600} destroyOnClose> +
+ + + + + + + + + + + + + {order.details.map((d) => ( + + + + ))} +
+
+ + setPaymentModalOpen(false)} destroyOnClose> +
+ + + + + + + + + + + + +
+
+
+ ); +}; + +export default OrderDetailPage; diff --git a/src/pages/Purchase/Orders/index.tsx b/src/pages/Purchase/Orders/index.tsx new file mode 100644 index 0000000..d6534df --- /dev/null +++ b/src/pages/Purchase/Orders/index.tsx @@ -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 = { + 0: { text: '草稿', color: 'default' }, + 1: { text: '已确认', color: 'processing' }, + 2: { text: '已取消', color: 'error' }, +}; + +const RECEIPT_STATUS_MAP: Record = { + 0: '未入库', + 1: '部分入库', + 2: '已入库', +}; + +const PAYMENT_STATUS_MAP: Record = { + 0: '未付款', + 1: '部分付款', + 2: '已付清', +}; + +const OrdersPage: React.FC = () => { + const actionRef = useRef(); + + 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[] = [ + { 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 {s.text}; + }, + }, + { title: '创建时间', dataIndex: 'createdAt', valueType: 'dateTime', search: false }, + { + title: '操作', + valueType: 'option', + render: (_, r) => [ + history.push(`/purchase/orders/${r.orderId}`)}>详情, + r.status === 0 && ( + Modal.confirm({ title: '确认采购订单?', onOk: () => handleConfirm(r.orderId) })}>确认 + ), + r.status === 1 && r.receiptStatus === 0 && ( + Modal.confirm({ title: '确认取消?', onOk: () => handleCancel(r.orderId) })}>取消 + ), + ].filter(Boolean), + }, + ]; + + return ( + + 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={() => [ + , + ]} + /> + ); +}; + +export default OrdersPage; diff --git a/src/pages/Purchase/Receipts/index.tsx b/src/pages/Purchase/Receipts/index.tsx new file mode 100644 index 0000000..f789698 --- /dev/null +++ b/src/pages/Purchase/Receipts/index.tsx @@ -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(); + + const handleConfirm = async (id: string) => { + await confirmPurchaseReceipt(id); + message.success('入库单已确认'); + actionRef.current?.reload(); + }; + + const columns: ProColumns[] = [ + { 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 ? 已确认 : 草稿, + }, + { title: '创建时间', dataIndex: 'createdAt', valueType: 'dateTime', search: false }, + { + title: '操作', + valueType: 'option', + render: (_, r) => [ + r.status === 0 && ( + Modal.confirm({ title: '确认入库单? 确认后将写入库存变动记录。', onOk: () => handleConfirm(r.receiptId) })}>确认 + ), + ].filter(Boolean), + }, + ]; + + return ( + + 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; diff --git a/src/pages/Purchase/Stats/index.tsx b/src/pages/Purchase/Stats/index.tsx new file mode 100644 index 0000000..00dfde8 --- /dev/null +++ b/src/pages/Purchase/Stats/index.tsx @@ -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(null); + const [bySupplier, setBySupplier] = useState([]); + const [byProduct, setByProduct] = useState([]); + 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 ( +
+ + + 采购报表 + { + const [start, end] = dateStrings; + setDateRange([start, end]); + loadData(start, end); + }} + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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}` }, + ]} + /> + + + + + + 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}` }, + ]} + /> + + + +
+ ); +}; + +export default StatsPage; diff --git a/src/pages/Purchase/Suppliers/index.tsx b/src/pages/Purchase/Suppliers/index.tsx new file mode 100644 index 0000000..26aea6c --- /dev/null +++ b/src/pages/Purchase/Suppliers/index.tsx @@ -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 = { + 0: { text: '停用', color: 'default' }, + 1: { text: '正常', color: 'success' }, +}; + +const SuppliersPage: React.FC = () => { + const actionRef = useRef(); + const [form] = Form.useForm(); + const [modalOpen, setModalOpen] = useState(false); + const [editing, setEditing] = useState(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[] = [ + { 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 {s.text}; + }, + }, + { title: '创建时间', dataIndex: 'createdAt', valueType: 'dateTime', search: false }, + { + title: '操作', + valueType: 'option', + render: (_, r) => [ + openEdit(r)}>编辑, + handleToggleStatus(r)}> + {r.status === 1 ? '停用' : '启用'} + , + Modal.confirm({ title: '确认删除?', onOk: () => handleDelete(r.supplierId) })}>删除, + ], + }, + ]; + + return ( + <> + + 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={() => [ + , + ]} + /> + setModalOpen(false)} + destroyOnClose + > +
+ + + + + + + + + +
+
+ + ); +}; + +export default SuppliersPage; diff --git a/src/services/api.ts b/src/services/api.ts index f1daa98..fd21f1a 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -4,6 +4,7 @@ import type { BoltInfo, ColorDetailItem, CurrentUser, + InventoryLog, LoginParams, LoginResult, Menu, @@ -15,6 +16,12 @@ import type { Product, ProductSummaryItem, ProductTree, + PurchaseOrder, + PurchasePayment, + PurchaseReceipt, + PurchaseStatsByProductItem, + PurchaseStatsBySupplierItem, + PurchaseStatsSummary, Relation, RelationHistory, Role, @@ -22,6 +29,7 @@ import type { StockCheck, StockGroup, StockSummary, + Supplier, SystemConfig, User, } from '@/types/api'; @@ -234,3 +242,96 @@ export const updateRelation = (id: string, data: { status: number; validFrom?: s export const listRelationHistory = (params?: PaginationParams) => request>>('/api/v1/crm/relationships/history', { method: 'GET', params }); + +// ── Suppliers ───────────────────────────────────── + +export const listSuppliers = (params: PaginationParams & { supplierName?: string; status?: number }) => + request>>('/api/v1/purchase/suppliers', { method: 'GET', params }); + +export const getSupplier = (id: string) => + request>(`/api/v1/purchase/suppliers/${id}`, { method: 'GET' }); + +export const createSupplier = (data: { supplierName: string; phone?: string; remark?: string }) => + request>('/api/v1/purchase/suppliers', { method: 'POST', data }); + +export const updateSupplier = (id: string, data: { supplierName?: string; phone?: string; status?: number; remark?: string }) => + request(`/api/v1/purchase/suppliers/${id}`, { method: 'PUT', data }); + +export const deleteSupplier = (id: string) => + request(`/api/v1/purchase/suppliers/${id}`, { method: 'DELETE' }); + +// ── Purchase Orders ─────────────────────────────── + +export const listPurchaseOrders = (params: PaginationParams & { supplierId?: string; status?: number; startDate?: string; endDate?: string }) => + request>>('/api/v1/purchase/orders', { method: 'GET', params }); + +export const getPurchaseOrder = (id: string) => + request>(`/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>('/api/v1/purchase/orders', { method: 'POST', data }); + +export const updatePurchaseOrder = (id: string, data: object) => + request(`/api/v1/purchase/orders/${id}`, { method: 'PUT', data }); + +export const confirmPurchaseOrder = (id: string) => + request(`/api/v1/purchase/orders/${id}/confirm`, { method: 'POST' }); + +export const cancelPurchaseOrder = (id: string) => + request(`/api/v1/purchase/orders/${id}/cancel`, { method: 'POST' }); + +// ── Purchase Receipts ───────────────────────────── + +export const listPurchaseReceipts = (params: PaginationParams & { orderId?: string; status?: number; startDate?: string; endDate?: string }) => + request>>('/api/v1/purchase/receipts', { method: 'GET', params }); + +export const getPurchaseReceipt = (id: string) => + request>(`/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>('/api/v1/purchase/receipts', { method: 'POST', data }); + +export const confirmPurchaseReceipt = (id: string) => + request(`/api/v1/purchase/receipts/${id}/confirm`, { method: 'POST' }); + +// ── Purchase Payments ───────────────────────────── + +export const listPurchasePayments = (params: PaginationParams & { orderId?: string; startDate?: string; endDate?: string }) => + request>>('/api/v1/purchase/payments', { method: 'GET', params }); + +export const createPurchasePayment = (data: { + orderId: string; + paymentDate: string; + amount: string; + paymentMethod?: string; + remark?: string; +}) => + request>('/api/v1/purchase/payments', { method: 'POST', data }); + +// ── Purchase Stats ──────────────────────────────── + +export const getPurchaseStatsSummary = (params: { startDate?: string; endDate?: string }) => + request>('/api/v1/purchase/stats/summary', { method: 'GET', params }); + +export const getPurchaseStatsBySupplier = (params: { startDate?: string; endDate?: string }) => + request>('/api/v1/purchase/stats/by-supplier', { method: 'GET', params }); + +export const getPurchaseStatsByProduct = (params: { startDate?: string; endDate?: string }) => + request>('/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>>('/api/v1/inventory/logs', { method: 'GET', params }); diff --git a/src/types/api.ts b/src/types/api.ts index cf52821..29a38a3 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -246,3 +246,126 @@ export interface PaginationParams { page?: 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; +}