import React, { useEffect, useRef, useState } from 'react'; import { Button, DatePicker, Descriptions, Drawer, Form, InputNumber, 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 { XinTableColumn, XinTableInstance, XinTableProps, } from '@/components/XinTable/typings.ts'; 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, 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'; import {UnorderedListOutlined} from "@ant-design/icons"; const { Title, Text } = Typography; /** 允许修改周转框/托盘数量的订单状态:已接单、采购中、配送中 */ const CONTAINER_EDITABLE_STATUS = [1, 2, 3]; /** * 状态流转合法路径:待接单→已接单/已取消;采购中→配送中/已完成;配送中→已完成 * (已接单→采购中 通过「生成采购单」完成,不在流转按钮内) */ const NEXT_STATUS: Record = { 0: [ { status: 1, label: '接单' }, { status: 9, label: '取消订单', danger: true }, ], 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: '取消订单(待接单 → 已取消)' }, ]; /** * 门店订单管理(只读 + 状态流转,订单由小程序端创建) */ const StoreOrderPage: React.FC = () => { const tableRef = useRef>(null); const [stores, setStores] = useState([]); const [detailOpen, setDetailOpen] = useState(false); const [detail, setDetail] = useState(null); const [detailLoading, setDetailLoading] = useState(false); const [containerOpen, setContainerOpen] = useState(false); const [containerOrder, setContainerOrder] = useState(null); const [containerSaving, setContainerSaving] = useState(false); const [containerForm] = Form.useForm<{ box_num: number; tray_num: number }>(); /** 勾选行(批量流转/生成采购单用) */ const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [batchOpen, setBatchOpen] = useState(false); const [batchTarget, setBatchTarget] = useState(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 ?? [])); }, []); const openDetail = async (id: number) => { setDetailOpen(true); setDetailLoading(true); try { const res = await getStoreOrder(id); setDetail(res.data.data ?? null); } finally { setDetailLoading(false); } }; const handleStatusChange = async (id: number, status: number) => { await updateOrderStatus(id, status); message.success('状态已更新'); setDetailOpen(false); 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 }); setContainerOpen(true); }; const handleContainerSave = async (values: { box_num: number; tray_num: number }) => { if (!containerOrder?.id) return; setContainerSaving(true); try { await updateOrderContainer(containerOrder.id, values); message.success('周转框/托盘数量已更新'); setContainerOpen(false); await tableRef.current?.reload(); } finally { setContainerSaving(false); } }; // 弹窗内实时预览:附加金额 = 框×单价 + 托盘×单价;订单总金额 = 商品金额 + 附加金额 const watchBoxNum = Number(Form.useWatch('box_num', containerForm) ?? 0); const watchTrayNum = Number(Form.useWatch('tray_num', containerForm) ?? 0); const previewAdded = watchBoxNum * Number(containerOrder?.box_price ?? 0) + watchTrayNum * Number(containerOrder?.tray_price ?? 0); const previewTotal = Number(containerOrder?.product_amount ?? 0) + previewAdded; const itemColumns: TableProps['columns'] = [ { title: '品名', dataIndex: 'product_name' }, { title: '规格', dataIndex: 'product_spec', render: (v) => v || '-' }, { title: '单价', dataIndex: 'price', align: 'right', render: (v) => `¥${v}` }, { title: '订货量', dataIndex: 'quantity', align: 'right' }, { title: '金额', dataIndex: 'amount', align: 'right', render: (v) => `¥${v}` }, { title: '备注', dataIndex: 'remark', render: (v) => v || '-' }, ]; const columns: XinTableColumn[] = [ { title: '订单号', hideInTable: true, dataIndex: 'order_no', valueType: 'text', hideInForm: true }, { title: '商品信息', dataIndex: 'items', hideInForm: true, hideInSearch: true, width: 300, render: (_, record) => { return ( { record.items && record.items.length > 0 && record.items.map(item => (
{item.product_name}
{ item.price } ¥ × { item.quantity }
))}
) } }, { title: '基本信息', dataIndex: 'order_no', hideInForm: true, hideInSearch: true, width: 300, render: (_, record) => { return (
订货门店: {record.store?.name ?? `门店#${record.store_id}`}
订单号: {record.order_no}
订货时间:{record.created_at}
) } }, { title: '门店', dataIndex: 'store_id', valueType: 'select', hideInForm: true, hideInTable: true, fieldProps: { options: stores.map((s) => ({ label: s.name, value: s.id })), showSearch: true, optionFilterProp: 'label', }, render: (_, record) => record.store?.name ?? '-', }, { title: '门店信息', dataIndex: 'store_id', hideInForm: true, hideInSearch: true, width: 300, render: (_, record) => (
联系人:{ record.store?.contact ?? '-' }
联系电话:{ record.store?.phone ?? '-' }
门店地址:{ record.store?.address ?? '-' }
), }, { title: '订货日期', dataIndex: 'order_date', valueType: 'dateRange', hideInForm: true, hideInTable: true, align: 'center', }, { title: '订单信息', hideInForm: true, dataIndex: 'status', hideInSearch: true, width: 200, render: (_, record) => (
订货数量: {record.total_quantity}
周转框数量: {record.box_num}
周转托盘数量: {record.tray_num}
) }, { title: '附加信息', hideInForm: true, dataIndex: 'box_num', hideInSearch: true, width: 200, render: (_, record) => (
附加总金额: {record.added_amount} ¥
商品总金额: {record.product_amount} ¥
订单总金额: {record.total_amount} ¥
) }, { title: '订单状态', dataIndex: 'status', valueType: 'select', hideInForm: true, align: 'center', fieldProps: { options: Object.entries(STORE_ORDER_STATUS_MAP).map(([value, item]) => ({ value: Number(value), label: item.text, })), }, render: (_, record) => ( {STORE_ORDER_STATUS_MAP[record.status ?? 0]?.text} ) }, { title: '采购单ID', dataIndex: 'purchase_id', valueType: 'digit', hideInForm: true, }, { title: '账单ID', dataIndex: 'statement_id', valueType: 'digit', hideInForm: true, }, { title: '操作栏', dataIndex: 'operate', align: 'center', hideInForm: true, hideInSearch: true, render: (_, record) => ( { (NEXT_STATUS[record.status ?? -1] ?? []).map((action) => ( handleStatusChange(record.id!, action.status)} > )) } { CONTAINER_EDITABLE_STATUS.includes(record.status ?? -1) ? ( ) : null } ) } ]; const tableProps: XinTableProps = { api: '/order/store', columns, rowKey: 'id', accessName: 'order.store', tableRef, operateShow: false, formProps: false, rowSelection: { selectedRowKeys, onChange: (keys) => setSelectedRowKeys(keys), }, actionBarRender: (dom) => [ dom.search, , , dom.keywordSearch, ], }; return ( <>
门店订单 小程序下单为待接单;接单后通过「生成采购单」合并为采购单并转采购中,采购中可批量流转为配送中或已完成。
{...tableProps} /> setDetailOpen(false)} width={720} loading={detailLoading} footer={ detail && NEXT_STATUS[detail.status ?? -1] ? ( {NEXT_STATUS[detail.status!].map((action) => ( handleStatusChange(detail.id!, action.status)} > ))} ) : null } > {detail ? ( <> {detail.order_no} {detail.store?.name} {detail.order_date} {STORE_ORDER_STATUS_MAP[detail.status ?? 0]?.text} {detail.total_quantity} ¥{detail.total_amount} {detail.remark ? ( {detail.remark} ) : null} 商品明细 rowKey="id" size="small" columns={itemColumns} dataSource={detail.items ?? []} pagination={false} summary={() => ( 合计 ¥{detail.total_amount} )} /> ) : null} {/* 生成采购单:合并已接单订单 */} setGenerateOpen(false)} onOk={() => generateForm.submit()} confirmLoading={generateLoading} okText="确认生成" destroyOnHidden >
{selectedRowKeys.length > 0 ? `将合并选中的 ${selectedRowKeys.length} 笔订单生成一张采购单(须均为已接单状态,否则整批中止)。` : '未勾选订单时,将合并全部「已接单」订单生成一张采购单。'} 生成后源订单自动转为「采购中」。
{/* 批量状态流转 */} setBatchOpen(false)} onOk={handleBatchStatus} confirmLoading={batchLoading} okText="确认流转" destroyOnHidden >
已勾选 {selectedRowKeys.length} 笔订单;任一订单不满足流转条件时,本次批量操作将整批中止,不做任何修改。
setBatchTarget(e.target.value as number)} > {BATCH_TARGET_OPTIONS.map((opt) => ( {opt.label} ))}
{/* 修改周转框/托盘数量 */} setContainerOpen(false)} onOk={() => containerForm.submit()} confirmLoading={containerSaving} okText="保存" destroyOnHidden >
订单 {containerOrder?.order_no},保存后将按单价自动重算附加金额与订单总金额。
商品总金额:¥{containerOrder?.product_amount ?? '0.00'}
附加总金额: ¥{previewAdded.toFixed(2)}
订单总金额: ¥{previewTotal.toFixed(2)}
); }; export default StoreOrderPage;