采购单优化
This commit is contained in:
+10
-179
@@ -6,13 +6,11 @@ import {
|
||||
Drawer,
|
||||
Empty,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
message,
|
||||
Modal,
|
||||
Popconfirm,
|
||||
Radio,
|
||||
Select,
|
||||
Space,
|
||||
Tag,
|
||||
Typography,
|
||||
@@ -26,33 +24,25 @@ import type {
|
||||
XinTableProps,
|
||||
} from '@/components/XinTable/typings.ts';
|
||||
import type IStoreOrder from '@/domain/iStoreOrder.ts';
|
||||
import type { IStoreOrderItem, IStoreOrderItemUpdate } from '@/domain/iStoreOrder.ts';
|
||||
import { STORE_ORDER_STATUS_MAP } from '@/domain/iStoreOrder.ts';
|
||||
import {
|
||||
getStoreOrder,
|
||||
updateOrderContainer,
|
||||
updateOrderStatus,
|
||||
batchUpdateOrderStatus,
|
||||
updateOrderItem,
|
||||
syncOrderItem,
|
||||
deleteStoreOrder,
|
||||
} from '@/api/order/store.ts';
|
||||
import { generatePurchase } from '@/api/purchase/order.ts';
|
||||
import { getStoreOptions } from '@/api/customer/store.ts';
|
||||
import { getSupplierOptions } from '@/api/customer/supplier.ts';
|
||||
import type IStore from '@/domain/iStore.ts';
|
||||
import type ISupplier from '@/domain/iSupplier.ts';
|
||||
import AuthButton from '@/components/AuthButton';
|
||||
import { DeleteOutlined, EditOutlined, SettingOutlined, SyncOutlined, UnorderedListOutlined } from '@ant-design/icons';
|
||||
import TextArea from "antd/es/input/TextArea";
|
||||
import { DeleteOutlined, SettingOutlined, UnorderedListOutlined } from '@ant-design/icons';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
/** 允许修改周转框/托盘数量的订单状态:已接单、采购中、配送中 */
|
||||
const CONTAINER_EDITABLE_STATUS = [1, 2, 3];
|
||||
|
||||
/** 允许修改/同步商品明细的订单状态:待接单、已接单、采购中、配送中(已完成/已取消锁定) */
|
||||
const ITEM_EDITABLE_STATUS = [0, 1, 2, 3];
|
||||
|
||||
/**
|
||||
* 状态流转合法路径:待接单→已接单/已取消;采购中→配送中/已完成;配送中→已完成
|
||||
@@ -99,18 +89,9 @@ const StoreOrderPage: React.FC = () => {
|
||||
const [generateLoading, setGenerateLoading] = useState(false);
|
||||
const [generateForm] = Form.useForm<{ purchase_date: dayjs.Dayjs }>();
|
||||
|
||||
/** 供应商选项(明细编辑用) */
|
||||
const [suppliers, setSuppliers] = useState<ISupplier[]>([]);
|
||||
|
||||
/** 商品明细编辑(弹窗表单,保存后后端重算订单总价) */
|
||||
const [itemEditOpen, setItemEditOpen] = useState(false);
|
||||
const [itemEditTarget, setItemEditTarget] = useState<IStoreOrderItem | null>(null);
|
||||
const [itemSaving, setItemSaving] = useState(false);
|
||||
const [itemForm] = Form.useForm<IStoreOrderItemUpdate>();
|
||||
|
||||
useEffect(() => {
|
||||
getStoreOptions().then((res) => setStores(res.data.data ?? []));
|
||||
getSupplierOptions().then((res) => setSuppliers(res.data.data ?? []));
|
||||
}, []);
|
||||
|
||||
const openDetail = async (id: number) => {
|
||||
@@ -184,47 +165,6 @@ const StoreOrderPage: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 打开明细编辑弹窗(商品快照 + 订货量/重量) */
|
||||
const openItemEdit = (item: IStoreOrderItem) => {
|
||||
setItemEditTarget(item);
|
||||
itemForm.setFieldsValue({
|
||||
supplier_id: item.supplier_id ?? 0,
|
||||
product_name: item.product_name ?? '',
|
||||
product_spec: item.product_spec ?? '',
|
||||
unit: item.unit ?? '',
|
||||
price: Number(item.price ?? 0),
|
||||
cost_price: Number(item.cost_price ?? 0),
|
||||
quantity: Number(item.quantity ?? 0),
|
||||
weight: Number(item.weight ?? 0),
|
||||
remark: item.remark ?? '',
|
||||
});
|
||||
setItemEditOpen(true);
|
||||
};
|
||||
|
||||
/** 保存明细修改:后端重算单品金额与订单总价 */
|
||||
const handleItemSave = async (values: IStoreOrderItemUpdate) => {
|
||||
if (!itemEditTarget?.id || !detail?.id) return;
|
||||
setItemSaving(true);
|
||||
try {
|
||||
await updateOrderItem(itemEditTarget.id, values);
|
||||
message.success('明细已更新,订单总价已重算');
|
||||
setItemEditOpen(false);
|
||||
await openDetail(detail.id);
|
||||
await tableRef.current?.reload();
|
||||
} finally {
|
||||
setItemSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
/** 一键同步:按商品ID拉取最新商品档案(供应商/品名/规格/单位/单价/成本价) */
|
||||
const handleItemSync = async (item: IStoreOrderItem) => {
|
||||
if (!item.id || !detail?.id) return;
|
||||
await syncOrderItem(item.id);
|
||||
message.success('已同步最新商品信息,订单总价已重算');
|
||||
await openDetail(detail.id);
|
||||
await tableRef.current?.reload();
|
||||
};
|
||||
|
||||
/** 删除订单(软删除,仅已取消订单可删;删除后后台/小程序端均不可见) */
|
||||
const handleDelete = async (id: number) => {
|
||||
await deleteStoreOrder(id);
|
||||
@@ -240,9 +180,6 @@ const StoreOrderPage: React.FC = () => {
|
||||
+ watchTrayNum * Number(containerOrder?.tray_price ?? 0);
|
||||
const previewTotal = Number(containerOrder?.product_amount ?? 0) + previewAdded;
|
||||
|
||||
/** 明细行编辑/同步按钮(仅未完成、未取消订单可见) */
|
||||
const itemEditable = ITEM_EDITABLE_STATUS.includes(detail?.status ?? -1);
|
||||
|
||||
const columns: XinTableColumn<IStoreOrder>[] = [
|
||||
{
|
||||
title: '订单号',
|
||||
@@ -622,8 +559,8 @@ const StoreOrderPage: React.FC = () => {
|
||||
<div className="w-30 shrink-0 text-center">供应商</div>
|
||||
<div className="w-30 shrink-0 text-center">单价</div>
|
||||
<div className="w-26 shrink-0 text-center">订货量</div>
|
||||
<div className="w-33 shrink-0 text-center">金额</div>
|
||||
{itemEditable ? <div className="w-40 shrink-0 text-center">操作</div> : null}
|
||||
<div className="w-33 shrink-0 text-center">订货金额</div>
|
||||
<div className="w-33 shrink-0 text-center">重量</div>
|
||||
</div>
|
||||
{(detail.items ?? []).map((item) => (
|
||||
<div key={item.id} className="flex items-center border-t border-gray-100 px-4 py-3">
|
||||
@@ -645,9 +582,8 @@ const StoreOrderPage: React.FC = () => {
|
||||
<div className="ml-3 min-w-0">
|
||||
<div className="text-sm font-medium">{item.product_name}</div>
|
||||
<div className="mt-0.5 text-xs text-gray-500">
|
||||
{item.product_spec || '-'} / {item.unit || '-'} · 成本价 ¥
|
||||
{item.cost_price ?? '0.00'}
|
||||
{Number(item.weight ?? 0) > 0 ? ` · 重量:${item.weight}` : ''}
|
||||
{item.product_spec || '-'} {item.unit || '-'}
|
||||
<div>成本:¥{item.cost_price ?? '0.00'}</div>
|
||||
</div>
|
||||
{item.remark ? (
|
||||
<div className="mt-0.5 truncate text-xs text-gray-500">备注:{item.remark}</div>
|
||||
@@ -660,42 +596,18 @@ const StoreOrderPage: React.FC = () => {
|
||||
<div className="w-33 shrink-0 text-center">
|
||||
<Text strong>¥{item.amount}</Text>
|
||||
</div>
|
||||
{itemEditable ? (
|
||||
<div className="w-40 shrink-0 text-center">
|
||||
<Space size={0}>
|
||||
<AuthButton auth="order.store.update">
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => openItemEdit(item)}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
</AuthButton>
|
||||
<AuthButton auth="order.store.update">
|
||||
<Popconfirm
|
||||
title="确认同步最新商品信息?"
|
||||
description="将按商品ID同步供应商、品名、规格、单位、成本价,并按门店等级价重算单价"
|
||||
onConfirm={() => handleItemSync(item)}
|
||||
>
|
||||
<Button type="link" size="small" icon={<SyncOutlined />}>
|
||||
同步
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</AuthButton>
|
||||
</Space>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="w-33 shrink-0 text-center">
|
||||
{item.weight ?? '-'} 斤
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{(detail.items ?? []).length === 0 ? (
|
||||
{(detail.items ?? []).length === 0 && (
|
||||
<Empty
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
description="暂无商品明细"
|
||||
className="py-8!"
|
||||
/>
|
||||
) : null}
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 附加信息 */}
|
||||
@@ -828,87 +740,6 @@ const StoreOrderPage: React.FC = () => {
|
||||
</div>
|
||||
</Space>
|
||||
</Modal>
|
||||
|
||||
{/* 编辑商品明细(保存后后端重算单品金额与订单总价) */}
|
||||
<Modal
|
||||
title="编辑商品明细"
|
||||
open={itemEditOpen}
|
||||
onCancel={() => setItemEditOpen(false)}
|
||||
onOk={() => itemForm.submit()}
|
||||
confirmLoading={itemSaving}
|
||||
okText="保存"
|
||||
width={640}
|
||||
destroyOnHidden
|
||||
>
|
||||
<div className="py-2 text-gray-500">
|
||||
订单 {detail?.order_no};修改保存后,系统将自动重算单品金额与订单总价。
|
||||
</div>
|
||||
<Form form={itemForm} layout="vertical" onFinish={handleItemSave}>
|
||||
<div className="grid grid-cols-2 gap-x-4">
|
||||
<Form.Item
|
||||
label="供应商"
|
||||
name="supplier_id"
|
||||
rules={[{ required: true, message: '请选择供应商' }]}
|
||||
>
|
||||
<Select
|
||||
showSearch={{
|
||||
optionFilterProp: 'label'
|
||||
}}
|
||||
placeholder="请选择供应商"
|
||||
options={suppliers.map((s) => ({ label: s.name, value: s.id }))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="品名"
|
||||
name="product_name"
|
||||
rules={[{ required: true, message: '请输入品名' }]}
|
||||
>
|
||||
<Input placeholder="请输入品名" maxLength={100} />
|
||||
</Form.Item>
|
||||
<Form.Item label="规格/包规" name="product_spec">
|
||||
<Input placeholder="请输入规格/包规" maxLength={100} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="计价单位"
|
||||
name="unit"
|
||||
rules={[{ required: true, message: '请输入计价单位' }]}
|
||||
>
|
||||
<Input placeholder="斤/件/箱等" maxLength={20} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="单价(元)"
|
||||
name="price"
|
||||
rules={[{ required: true, message: '请输入单价' }]}
|
||||
>
|
||||
<InputNumber className="w-full" min={0} precision={2} placeholder="请输入单价" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="成本价(元)"
|
||||
name="cost_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={1} 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.Item label="备注" name="remark">
|
||||
<TextArea className="w-full" placeholder="请输入备注" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user