生成采购单基础
This commit is contained in:
+11
-2
@@ -10,7 +10,7 @@ export async function getStoreOrder(id: number) {
|
||||
});
|
||||
}
|
||||
|
||||
/** 订单状态流转(2配送中 3已完成 9取消) */
|
||||
/** 订单状态流转(1已接单 3配送中 4已完成 9取消;已接单→采购中由生成采购单完成) */
|
||||
export async function updateOrderStatus(id: number, status: number) {
|
||||
return createAxios({
|
||||
url: `/order/store/${id}/status`,
|
||||
@@ -19,6 +19,15 @@ export async function updateOrderStatus(id: number, status: number) {
|
||||
});
|
||||
}
|
||||
|
||||
/** 批量订单状态流转(全量校验,任一订单不允许流转则整批中止) */
|
||||
export async function batchUpdateOrderStatus(ids: number[], status: number) {
|
||||
return createAxios<{ success: number }>({
|
||||
url: '/order/store/batchStatus',
|
||||
method: 'put',
|
||||
data: { ids, status },
|
||||
});
|
||||
}
|
||||
|
||||
/** 修改周转框/周转托盘数量(自动重算附加金额与订单总金额,仅已接单/采购中/配送中可改) */
|
||||
export async function updateOrderContainer(id: number, data: { box_num: number; tray_num: number }) {
|
||||
return createAxios({
|
||||
@@ -28,7 +37,7 @@ export async function updateOrderContainer(id: number, data: { box_num: number;
|
||||
});
|
||||
}
|
||||
|
||||
/** 待汇总预览(按商品聚合) */
|
||||
/** 已接单预览(按商品聚合,生成采购单前确认) */
|
||||
export async function getOrderSummary() {
|
||||
return createAxios<IOrderSummaryRow[]>({
|
||||
url: '/order/store/summary',
|
||||
|
||||
@@ -16,12 +16,12 @@ export interface PurchaseItemUpdateParams {
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
/** C1 按门店订单汇总生成采购单 */
|
||||
export async function generatePurchase(purchase_date: string) {
|
||||
/** C1 合并「已接单」门店订单生成采购单(order_ids 为空 = 全部已接单;生成后源订单转采购中) */
|
||||
export async function generatePurchase(purchase_date: string, order_ids?: number[]) {
|
||||
return createAxios<{ id: number; purchase_no: string }>({
|
||||
url: '/purchase/order/generate',
|
||||
method: 'post',
|
||||
data: { purchase_date },
|
||||
data: { purchase_date, order_ids },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+159
-6
@@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
DatePicker,
|
||||
Descriptions,
|
||||
Drawer,
|
||||
Form,
|
||||
@@ -8,12 +9,14 @@ import {
|
||||
message,
|
||||
Modal,
|
||||
Popconfirm,
|
||||
Radio,
|
||||
Space,
|
||||
Table,
|
||||
Tag,
|
||||
Typography,
|
||||
Image,
|
||||
} from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import type { TableProps } from 'antd';
|
||||
import XinTable from '@/components/XinTable';
|
||||
import type {
|
||||
@@ -24,7 +27,8 @@ import type {
|
||||
import type IStoreOrder from '@/domain/iStoreOrder.ts';
|
||||
import type { IStoreOrderItem } from '@/domain/iStoreOrder.ts';
|
||||
import { STORE_ORDER_STATUS_MAP } from '@/domain/iStoreOrder.ts';
|
||||
import { getStoreOrder, updateOrderContainer, updateOrderStatus } from '@/api/order/store.ts';
|
||||
import { getStoreOrder, updateOrderContainer, updateOrderStatus, batchUpdateOrderStatus } from '@/api/order/store.ts';
|
||||
import { generatePurchase } from '@/api/purchase/order.ts';
|
||||
import { getStoreOptions } from '@/api/customer/store.ts';
|
||||
import type IStore from '@/domain/iStore.ts';
|
||||
import AuthButton from '@/components/AuthButton';
|
||||
@@ -36,17 +40,29 @@ const { Title, Text } = Typography;
|
||||
const CONTAINER_EDITABLE_STATUS = [1, 2, 3];
|
||||
|
||||
/**
|
||||
* 状态流转合法路径:待汇总→配送中/取消;已汇总→配送中;配送中→已完成
|
||||
* 状态流转合法路径:待接单→已接单/已取消;采购中→配送中/已完成;配送中→已完成
|
||||
* (已接单→采购中 通过「生成采购单」完成,不在流转按钮内)
|
||||
*/
|
||||
const NEXT_STATUS: Record<number, { status: number; label: string; danger?: boolean }[]> = {
|
||||
0: [
|
||||
{ status: 2, label: '开始配送' },
|
||||
{ status: 1, label: '接单' },
|
||||
{ status: 9, label: '取消订单', danger: true },
|
||||
],
|
||||
1: [{ status: 2, label: '开始配送' }],
|
||||
2: [{ status: 3, label: '完成订单' }],
|
||||
2: [
|
||||
{ status: 3, label: '开始配送' },
|
||||
{ status: 4, label: '完成订单' },
|
||||
],
|
||||
3: [{ status: 4, label: '完成订单' }],
|
||||
};
|
||||
|
||||
/** 批量流转可选目标状态(弹窗单选,后端全量校验,任一不允许则整批中止) */
|
||||
const BATCH_TARGET_OPTIONS = [
|
||||
{ value: 1, label: '接单(待接单 → 已接单)' },
|
||||
{ value: 3, label: '开始配送(采购中 → 配送中)' },
|
||||
{ value: 4, label: '完成订单(采购中/配送中 → 已完成)' },
|
||||
{ value: 9, label: '取消订单(待接单 → 已取消)' },
|
||||
];
|
||||
|
||||
/**
|
||||
* 门店订单管理(只读 + 状态流转,订单由小程序端创建)
|
||||
*/
|
||||
@@ -63,6 +79,17 @@ const StoreOrderPage: React.FC = () => {
|
||||
const [containerSaving, setContainerSaving] = useState(false);
|
||||
const [containerForm] = Form.useForm<{ box_num: number; tray_num: number }>();
|
||||
|
||||
/** 勾选行(批量流转/生成采购单用) */
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||
const [batchOpen, setBatchOpen] = useState(false);
|
||||
const [batchTarget, setBatchTarget] = useState<number | null>(null);
|
||||
const [batchLoading, setBatchLoading] = useState(false);
|
||||
|
||||
/** 生成采购单(合并已接单订单,生成后源订单转采购中) */
|
||||
const [generateOpen, setGenerateOpen] = useState(false);
|
||||
const [generateLoading, setGenerateLoading] = useState(false);
|
||||
const [generateForm] = Form.useForm<{ purchase_date: dayjs.Dayjs }>();
|
||||
|
||||
useEffect(() => {
|
||||
getStoreOptions().then((res) => setStores(res.data.data ?? []));
|
||||
}, []);
|
||||
@@ -85,6 +112,40 @@ const StoreOrderPage: React.FC = () => {
|
||||
await tableRef.current?.reload();
|
||||
};
|
||||
|
||||
/** 批量状态流转:后端全量校验,任一订单不允许流转则整批中止 */
|
||||
const handleBatchStatus = async () => {
|
||||
if (batchTarget === null) {
|
||||
message.warning('请选择要流转的目标状态');
|
||||
return;
|
||||
}
|
||||
setBatchLoading(true);
|
||||
try {
|
||||
const res = await batchUpdateOrderStatus(selectedRowKeys as number[], batchTarget);
|
||||
message.success(`已批量流转 ${res.data.data?.success ?? 0} 单`);
|
||||
setBatchOpen(false);
|
||||
setBatchTarget(null);
|
||||
setSelectedRowKeys([]);
|
||||
await tableRef.current?.reload();
|
||||
} finally {
|
||||
setBatchLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/** 生成采购单:勾选时合并所选订单(须均为已接单),未勾选时合并全部已接单订单 */
|
||||
const handleGeneratePurchase = async (values: { purchase_date: dayjs.Dayjs }) => {
|
||||
setGenerateLoading(true);
|
||||
try {
|
||||
const orderIds = selectedRowKeys.length > 0 ? selectedRowKeys.map(Number) : undefined;
|
||||
const res = await generatePurchase(values.purchase_date.format('YYYY-MM-DD'), orderIds);
|
||||
message.success(`采购单 ${res.data.data?.purchase_no} 已生成,源订单已转为采购中`);
|
||||
setGenerateOpen(false);
|
||||
setSelectedRowKeys([]);
|
||||
await tableRef.current?.reload();
|
||||
} finally {
|
||||
setGenerateLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const openContainer = (record: IStoreOrder) => {
|
||||
setContainerOrder(record);
|
||||
containerForm.setFieldsValue({ box_num: record.box_num, tray_num: record.tray_num });
|
||||
@@ -294,6 +355,20 @@ const StoreOrderPage: React.FC = () => {
|
||||
<Button key="detail" type={'primary'} icon={<UnorderedListOutlined />} onClick={() => openDetail(record.id!)}>
|
||||
详情
|
||||
</Button>
|
||||
{
|
||||
(NEXT_STATUS[record.status ?? -1] ?? []).map((action) => (
|
||||
<AuthButton key={action.status} auth="order.store.update">
|
||||
<Popconfirm
|
||||
title={`确认将订单状态更新为「${action.label}」?`}
|
||||
onConfirm={() => handleStatusChange(record.id!, action.status)}
|
||||
>
|
||||
<Button type={action.danger ? undefined : 'primary'} danger={action.danger}>
|
||||
{action.label}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</AuthButton>
|
||||
))
|
||||
}
|
||||
{
|
||||
CONTAINER_EDITABLE_STATUS.includes(record.status ?? -1) ? (
|
||||
<AuthButton key="container" auth="order.store.update">
|
||||
@@ -316,6 +391,28 @@ const StoreOrderPage: React.FC = () => {
|
||||
tableRef,
|
||||
operateShow: false,
|
||||
formProps: false,
|
||||
rowSelection: {
|
||||
selectedRowKeys,
|
||||
onChange: (keys) => setSelectedRowKeys(keys),
|
||||
},
|
||||
actionBarRender: (dom) => [
|
||||
dom.search,
|
||||
<AuthButton key="generate" auth="purchase.order.generate">
|
||||
<Button type="primary" onClick={() => setGenerateOpen(true)}>
|
||||
生成采购单
|
||||
</Button>
|
||||
</AuthButton>,
|
||||
<AuthButton key="batch" auth="order.store.update">
|
||||
<Button
|
||||
type="primary"
|
||||
disabled={selectedRowKeys.length === 0}
|
||||
onClick={() => setBatchOpen(true)}
|
||||
>
|
||||
批量操作{selectedRowKeys.length > 0 ? `(${selectedRowKeys.length})` : ''}
|
||||
</Button>
|
||||
</AuthButton>,
|
||||
dom.keywordSearch,
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -323,7 +420,7 @@ const StoreOrderPage: React.FC = () => {
|
||||
<div className="mb-5">
|
||||
<Title level={3}>门店订单</Title>
|
||||
<Text type="secondary">
|
||||
小程序下单汇总;待汇总订单将进入采购单生成,订单创建与取消在小程序端完成。
|
||||
小程序下单为待接单;接单后通过「生成采购单」合并为采购单并转采购中,采购中可批量流转为配送中或已完成。
|
||||
</Text>
|
||||
</div>
|
||||
<XinTable<IStoreOrder> {...tableProps} />
|
||||
@@ -397,6 +494,62 @@ const StoreOrderPage: React.FC = () => {
|
||||
) : null}
|
||||
</Drawer>
|
||||
|
||||
{/* 生成采购单:合并已接单订单 */}
|
||||
<Modal
|
||||
title="生成采购单"
|
||||
open={generateOpen}
|
||||
onCancel={() => setGenerateOpen(false)}
|
||||
onOk={() => generateForm.submit()}
|
||||
confirmLoading={generateLoading}
|
||||
okText="确认生成"
|
||||
destroyOnHidden
|
||||
>
|
||||
<div className="py-2 text-gray-500">
|
||||
{selectedRowKeys.length > 0
|
||||
? `将合并选中的 ${selectedRowKeys.length} 笔订单生成一张采购单(须均为已接单状态,否则整批中止)。`
|
||||
: '未勾选订单时,将合并全部「已接单」订单生成一张采购单。'}
|
||||
生成后源订单自动转为「采购中」。
|
||||
</div>
|
||||
<Form form={generateForm} layout="vertical" onFinish={handleGeneratePurchase}>
|
||||
<Form.Item
|
||||
label="采购日期"
|
||||
name="purchase_date"
|
||||
initialValue={dayjs()}
|
||||
rules={[{ required: true, message: '请选择采购日期' }]}
|
||||
>
|
||||
<DatePicker className="w-full" allowClear={false} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{/* 批量状态流转 */}
|
||||
<Modal
|
||||
title="批量状态流转"
|
||||
open={batchOpen}
|
||||
onCancel={() => setBatchOpen(false)}
|
||||
onOk={handleBatchStatus}
|
||||
confirmLoading={batchLoading}
|
||||
okText="确认流转"
|
||||
destroyOnHidden
|
||||
>
|
||||
<div className="py-2 text-gray-500">
|
||||
已勾选 {selectedRowKeys.length} 笔订单;任一订单不满足流转条件时,本次批量操作将整批中止,不做任何修改。
|
||||
</div>
|
||||
<Radio.Group
|
||||
className="w-full py-2"
|
||||
value={batchTarget}
|
||||
onChange={(e) => setBatchTarget(e.target.value as number)}
|
||||
>
|
||||
<Space orientation="vertical">
|
||||
{BATCH_TARGET_OPTIONS.map((opt) => (
|
||||
<Radio key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</Radio>
|
||||
))}
|
||||
</Space>
|
||||
</Radio.Group>
|
||||
</Modal>
|
||||
|
||||
{/* 修改周转框/托盘数量 */}
|
||||
<Modal
|
||||
title="修改周转框/托盘"
|
||||
|
||||
@@ -457,7 +457,7 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
destroyOnHidden
|
||||
>
|
||||
<div className="py-2 text-gray-500">
|
||||
将汇总所选日期全部「待汇总」订单,按商品聚合生成采购单(估算单价取最低等级价)。
|
||||
将合并全部「已接单」门店订单,按商品聚合生成采购单(估算单价取最低等级价),生成后源订单转为「采购中」。
|
||||
</div>
|
||||
<Form
|
||||
form={generateForm}
|
||||
@@ -480,7 +480,7 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
title={detail ? `采购单 ${detail.purchase_no}` : '采购单详情'}
|
||||
open={detailOpen}
|
||||
onClose={() => setDetailOpen(false)}
|
||||
width={1080}
|
||||
size={1080}
|
||||
loading={detailLoading}
|
||||
>
|
||||
{detail ? (
|
||||
|
||||
Reference in New Issue
Block a user