采购单优化
This commit is contained in:
+491
-20
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Descriptions,
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
message,
|
||||
Modal,
|
||||
Popconfirm,
|
||||
Radio,
|
||||
Select,
|
||||
Space,
|
||||
Spin,
|
||||
@@ -19,7 +20,7 @@ import {
|
||||
Tag,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import {AccountBookOutlined, DownloadOutlined, EditOutlined, UnorderedListOutlined} from '@ant-design/icons';
|
||||
import {AccountBookOutlined, DeleteOutlined, DownloadOutlined, EditOutlined, PlusOutlined, UnorderedListOutlined} from '@ant-design/icons';
|
||||
import type { TableProps } from 'antd';
|
||||
import XinTable from '@/components/XinTable';
|
||||
import type {
|
||||
@@ -37,25 +38,35 @@ import type {
|
||||
IPurchaseDetailRow,
|
||||
IPurchaseStoreItem,
|
||||
IPurchaseStoreSummary,
|
||||
IPurchaseSupplierItem,
|
||||
PurchaseStoreItemAddParams,
|
||||
PurchaseStoreItemUpdateParams,
|
||||
} from '@/domain/iPurchaseOrder.ts';
|
||||
import { PURCHASE_STATUS_MAP, BILL_STATUS_MAP } from '@/domain/iPurchaseOrder.ts';
|
||||
import { STORE_ORDER_STATUS_MAP } from '@/domain/iStoreOrder.ts';
|
||||
import {
|
||||
addPurchaseStoreItem,
|
||||
exportPurchase,
|
||||
exportPurchaseStores,
|
||||
exportPurchaseSuppliers,
|
||||
generateBill,
|
||||
getBillPrepare,
|
||||
getPurchaseCell,
|
||||
getPurchaseDetail,
|
||||
getPurchaseStoreSummary,
|
||||
removePurchaseStoreItem,
|
||||
type BillGenerateStoreParams,
|
||||
type PurchaseCellUpdateParams,
|
||||
type PurchaseRowUpdateParams,
|
||||
updatePurchaseCellItem,
|
||||
updatePurchaseRow,
|
||||
updatePurchaseStoreItem,
|
||||
} from '@/api/purchase/order.ts';
|
||||
import { Update } from '@/api/common/table.ts';
|
||||
import { getSupplierOptions } from '@/api/customer/supplier.ts';
|
||||
import { getProductOptions } from '@/api/product/goods.ts';
|
||||
import type ISupplier from '@/domain/iSupplier.ts';
|
||||
import type IProduct from '@/domain/iProduct.ts';
|
||||
import AuthButton from '@/components/AuthButton';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
@@ -103,6 +114,56 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
const [storeSummary, setStoreSummary] = useState<IPurchaseStoreSummary | null>(null);
|
||||
const [storeLoading, setStoreLoading] = useState(false);
|
||||
|
||||
// 门店购买详情:新增单品弹窗
|
||||
const [addItemOpen, setAddItemOpen] = useState(false);
|
||||
const [addItemSaving, setAddItemSaving] = useState(false);
|
||||
const [addItemForm] = Form.useForm<PurchaseStoreItemAddParams>();
|
||||
const [productOptions, setProductOptions] = useState<IProduct[]>([]);
|
||||
|
||||
// 门店购买详情:单品编辑弹窗(数量/称重/单价)
|
||||
const [storeItemTarget, setStoreItemTarget] = useState<IPurchaseStoreItem | null>(null);
|
||||
const [storeItemSaving, setStoreItemSaving] = useState(false);
|
||||
const [storeItemForm] = Form.useForm<PurchaseStoreItemUpdateParams>();
|
||||
|
||||
// 供应商采购明细页签
|
||||
const [supplierId, setSupplierId] = useState<number>(0);
|
||||
|
||||
// 导出弹窗:商品明细(供应商筛选)/ 门店购买详情 / 供应商采购明细
|
||||
const [itemExportOpen, setItemExportOpen] = useState(false);
|
||||
const [itemExportSupplier, setItemExportSupplier] = useState<number>(0);
|
||||
const [storeExportOpen, setStoreExportOpen] = useState(false);
|
||||
const [storeExportScope, setStoreExportScope] = useState<'current' | 'all'>('current');
|
||||
const [supplierExportOpen, setSupplierExportOpen] = useState(false);
|
||||
const [supplierExportScope, setSupplierExportScope] = useState<'current' | 'all'>('current');
|
||||
|
||||
/** 采购单内出现的供应商(矩阵/供应商页签筛选与导出选项共用) */
|
||||
const purchaseSuppliers = useMemo(() => {
|
||||
const map = new Map<number, string>();
|
||||
(detail?.items ?? []).forEach((row) => {
|
||||
if (row.supplier_id > 0 && row.supplier?.name) {
|
||||
map.set(row.supplier_id, row.supplier.name);
|
||||
}
|
||||
});
|
||||
return Array.from(map, ([id, name]) => ({ id, name }));
|
||||
}, [detail?.items]);
|
||||
|
||||
/** 供应商采购明细行:按供应商过滤矩阵行,金额=数量×成本价(成本口径) */
|
||||
const supplierItems = useMemo<IPurchaseSupplierItem[]>(() => {
|
||||
if (!detail || supplierId <= 0) return [];
|
||||
return detail.items
|
||||
.filter((row) => row.supplier_id === supplierId)
|
||||
.map((row) => ({
|
||||
product_id: row.product_id,
|
||||
product_name: row.product_name,
|
||||
product_spec: row.product_spec,
|
||||
unit: row.unit,
|
||||
cost_price: String(row.cost_price),
|
||||
quantity: row.quantity,
|
||||
weight: String(row.weight),
|
||||
amount: (row.quantity * Number(row.cost_price)).toFixed(2),
|
||||
}));
|
||||
}, [detail, supplierId]);
|
||||
|
||||
// 生成账单(已完成采购单按门店生成:填写配送费/周转筐/托盘数量,金额只读)
|
||||
const [billOpen, setBillOpen] = useState(false);
|
||||
const [billPrepare, setBillPrepare] = useState<IBillPrepare | null>(null);
|
||||
@@ -144,6 +205,15 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
}
|
||||
}, [detail]);
|
||||
|
||||
// 详情加载后默认选中第一个供应商(当前选中供应商仍在采购单内则保留)
|
||||
useEffect(() => {
|
||||
if (purchaseSuppliers.length > 0) {
|
||||
setSupplierId((prev) => (purchaseSuppliers.some((s) => s.id === prev) ? prev : purchaseSuppliers[0].id));
|
||||
} else {
|
||||
setSupplierId(0);
|
||||
}
|
||||
}, [purchaseSuppliers]);
|
||||
|
||||
// 切到「门店购买详情」页签或切换门店时加载汇总
|
||||
useEffect(() => {
|
||||
if (detailOpen && detailTab === 'stores' && detail && storeId > 0) {
|
||||
@@ -182,6 +252,76 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 门店单品增删改后:刷新门店汇总、采购单详情与列表 */
|
||||
const refreshAfterStoreItemChange = async () => {
|
||||
await loadStoreSummary();
|
||||
if (detail) {
|
||||
await loadDetail(detail.purchase.id!);
|
||||
}
|
||||
await tableRef.current?.reload();
|
||||
};
|
||||
|
||||
/** 打开新增单品弹窗(懒加载商品选项) */
|
||||
const openAddItem = () => {
|
||||
addItemForm.resetFields();
|
||||
setAddItemOpen(true);
|
||||
if (productOptions.length === 0) {
|
||||
getProductOptions().then((res) => setProductOptions(res.data.data ?? []));
|
||||
}
|
||||
};
|
||||
|
||||
/** 提交新增单品 */
|
||||
const handleAddItemSave = async (values: PurchaseStoreItemAddParams) => {
|
||||
if (!detail || storeId <= 0) {
|
||||
return;
|
||||
}
|
||||
setAddItemSaving(true);
|
||||
try {
|
||||
await addPurchaseStoreItem(detail.purchase.id!, storeId, values);
|
||||
message.success('已添加单品,订货单与采购单汇总已重算');
|
||||
setAddItemOpen(false);
|
||||
await refreshAfterStoreItemChange();
|
||||
} finally {
|
||||
setAddItemSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
/** 打开单品编辑弹窗(回显加权平均单价/数量/称重) */
|
||||
const openStoreItemEdit = (row: IPurchaseStoreItem) => {
|
||||
setStoreItemTarget(row);
|
||||
storeItemForm.setFieldsValue({
|
||||
quantity: row.quantity,
|
||||
price: Number(row.price ?? 0),
|
||||
weight: Number(row.weight ?? 0),
|
||||
});
|
||||
};
|
||||
|
||||
/** 提交单品修改:同商品多笔订单明细合并到最早一条,级联重算 */
|
||||
const handleStoreItemSave = async (values: PurchaseStoreItemUpdateParams) => {
|
||||
if (!detail || !storeItemTarget || storeId <= 0) {
|
||||
return;
|
||||
}
|
||||
setStoreItemSaving(true);
|
||||
try {
|
||||
await updatePurchaseStoreItem(detail.purchase.id!, storeId, storeItemTarget.product_id, values);
|
||||
message.success('单品已更新,订货单与采购单汇总已重算');
|
||||
setStoreItemTarget(null);
|
||||
await refreshAfterStoreItemChange();
|
||||
} finally {
|
||||
setStoreItemSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
/** 移除单品 */
|
||||
const handleStoreItemRemove = async (row: IPurchaseStoreItem) => {
|
||||
if (!detail || storeId <= 0) {
|
||||
return;
|
||||
}
|
||||
await removePurchaseStoreItem(detail.purchase.id!, storeId, row.product_id);
|
||||
message.success('已移除单品,订货单与采购单汇总已重算');
|
||||
await refreshAfterStoreItemChange();
|
||||
};
|
||||
|
||||
/** 打开单元格下钻:门店 + 商品 → 该采购单下全部订货明细 */
|
||||
const openCell = (row: IPurchaseDetailRow, store: { id: number; name: string }) => {
|
||||
setCellQuery({ productId: row.product_id, storeId: store.id });
|
||||
@@ -323,7 +463,7 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 明细矩阵列:品名/供应商/单价/包规/单位/成本 + 每门店一列(数量)+ 合计 + 操作 */
|
||||
/** 明细矩阵列:品名/供应商/参考零售价/包规/单位/成本 + 每门店一列(数量)+ 合计 + 操作 */
|
||||
const buildItemColumns = (): TableProps<IPurchaseDetailRow>['columns'] => {
|
||||
const fixed: NonNullable<TableProps<IPurchaseDetailRow>['columns']> = [
|
||||
{ title: '品名', dataIndex: 'product_name', width: 120, align: 'center' },
|
||||
@@ -335,11 +475,17 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
render: (_, row) => row.supplier?.name ?? '-',
|
||||
},
|
||||
{
|
||||
title: '参考成本单价',
|
||||
key: 'unit_cost',
|
||||
title: '参考零售价',
|
||||
key: 'retail_price',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
render: (_, row) => `¥${calcUnitRefPrice(Number(row.cost_price), row.product_spec ?? '').toFixed(2)}`,
|
||||
render: (_, row) => {
|
||||
// 加权平均售价 ÷ 包规数值(无订货数量时无参考价)
|
||||
const quantity = Number(row.quantity ?? 0);
|
||||
if (quantity <= 0) return <Text type="secondary">-</Text>;
|
||||
const weightedPrice = Number(row.amount ?? 0) / quantity;
|
||||
return `¥${calcUnitRefPrice(weightedPrice, 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 || '-' },
|
||||
@@ -430,7 +576,7 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
/** 门店购买详情列:商品/购买参考零售价(单价÷包规)/包规/单位/单价/数量/预计金额 */
|
||||
/** 门店购买详情列:商品/购买参考零售价(单价÷包规)/包规/单位/单价/数量/预计金额 + 操作 */
|
||||
const storeColumns: TableProps<IPurchaseStoreItem>['columns'] = [
|
||||
{ title: '商品', dataIndex: 'product_name', width: 160, align: 'center' },
|
||||
{
|
||||
@@ -464,6 +610,40 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
align: 'center',
|
||||
render: (v) => <Text strong>¥{Number(v).toFixed(2)}</Text>,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 130,
|
||||
align: 'center',
|
||||
render: (_, row) =>
|
||||
detail?.purchase.status === 0 ? (
|
||||
<Space size={0}>
|
||||
<AuthButton auth="purchase.order.update">
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => openStoreItemEdit(row)}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
</AuthButton>
|
||||
<AuthButton auth="purchase.order.update">
|
||||
<Popconfirm
|
||||
title="移除该单品?"
|
||||
description={`将从「${storeSummary?.store?.name ?? '该门店'}」采购明细中移除「${row.product_name}」`}
|
||||
onConfirm={() => handleStoreItemRemove(row)}
|
||||
>
|
||||
<Button type="link" size="small" danger icon={<DeleteOutlined />}>
|
||||
移除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</AuthButton>
|
||||
</Space>
|
||||
) : (
|
||||
<Text type="secondary">-</Text>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
/** 门店购买详情合计行:总数量 + 总预计金额 */
|
||||
@@ -486,6 +666,61 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
<Table.Summary.Cell index={7} align="center">
|
||||
<Text strong type="danger">¥{totalAmount.toFixed(2)}</Text>
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell index={8} align="center">
|
||||
<Text strong>-</Text>
|
||||
</Table.Summary.Cell>
|
||||
</Table.Summary.Row>
|
||||
);
|
||||
};
|
||||
|
||||
/** 供应商采购明细列:品名/包规/单位/成本价/数量/重量/金额(成本口径) */
|
||||
const supplierColumns: TableProps<IPurchaseSupplierItem>['columns'] = [
|
||||
{ title: '品名', dataIndex: 'product_name', width: 180, align: 'center' },
|
||||
{ title: '包规', dataIndex: 'product_spec', width: 110, align: 'center', render: (v) => v || '-' },
|
||||
{ title: '单位', dataIndex: 'unit', width: 90, align: 'center', render: (v) => v || '-' },
|
||||
{
|
||||
title: '成本价',
|
||||
dataIndex: 'cost_price',
|
||||
width: 110,
|
||||
align: 'center',
|
||||
render: (v) => `¥${Number(v).toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
title: '数量',
|
||||
dataIndex: 'quantity',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
render: (v) => <Text strong>{v}</Text>,
|
||||
},
|
||||
{ title: '重量', dataIndex: 'weight', width: 110, align: 'center', render: (v) => `${Number(v).toFixed(3)}斤` },
|
||||
{
|
||||
title: '金额',
|
||||
dataIndex: 'amount',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
render: (v) => <Text strong>¥{Number(v).toFixed(2)}</Text>,
|
||||
},
|
||||
];
|
||||
|
||||
/** 供应商采购明细合计行 */
|
||||
const renderSupplierTotal = () => {
|
||||
const totalQuantity = supplierItems.reduce((sum, row) => sum + Number(row.quantity), 0);
|
||||
const totalWeight = supplierItems.reduce((sum, row) => sum + Number(row.weight), 0);
|
||||
const totalAmount = supplierItems.reduce((sum, row) => sum + Number(row.amount), 0);
|
||||
return (
|
||||
<Table.Summary.Row>
|
||||
<Table.Summary.Cell index={0} colSpan={4} align="center">
|
||||
<Text strong>合计</Text>
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell index={4} align="center">
|
||||
<Text strong>{totalQuantity}</Text>
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell index={5} align="center">
|
||||
<Text strong>{totalWeight.toFixed(3)}斤</Text>
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell index={6} align="center">
|
||||
<Text strong type="danger">¥{totalAmount.toFixed(2)}</Text>
|
||||
</Table.Summary.Cell>
|
||||
</Table.Summary.Row>
|
||||
);
|
||||
};
|
||||
@@ -770,23 +1005,48 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
items={[
|
||||
{ key: 'items', label: '商品明细' },
|
||||
{ key: 'stores', label: '门店购买详情' },
|
||||
{ key: 'suppliers', label: '供应商采购明细' },
|
||||
{ key: 'bills', 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 className="mb-3 flex items-center justify-between gap-2">
|
||||
<div className="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>
|
||||
<Space>
|
||||
{detail.purchase.status === 0 && (
|
||||
<AuthButton auth="purchase.order.update">
|
||||
<Button icon={<PlusOutlined />} onClick={openAddItem}>
|
||||
新增单品
|
||||
</Button>
|
||||
</AuthButton>
|
||||
)}
|
||||
<AuthButton auth="purchase.order.export">
|
||||
<Button
|
||||
type="primary"
|
||||
ghost
|
||||
icon={<DownloadOutlined />}
|
||||
onClick={() => {
|
||||
setStoreExportScope('current');
|
||||
setStoreExportOpen(true);
|
||||
}}
|
||||
>
|
||||
导出
|
||||
</Button>
|
||||
</AuthButton>
|
||||
</Space>
|
||||
</div>
|
||||
<Spin spinning={storeLoading}>
|
||||
{storeSummary && storeSummary.items.length > 0 ? (
|
||||
@@ -810,6 +1070,53 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
)}
|
||||
</Spin>
|
||||
</>
|
||||
) : detailTab === 'suppliers' ? (
|
||||
<>
|
||||
<div className="mb-3 flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Text>选择供应商:</Text>
|
||||
<Select
|
||||
value={supplierId || undefined}
|
||||
onChange={(value) => setSupplierId(value)}
|
||||
placeholder="选择供应商"
|
||||
className="w-60!"
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
options={purchaseSuppliers.map((s) => ({ value: s.id, label: s.name }))}
|
||||
/>
|
||||
</div>
|
||||
<AuthButton auth="purchase.order.export">
|
||||
<Button
|
||||
type="primary"
|
||||
ghost
|
||||
icon={<DownloadOutlined />}
|
||||
onClick={() => {
|
||||
setSupplierExportScope('current');
|
||||
setSupplierExportOpen(true);
|
||||
}}
|
||||
>
|
||||
导出
|
||||
</Button>
|
||||
</AuthButton>
|
||||
</div>
|
||||
{supplierItems.length > 0 ? (
|
||||
<Table<IPurchaseSupplierItem>
|
||||
rowKey="product_id"
|
||||
size="small"
|
||||
bordered
|
||||
columns={supplierColumns}
|
||||
dataSource={supplierItems}
|
||||
pagination={false}
|
||||
summary={renderSupplierTotal}
|
||||
/>
|
||||
) : (
|
||||
<Empty
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
description="该供应商在此采购单中无采购明细"
|
||||
className="py-8!"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : detailTab === 'bills' ? (
|
||||
detail.bills.length > 0 ? (
|
||||
<Table<IBill>
|
||||
@@ -836,7 +1143,10 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
type="primary"
|
||||
ghost
|
||||
icon={<DownloadOutlined />}
|
||||
onClick={() => detail && exportPurchase(detail.purchase.id!)}
|
||||
onClick={() => {
|
||||
setItemExportSupplier(0);
|
||||
setItemExportOpen(true);
|
||||
}}
|
||||
>
|
||||
导出
|
||||
</Button>
|
||||
@@ -874,7 +1184,7 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
]}
|
||||
>
|
||||
<div className="py-2 text-gray-500">
|
||||
「保存」仅修改本采购单中该商品的所有订单项
|
||||
保存将同步修改本采购单中该商品的所有订单项,并同步保存到商品档案(商品列表)。
|
||||
</div>
|
||||
<Form form={editForm} layout="vertical" onFinish={handleEditSave}>
|
||||
<Form.Item
|
||||
@@ -1187,6 +1497,167 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
)}
|
||||
</Spin>
|
||||
</Modal>
|
||||
{/* 新增单品:选择商品 + 采购数量 + 称重 */}
|
||||
<Modal
|
||||
title={`新增单品 · ${storeSummary?.store?.name ?? ''}`}
|
||||
open={addItemOpen}
|
||||
onCancel={() => setAddItemOpen(false)}
|
||||
onOk={() => addItemForm.submit()}
|
||||
confirmLoading={addItemSaving}
|
||||
okText="添加"
|
||||
destroyOnHidden
|
||||
>
|
||||
<div className="py-2 text-gray-500">
|
||||
单品将挂靠到该门店在此采购单中的最新一笔订单;单价按门店等级上浮比例自动换算。
|
||||
</div>
|
||||
<Form form={addItemForm} layout="vertical" onFinish={handleAddItemSave}>
|
||||
<Form.Item
|
||||
label="商品"
|
||||
name="product_id"
|
||||
rules={[{ required: true, message: '请选择商品' }]}
|
||||
>
|
||||
<Select
|
||||
showSearch={{ optionFilterProp: 'label' }}
|
||||
placeholder="搜索并选择商品"
|
||||
options={productOptions.map((p) => ({
|
||||
value: p.id!,
|
||||
label: `${p.name}${p.spec ? `(${p.spec})` : ''}`,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="采购数量"
|
||||
name="quantity"
|
||||
rules={[{ required: true, message: '请输入采购数量' }]}
|
||||
>
|
||||
<InputNumber className="w-full" min={1} precision={0} placeholder="请输入采购数量(包数)" />
|
||||
</Form.Item>
|
||||
<Form.Item label="称重(斤,可选)" name="weight">
|
||||
<InputNumber className="w-full" min={0} precision={3} placeholder="请输入称重" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{/* 单品编辑:数量/称重/单价(同商品多笔订单明细合并到最早一条) */}
|
||||
<Modal
|
||||
title={storeItemTarget ? `编辑「${storeItemTarget.product_name}」` : '编辑单品'}
|
||||
open={storeItemTarget !== null}
|
||||
onCancel={() => setStoreItemTarget(null)}
|
||||
onOk={() => storeItemForm.submit()}
|
||||
confirmLoading={storeItemSaving}
|
||||
okText="保存"
|
||||
destroyOnHidden
|
||||
>
|
||||
<div className="py-2 text-gray-500">
|
||||
修改保存后,系统将自动重算明细金额、订货单与采购单汇总。
|
||||
</div>
|
||||
<Form form={storeItemForm} layout="vertical" onFinish={handleStoreItemSave}>
|
||||
<Form.Item
|
||||
label="单价(元)"
|
||||
name="price"
|
||||
rules={[{ required: true, message: '请输入单价' }]}
|
||||
>
|
||||
<InputNumber className="w-full" min={0} precision={2} placeholder="请输入单价" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="采购数量"
|
||||
name="quantity"
|
||||
rules={[{ required: true, message: '请输入采购数量' }]}
|
||||
>
|
||||
<InputNumber className="w-full" min={0} precision={0} placeholder="请输入采购数量" />
|
||||
</Form.Item>
|
||||
<Form.Item label="称重(斤)" name="weight" rules={[{ required: true, message: '请输入称重' }]}>
|
||||
<InputNumber className="w-full" min={0} precision={3} placeholder="请输入称重" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{/* 商品明细导出:供应商筛选(全部/单个) */}
|
||||
<Modal
|
||||
title="导出商品明细"
|
||||
open={itemExportOpen}
|
||||
onCancel={() => setItemExportOpen(false)}
|
||||
onOk={() => {
|
||||
if (detail) {
|
||||
void exportPurchase(detail.purchase.id!, itemExportSupplier > 0 ? itemExportSupplier : undefined);
|
||||
}
|
||||
setItemExportOpen(false);
|
||||
}}
|
||||
okText="导出"
|
||||
destroyOnHidden
|
||||
>
|
||||
<div className="py-2 text-gray-500">
|
||||
导出系统全部商品行(无订货的商品数量为 0),门店列附数量合计与突出颜色。
|
||||
</div>
|
||||
<div className="flex items-center gap-2 py-2">
|
||||
<Text>供应商:</Text>
|
||||
<Select
|
||||
value={itemExportSupplier}
|
||||
onChange={setItemExportSupplier}
|
||||
className="flex-1"
|
||||
options={[
|
||||
{ value: 0, label: '全部供应商' },
|
||||
...purchaseSuppliers.map((s) => ({ value: s.id, label: s.name })),
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
{/* 门店购买详情导出:当前门店 / 全部门店(多工作表) */}
|
||||
<Modal
|
||||
title="导出门店购买详情"
|
||||
open={storeExportOpen}
|
||||
onCancel={() => setStoreExportOpen(false)}
|
||||
onOk={() => {
|
||||
if (detail) {
|
||||
void exportPurchaseStores(
|
||||
detail.purchase.id!,
|
||||
storeExportScope === 'current' && storeId > 0 ? storeId : undefined,
|
||||
);
|
||||
}
|
||||
setStoreExportOpen(false);
|
||||
}}
|
||||
okText="导出"
|
||||
destroyOnHidden
|
||||
>
|
||||
<Radio.Group
|
||||
className="py-2"
|
||||
value={storeExportScope}
|
||||
onChange={(e) => setStoreExportScope(e.target.value)}
|
||||
options={[
|
||||
{ value: 'current', label: `当前门店(${detail?.stores.find((s) => s.id === storeId)?.name ?? '-'})` },
|
||||
{ value: 'all', label: '全部门店(合并为一个 XLSX,每门店一个工作表)' },
|
||||
]}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
{/* 供应商采购明细导出:当前供应商 / 全部供应商(多工作表) */}
|
||||
<Modal
|
||||
title="导出供应商采购明细"
|
||||
open={supplierExportOpen}
|
||||
onCancel={() => setSupplierExportOpen(false)}
|
||||
onOk={() => {
|
||||
if (detail) {
|
||||
void exportPurchaseSuppliers(
|
||||
detail.purchase.id!,
|
||||
supplierExportScope === 'current' && supplierId > 0 ? supplierId : undefined,
|
||||
);
|
||||
}
|
||||
setSupplierExportOpen(false);
|
||||
}}
|
||||
okText="导出"
|
||||
destroyOnHidden
|
||||
>
|
||||
<Radio.Group
|
||||
className="py-2"
|
||||
value={supplierExportScope}
|
||||
onChange={(e) => setSupplierExportScope(e.target.value)}
|
||||
options={[
|
||||
{ value: 'current', label: `当前供应商(${purchaseSuppliers.find((s) => s.id === supplierId)?.name ?? '-'})` },
|
||||
{ value: 'all', label: '全部供应商(合并为一个 XLSX,每供应商一个工作表)' },
|
||||
]}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user