账单生成
This commit is contained in:
+19
-13
@@ -1,6 +1,7 @@
|
||||
import createAxios from '@/utils/request';
|
||||
import { downloadBlob } from '@/api/common/download.ts';
|
||||
import type {
|
||||
IBillPrepare,
|
||||
IPurchaseCell,
|
||||
IPurchaseDetail,
|
||||
IPurchaseStoreSummary,
|
||||
@@ -23,9 +24,10 @@ export interface PurchaseCellUpdateParams {
|
||||
weight?: number;
|
||||
}
|
||||
|
||||
/** 采购单周转框/托盘合并记录修改:单笔订单的框/托盘数量 */
|
||||
export interface PurchaseContainerOrderParams {
|
||||
order_id: number;
|
||||
/** 生成账单:单个门店的配送费/周转筐/托盘数量 */
|
||||
export interface BillGenerateStoreParams {
|
||||
store_id: number;
|
||||
delivery_fee: number;
|
||||
box_num: number;
|
||||
tray_num: number;
|
||||
}
|
||||
@@ -84,16 +86,20 @@ export async function getPurchaseStoreSummary(purchaseId: number, storeId: numbe
|
||||
});
|
||||
}
|
||||
|
||||
/** 采购单周转框/托盘合并记录修改:按门店覆盖全部订单逐笔更新(自动重算附加金额与订单总金额) */
|
||||
export async function updatePurchaseContainer(
|
||||
purchaseId: number,
|
||||
storeId: number,
|
||||
orders: PurchaseContainerOrderParams[],
|
||||
) {
|
||||
return createAxios({
|
||||
url: `/purchase/order/${purchaseId}/container/${storeId}`,
|
||||
method: 'put',
|
||||
data: { orders },
|
||||
/** 账单生成预览:按门店汇总商品金额(金额只读) */
|
||||
export async function getBillPrepare(purchaseId: number) {
|
||||
return createAxios<IBillPrepare>({
|
||||
url: `/purchase/order/${purchaseId}/bill/prepare`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
/** 生成账单:已完成采购单按门店各生成一张(关联采购单全部订单,金额由系统汇总不可修改) */
|
||||
export async function generateBill(purchaseId: number, stores: BillGenerateStoreParams[]) {
|
||||
return createAxios<{ count: number }>({
|
||||
url: `/purchase/order/${purchaseId}/bill`,
|
||||
method: 'post',
|
||||
data: { stores },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -48,34 +48,56 @@ export interface IPurchaseDetail {
|
||||
purchase: IPurchaseOrder;
|
||||
stores: { id: number; name: string }[];
|
||||
items: IPurchaseDetailRow[];
|
||||
/** 周转框/托盘合并记录(按门店聚合) */
|
||||
containers: IPurchaseContainerStore[];
|
||||
/** 门店账单(采购单完成后按门店生成) */
|
||||
bills: IBill[];
|
||||
}
|
||||
|
||||
/** 合并记录中的底层门店订单(订单号 + 框/托盘数量) */
|
||||
export interface IPurchaseContainerOrder {
|
||||
order_id: number;
|
||||
order_no: string;
|
||||
/** 门店账单(采购单完成后按门店生成,商品金额为订单汇总快照不可修改) */
|
||||
export interface IBill {
|
||||
id: number;
|
||||
bill_no: string;
|
||||
purchase_id: number;
|
||||
store_id: number;
|
||||
store_name?: string;
|
||||
bill_date: string;
|
||||
/** 商品金额(订单商品金额汇总) */
|
||||
product_amount: string;
|
||||
/** 配送费(生成账单时填写) */
|
||||
delivery_fee: string;
|
||||
box_num: number;
|
||||
tray_num: number;
|
||||
/** 周转筐单价(生成时快照) */
|
||||
box_price: string;
|
||||
/** 周转托盘单价(生成时快照) */
|
||||
tray_price: string;
|
||||
/** 附加金额 = 周转筐×筐单价 + 托盘×托盘单价 */
|
||||
added_amount: string;
|
||||
/** 账单总金额 = 商品金额 + 配送费 + 附加金额 */
|
||||
total_amount: string;
|
||||
order_count?: number;
|
||||
remark?: string;
|
||||
created_at?: string;
|
||||
}
|
||||
|
||||
/** 门店周转框/托盘合并记录(该门店在本采购单下全部订单的合计) */
|
||||
export interface IPurchaseContainerStore {
|
||||
/** 账单生成预览行(按门店汇总,金额只读) */
|
||||
export interface IBillPrepareStore {
|
||||
store_id: number;
|
||||
store_name: string;
|
||||
/** 周转框合计 */
|
||||
box_num: number;
|
||||
/** 周转托盘合计 */
|
||||
tray_num: number;
|
||||
/** 周转框单价(站点配置) */
|
||||
order_count: number;
|
||||
/** 商品金额(订单汇总,不可修改) */
|
||||
product_amount: string;
|
||||
/** 周转筐单价(站点配置) */
|
||||
box_price: string;
|
||||
/** 周转托盘单价(站点配置) */
|
||||
tray_price: string;
|
||||
/** 附加金额合计 = 框合计×框单价 + 托盘合计×托盘单价 */
|
||||
added_amount: string;
|
||||
order_count: number;
|
||||
orders: IPurchaseContainerOrder[];
|
||||
/** 已生成的账单(未生成为 null) */
|
||||
bill: IBill | null;
|
||||
}
|
||||
|
||||
/** 账单生成预览(采购单 + 门店行) */
|
||||
export interface IBillPrepare {
|
||||
purchase: { id: number; purchase_no: string; status: number };
|
||||
stores: IBillPrepareStore[];
|
||||
}
|
||||
|
||||
/** 单元格下钻明细行(溯源订货单明细,附订单号/状态/可编辑标记) */
|
||||
|
||||
@@ -45,6 +45,8 @@ export default interface IStoreOrder {
|
||||
store_id?: number;
|
||||
purchase_id?: number;
|
||||
statement_id?: number;
|
||||
/** 关联账单ID(采购单完成后按门店生成账单时回写) */
|
||||
bill_id?: number;
|
||||
store?: {
|
||||
id: number;
|
||||
name: string;
|
||||
@@ -56,18 +58,6 @@ export default interface IStoreOrder {
|
||||
total_quantity?: number;
|
||||
total_weight?: string;
|
||||
total_amount?: string;
|
||||
product_amount: string;
|
||||
added_amount: string;
|
||||
box_num: number;
|
||||
tray_num: number;
|
||||
/** 周转框单价(列表接口附加) */
|
||||
box_price?: string;
|
||||
/** 周转托盘单价(列表接口附加) */
|
||||
tray_price?: string;
|
||||
/** 周转框金额(列表接口附加) */
|
||||
box_amount?: string;
|
||||
/** 周转托盘金额(列表接口附加) */
|
||||
tray_amount?: string;
|
||||
/** 0待接单 1已接单 2采购中 3配送中 4已完成 9已取消 */
|
||||
status?: number;
|
||||
remark?: string;
|
||||
|
||||
+16
-53
@@ -261,12 +261,8 @@ const StoreOrderPage: React.FC = () => {
|
||||
{record.total_quantity}
|
||||
</div>
|
||||
<div>
|
||||
<Text type={'secondary'}>周转框数量:</Text>
|
||||
{record.box_num}
|
||||
</div>
|
||||
<div>
|
||||
<Text type={'secondary'}>周转托盘数量:</Text>
|
||||
{record.tray_num}
|
||||
<Text type={'secondary'}>总重量:</Text>
|
||||
{record.total_weight}斤
|
||||
</div>
|
||||
</Space>
|
||||
)
|
||||
@@ -274,24 +270,11 @@ const StoreOrderPage: React.FC = () => {
|
||||
{
|
||||
title: '订单金额',
|
||||
hideInForm: true,
|
||||
dataIndex: 'box_num',
|
||||
dataIndex: 'total_amount',
|
||||
hideInSearch: true,
|
||||
width: 200,
|
||||
render: (_, record) => (
|
||||
<Space orientation={'vertical'}>
|
||||
<div>
|
||||
<Text type={'secondary'}>商品总金额:</Text>
|
||||
<span className={'text-[red]'}>{record.product_amount} ¥</span>
|
||||
</div>
|
||||
<div>
|
||||
<Text type={'secondary'}>附加总金额:</Text>
|
||||
<span className={'text-[red]'}>{record.added_amount} ¥</span>
|
||||
</div>
|
||||
<div>
|
||||
<Text type={'secondary'}>订单总金额:</Text>
|
||||
<span className={'text-[red] text-[16px]'}>{record.total_amount} ¥</span>
|
||||
</div>
|
||||
</Space>
|
||||
render: (text) => (
|
||||
<span className={'text-[red] text-[16px]'}>{text} ¥</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
@@ -334,7 +317,7 @@ const StoreOrderPage: React.FC = () => {
|
||||
},
|
||||
{
|
||||
title: '账单ID',
|
||||
dataIndex: 'statement_id',
|
||||
dataIndex: 'bill_id',
|
||||
valueType: 'digit',
|
||||
hideInForm: true,
|
||||
},
|
||||
@@ -507,8 +490,8 @@ const StoreOrderPage: React.FC = () => {
|
||||
</Tag>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="订货总量">{detail.total_quantity}</Descriptions.Item>
|
||||
<Descriptions.Item label="周转框数量">{detail.box_num}</Descriptions.Item>
|
||||
<Descriptions.Item label="周转托盘数量">{detail.tray_num}</Descriptions.Item>
|
||||
<Descriptions.Item label="总重量">{detail.total_weight}斤</Descriptions.Item>
|
||||
<Descriptions.Item label="账单ID">{detail.bill_id ?? '-'}</Descriptions.Item>
|
||||
{detail.remark ? (
|
||||
<Descriptions.Item label="备注" span={3}>
|
||||
{detail.remark}
|
||||
@@ -568,6 +551,14 @@ const StoreOrderPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex bg-gray-50 px-4 py-3 text-sm">
|
||||
<div className="flex-1">总计</div>
|
||||
<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">{detail.total_quantity}</div>
|
||||
<div className="w-33 shrink-0 text-center text-[red] text-[16px]">¥{detail.total_amount}</div>
|
||||
<div className="w-33 shrink-0 text-center">{detail.total_weight}斤</div>
|
||||
</div>
|
||||
{(detail.items ?? []).length === 0 && (
|
||||
<Empty
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
@@ -576,34 +567,6 @@ const StoreOrderPage: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 附加信息 */}
|
||||
<div className="mt-3! flex justify-end">
|
||||
<div className="w-full rounded bg-gray-50 p-4 text-sm">
|
||||
<div className="flex justify-between py-1">
|
||||
<Text type="secondary">
|
||||
周转框金额({detail.box_num} × ¥{detail.box_price ?? '0.00'})
|
||||
</Text>
|
||||
<span>¥{detail.box_amount ?? '0.00'}</span>
|
||||
</div>
|
||||
<div className="flex justify-between py-1">
|
||||
<Text type="secondary">
|
||||
周转托盘金额({detail.tray_num} × ¥{detail.tray_price ?? '0.00'})
|
||||
</Text>
|
||||
<span>¥{detail.tray_amount ?? '0.00'}</span>
|
||||
</div>
|
||||
<div className="flex justify-between py-1">
|
||||
<Text type="secondary">商品总金额</Text>
|
||||
<span>¥{detail.product_amount}</span>
|
||||
</div>
|
||||
<div className="mt-1! flex justify-between border-t border-gray-200 pt-2">
|
||||
<Text strong>订单总金额</Text>
|
||||
<Text strong type="danger">
|
||||
¥{detail.total_amount}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</Drawer>
|
||||
|
||||
+251
-169
@@ -20,7 +20,7 @@ import {
|
||||
Tag,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import {DownloadOutlined, EditOutlined, UnorderedListOutlined} from '@ant-design/icons';
|
||||
import {AccountBookOutlined, DownloadOutlined, EditOutlined, UnorderedListOutlined} from '@ant-design/icons';
|
||||
import type { TableProps } from 'antd';
|
||||
import XinTable from '@/components/XinTable';
|
||||
import type {
|
||||
@@ -30,9 +30,10 @@ import type {
|
||||
} from '@/components/XinTable/typings.ts';
|
||||
import type IPurchaseOrder from '@/domain/iPurchaseOrder.ts';
|
||||
import type {
|
||||
IBill,
|
||||
IBillPrepare,
|
||||
IPurchaseCell,
|
||||
IPurchaseCellItem,
|
||||
IPurchaseContainerStore,
|
||||
IPurchaseDetail,
|
||||
IPurchaseDetailRow,
|
||||
IPurchaseStoreItem,
|
||||
@@ -42,12 +43,15 @@ import { PURCHASE_STATUS_MAP } from '@/domain/iPurchaseOrder.ts';
|
||||
import { STORE_ORDER_STATUS_MAP } from '@/domain/iStoreOrder.ts';
|
||||
import {
|
||||
exportPurchase,
|
||||
generateBill,
|
||||
getBillPrepare,
|
||||
getPurchaseCell,
|
||||
getPurchaseDetail,
|
||||
getPurchaseStoreSummary,
|
||||
type PurchaseCellUpdateParams, type PurchaseContainerOrderParams, type PurchaseRowUpdateParams,
|
||||
type BillGenerateStoreParams,
|
||||
type PurchaseCellUpdateParams,
|
||||
type PurchaseRowUpdateParams,
|
||||
updatePurchaseCellItem,
|
||||
updatePurchaseContainer,
|
||||
updatePurchaseRow,
|
||||
} from '@/api/purchase/order.ts';
|
||||
import { Update } from '@/api/common/table.ts';
|
||||
@@ -100,25 +104,28 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
const [storeSummary, setStoreSummary] = useState<IPurchaseStoreSummary | null>(null);
|
||||
const [storeLoading, setStoreLoading] = useState(false);
|
||||
|
||||
// 周转框/托盘合并记录修改(按门店覆盖全部订单逐笔修改)
|
||||
const [containerOpen, setContainerOpen] = useState(false);
|
||||
const [containerStore, setContainerStore] = useState<IPurchaseContainerStore | null>(null);
|
||||
const [containerSaving, setContainerSaving] = useState(false);
|
||||
const [containerForm] = Form.useForm<{ orders: PurchaseContainerOrderParams[] }>();
|
||||
// 生成账单(已完成采购单按门店生成:填写配送费/周转筐/托盘数量,金额只读)
|
||||
const [billOpen, setBillOpen] = useState(false);
|
||||
const [billPrepare, setBillPrepare] = useState<IBillPrepare | null>(null);
|
||||
const [billLoading, setBillLoading] = useState(false);
|
||||
const [billSaving, setBillSaving] = useState(false);
|
||||
const [billForm] = Form.useForm<{ stores: BillGenerateStoreParams[] }>();
|
||||
|
||||
// 弹窗内实时预览:合并合计 = Σ 各订单框/托盘数量,附加金额 = 合计 × 单价
|
||||
const watchContainerOrders = Form.useWatch('orders', containerForm) ?? [];
|
||||
const previewContainerBox = watchContainerOrders.reduce(
|
||||
(sum, order) => sum + Number(order?.box_num ?? 0),
|
||||
0,
|
||||
);
|
||||
const previewContainerTray = watchContainerOrders.reduce(
|
||||
(sum, order) => sum + Number(order?.tray_num ?? 0),
|
||||
0,
|
||||
);
|
||||
const previewContainerAdded =
|
||||
previewContainerBox * Number(containerStore?.box_price ?? 0) +
|
||||
previewContainerTray * Number(containerStore?.tray_price ?? 0);
|
||||
// 弹窗内实时预览:附加金额 = 筐×筐单价 + 托盘×托盘单价;总额 = 商品 + 配送费 + 附加
|
||||
const watchBillStores = Form.useWatch('stores', billForm) ?? [];
|
||||
const billPreview = (billPrepare?.stores ?? []).map((row, index) => {
|
||||
const input = watchBillStores[index] ?? {};
|
||||
const deliveryFee = Number(input.delivery_fee ?? 0);
|
||||
const added =
|
||||
Number(input.box_num ?? 0) * Number(row.box_price) +
|
||||
Number(input.tray_num ?? 0) * Number(row.tray_price);
|
||||
return {
|
||||
added,
|
||||
total: Number(row.product_amount) + deliveryFee + added,
|
||||
};
|
||||
});
|
||||
const billAllGenerated =
|
||||
(billPrepare?.stores ?? []).length > 0 && billPrepare!.stores.every((row) => row.bill !== null);
|
||||
|
||||
useEffect(() => {
|
||||
getSupplierOptions().then((res) => setSuppliers(res.data.data ?? []));
|
||||
@@ -264,34 +271,45 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 打开周转框/托盘合并记录修改(初始化该门店全部订单的当前数量) */
|
||||
const openContainerEdit = (store: IPurchaseContainerStore) => {
|
||||
setContainerStore(store);
|
||||
containerForm.setFieldsValue({
|
||||
orders: store.orders.map((order) => ({
|
||||
order_id: order.order_id,
|
||||
box_num: order.box_num,
|
||||
tray_num: order.tray_num,
|
||||
})),
|
||||
});
|
||||
setContainerOpen(true);
|
||||
/** 打开生成账单弹窗:拉取按门店汇总的商品金额(只读),初始化配送费/周转筐/托盘数量 */
|
||||
const openBillGenerate = async (id: number) => {
|
||||
setBillOpen(true);
|
||||
setBillLoading(true);
|
||||
setBillPrepare(null);
|
||||
try {
|
||||
const res = await getBillPrepare(id);
|
||||
const data = res.data.data ?? null;
|
||||
setBillPrepare(data);
|
||||
billForm.setFieldsValue({
|
||||
stores: (data?.stores ?? []).map((row) => ({
|
||||
store_id: row.store_id,
|
||||
delivery_fee: row.bill ? Number(row.bill.delivery_fee) : 0,
|
||||
box_num: row.bill ? row.bill.box_num : 0,
|
||||
tray_num: row.bill ? row.bill.tray_num : 0,
|
||||
})),
|
||||
});
|
||||
} finally {
|
||||
setBillLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/** 提交合并记录修改:逐笔更新订单,自动重算附加金额与订单总金额 */
|
||||
const handleContainerSave = async (values: { orders: PurchaseContainerOrderParams[] }) => {
|
||||
if (!detail || !containerStore) {
|
||||
/** 提交生成账单:按门店各生成一张并关联采购单全部订单,金额由系统汇总不可修改 */
|
||||
const handleBillSave = async (values: { stores: BillGenerateStoreParams[] }) => {
|
||||
if (!billPrepare) {
|
||||
return;
|
||||
}
|
||||
setContainerSaving(true);
|
||||
setBillSaving(true);
|
||||
try {
|
||||
await updatePurchaseContainer(detail.purchase.id!, containerStore.store_id, values.orders);
|
||||
message.success('周转框/托盘已更新,订单金额已重算');
|
||||
setContainerOpen(false);
|
||||
setContainerStore(null);
|
||||
await loadDetail(detail.purchase.id!);
|
||||
const res = await generateBill(billPrepare.purchase.id, values.stores);
|
||||
message.success(`已生成 ${res.data.data?.count ?? 0} 张门店账单,采购单订单已关联`);
|
||||
setBillOpen(false);
|
||||
setBillPrepare(null);
|
||||
await tableRef.current?.reload();
|
||||
if (detail && detail.purchase.id === billPrepare.purchase.id) {
|
||||
await loadDetail(detail.purchase.id!);
|
||||
}
|
||||
} finally {
|
||||
setContainerSaving(false);
|
||||
setBillSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -473,56 +491,87 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
/** 周转框/托盘合并记录列:门店/订单数/框合计/托盘合计/附加金额(合计可点击下钻逐订单修改) */
|
||||
const containerColumns: TableProps<IPurchaseContainerStore>['columns'] = [
|
||||
{ title: '门店', dataIndex: 'store_name', width: 180, align: 'center' },
|
||||
{ title: '订单数', dataIndex: 'order_count', width: 90, align: 'center' },
|
||||
/** 门店账单列:门店/账单号/账单日期/商品金额/配送费/周转筐/托盘/附加金额/总金额 */
|
||||
const billColumns: TableProps<IBill>['columns'] = [
|
||||
{ title: '门店', dataIndex: 'store_name', width: 140, align: 'center' },
|
||||
{
|
||||
title: '周转框合计',
|
||||
dataIndex: 'box_num',
|
||||
title: '账单号',
|
||||
dataIndex: 'bill_no',
|
||||
width: 180,
|
||||
align: 'center',
|
||||
render: (v) => <Text copyable={{ text: v }}>{v}</Text>,
|
||||
},
|
||||
{ title: '账单日期', dataIndex: 'bill_date', width: 110, align: 'center' },
|
||||
{
|
||||
title: '商品金额',
|
||||
dataIndex: 'product_amount',
|
||||
width: 110,
|
||||
align: 'center',
|
||||
render: (_, row) => (
|
||||
<Typography.Link onClick={() => openContainerEdit(row)}>{row.box_num}</Typography.Link>
|
||||
),
|
||||
render: (v) => `¥${Number(v).toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
title: '周转托盘合计',
|
||||
dataIndex: 'tray_num',
|
||||
width: 120,
|
||||
title: '配送费',
|
||||
dataIndex: 'delivery_fee',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
render: (_, row) => (
|
||||
<Typography.Link onClick={() => openContainerEdit(row)}>{row.tray_num}</Typography.Link>
|
||||
),
|
||||
render: (v) => `¥${Number(v).toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
title: '周转筐',
|
||||
key: 'box',
|
||||
width: 110,
|
||||
align: 'center',
|
||||
render: (_, row) => `${row.box_num} × ¥${Number(row.box_price).toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
title: '周转托盘',
|
||||
key: 'tray',
|
||||
width: 110,
|
||||
align: 'center',
|
||||
render: (_, row) => `${row.tray_num} × ¥${Number(row.tray_price).toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
title: '附加金额',
|
||||
dataIndex: 'added_amount',
|
||||
width: 110,
|
||||
width: 100,
|
||||
align: 'center',
|
||||
render: (_, row) => <Text strong type="danger">¥{row.added_amount}</Text>,
|
||||
render: (v) => `¥${Number(v).toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
title: '账单总金额',
|
||||
dataIndex: 'total_amount',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
render: (v) => <Text strong type="danger">¥{Number(v).toFixed(2)}</Text>,
|
||||
},
|
||||
];
|
||||
|
||||
/** 周转框/托盘合并记录合计行 */
|
||||
const renderContainerSummary = () => {
|
||||
const containers = detail?.containers ?? [];
|
||||
const totalBox = containers.reduce((sum, row) => sum + row.box_num, 0);
|
||||
const totalTray = containers.reduce((sum, row) => sum + row.tray_num, 0);
|
||||
const totalAdded = containers.reduce((sum, row) => sum + Number(row.added_amount), 0);
|
||||
/** 门店账单合计行 */
|
||||
const renderBillSummary = () => {
|
||||
const bills = detail?.bills ?? [];
|
||||
const totalProduct = bills.reduce((sum, row) => sum + Number(row.product_amount), 0);
|
||||
const totalDelivery = bills.reduce((sum, row) => sum + Number(row.delivery_fee), 0);
|
||||
const totalAdded = bills.reduce((sum, row) => sum + Number(row.added_amount), 0);
|
||||
const totalAmount = bills.reduce((sum, row) => sum + Number(row.total_amount), 0);
|
||||
return (
|
||||
<Table.Summary.Row>
|
||||
<Table.Summary.Cell index={0} colSpan={2} align="center">
|
||||
<Table.Summary.Cell index={0} colSpan={3} align="center">
|
||||
<Text strong>合计</Text>
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell index={2} align="center">
|
||||
<Text strong>{totalBox}</Text>
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell index={3} align="center">
|
||||
<Text strong>{totalTray}</Text>
|
||||
<Text strong>¥{totalProduct.toFixed(2)}</Text>
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell index={4} align="center">
|
||||
<Text strong type="danger">¥{totalAdded.toFixed(2)}</Text>
|
||||
<Text strong>¥{totalDelivery.toFixed(2)}</Text>
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell index={5} colSpan={2} align="center">
|
||||
<Text strong>-</Text>
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell index={7} align="center">
|
||||
<Text strong>¥{totalAdded.toFixed(2)}</Text>
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell index={8} align="center">
|
||||
<Text strong type="danger">¥{totalAmount.toFixed(2)}</Text>
|
||||
</Table.Summary.Cell>
|
||||
</Table.Summary.Row>
|
||||
);
|
||||
@@ -632,7 +681,20 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</AuthButton>
|
||||
) : null
|
||||
) : (
|
||||
<AuthButton auth="purchase.order.bill">
|
||||
<Button
|
||||
type={'primary'}
|
||||
size="small"
|
||||
variant={'solid'}
|
||||
color={'orange'}
|
||||
icon={<AccountBookOutlined />}
|
||||
onClick={() => openBillGenerate(record.id!)}
|
||||
>
|
||||
生成账单
|
||||
</Button>
|
||||
</AuthButton>
|
||||
)
|
||||
];
|
||||
|
||||
const tableProps: XinTableProps<IPurchaseOrder> = {
|
||||
@@ -696,7 +758,7 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
items={[
|
||||
{ key: 'items', label: '商品明细' },
|
||||
{ key: 'stores', label: '门店购买详情' },
|
||||
{ key: 'containers', label: '周转框/托盘' },
|
||||
{ key: 'bills', label: '门店账单' },
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -736,21 +798,21 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
)}
|
||||
</Spin>
|
||||
</>
|
||||
) : detailTab === 'containers' ? (
|
||||
detail.containers.length > 0 ? (
|
||||
<Table<IPurchaseContainerStore>
|
||||
rowKey="store_id"
|
||||
) : detailTab === 'bills' ? (
|
||||
detail.bills.length > 0 ? (
|
||||
<Table<IBill>
|
||||
rowKey="id"
|
||||
size="small"
|
||||
bordered
|
||||
columns={containerColumns}
|
||||
dataSource={detail.containers}
|
||||
columns={billColumns}
|
||||
dataSource={detail.bills}
|
||||
pagination={false}
|
||||
summary={renderContainerSummary}
|
||||
summary={renderBillSummary}
|
||||
/>
|
||||
) : (
|
||||
<Empty
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
description="该采购单暂无门店订单"
|
||||
description={detail.purchase.status === 0 ? '采购单完成后可在列表操作列生成账单' : '该采购单暂未生成账单'}
|
||||
className="py-8!"
|
||||
/>
|
||||
)
|
||||
@@ -1005,99 +1067,119 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{/* 周转框/托盘合并记录下钻:按门店展示全部订单逐笔修改(采购单已完成则只读) */}
|
||||
{/* 生成账单:按门店填写配送费/周转筐/托盘数量(商品金额只读,由订单汇总) */}
|
||||
<Modal
|
||||
title={
|
||||
containerStore
|
||||
? `${containerStore.store_name} · 周转框/托盘`
|
||||
: '周转框/托盘'
|
||||
}
|
||||
open={containerOpen}
|
||||
title={billPrepare ? `生成账单 · ${billPrepare.purchase.purchase_no}` : '生成账单'}
|
||||
open={billOpen}
|
||||
onCancel={() => {
|
||||
setContainerOpen(false);
|
||||
setContainerStore(null);
|
||||
setBillOpen(false);
|
||||
setBillPrepare(null);
|
||||
}}
|
||||
onOk={() => containerForm.submit()}
|
||||
confirmLoading={containerSaving}
|
||||
okText="保存"
|
||||
okButtonProps={{ disabled: detail?.purchase.status !== 0 }}
|
||||
width={760}
|
||||
onOk={() => billForm.submit()}
|
||||
confirmLoading={billSaving}
|
||||
okText="确认生成"
|
||||
okButtonProps={{ disabled: billAllGenerated }}
|
||||
width={960}
|
||||
destroyOnHidden
|
||||
>
|
||||
<div className="py-2 text-gray-500">
|
||||
该门店在本采购单下的全部订单({containerStore?.order_count ?? 0} 笔):
|
||||
{detail?.purchase.status === 0
|
||||
? '逐笔修改后保存,系统将自动重算每笔订单的附加金额与订单总金额。'
|
||||
: '采购单已完成,仅可查看。'}
|
||||
</div>
|
||||
<Form form={containerForm} layout="vertical" onFinish={handleContainerSave}>
|
||||
<Form.List name="orders">
|
||||
{(fields) => (
|
||||
<div className="overflow-hidden rounded border border-gray-200">
|
||||
<div className="flex bg-gray-50 px-4 py-2 text-sm text-gray-500">
|
||||
<div className="flex-1">订单号</div>
|
||||
<div className="w-36 shrink-0 text-center">
|
||||
周转框(单价 ¥{containerStore?.box_price ?? '0.00'})
|
||||
</div>
|
||||
<div className="w-36 shrink-0 text-center">
|
||||
周转托盘(单价 ¥{containerStore?.tray_price ?? '0.00'})
|
||||
</div>
|
||||
</div>
|
||||
{fields.map((field) => {
|
||||
const order = containerStore?.orders[field.name];
|
||||
return (
|
||||
<div key={field.key} className="flex items-center border-t border-gray-100 px-4 py-2">
|
||||
<div className="min-w-0 flex-1 pr-2 text-sm">
|
||||
<Text copyable={{ text: order?.order_no ?? '' }}>{order?.order_no ?? '-'}</Text>
|
||||
</div>
|
||||
<Form.Item name={[field.name, 'order_id']} hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
className="m-0! w-36 shrink-0"
|
||||
name={[field.name, 'box_num']}
|
||||
rules={[{ required: true, message: '请输入周转框数量' }]}
|
||||
>
|
||||
<InputNumber
|
||||
className="w-full"
|
||||
min={0}
|
||||
precision={0}
|
||||
disabled={detail?.purchase.status !== 0}
|
||||
placeholder="周转框数量"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
className="m-0! w-36 shrink-0"
|
||||
name={[field.name, 'tray_num']}
|
||||
rules={[{ required: true, message: '请输入周转托盘数量' }]}
|
||||
>
|
||||
<InputNumber
|
||||
className="w-full"
|
||||
min={0}
|
||||
precision={0}
|
||||
disabled={detail?.purchase.status !== 0}
|
||||
placeholder="周转托盘数量"
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<Spin spinning={billLoading}>
|
||||
{billPrepare && (
|
||||
<>
|
||||
<div className="py-2 text-gray-500">
|
||||
每个门店单独生成一张账单;商品金额由订单汇总不可修改,请填写各门店的配送费与周转筐/托盘数量。
|
||||
{billAllGenerated ? '该采购单已全部生成账单,仅可查看。' : '生成后采购单中的全部订单将关联到对应门店账单。'}
|
||||
</div>
|
||||
)}
|
||||
</Form.List>
|
||||
</Form>
|
||||
<Space orientation="vertical" className="mt-3! w-full rounded bg-gray-50 p-3">
|
||||
<div>
|
||||
<Text type="secondary">周转框合计:</Text>
|
||||
<Text strong>{previewContainerBox}</Text>
|
||||
<Text type="secondary" className="ml-6!">周转托盘合计:</Text>
|
||||
<Text strong>{previewContainerTray}</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text type="secondary">附加金额合计:</Text>
|
||||
<Text strong type="danger">¥{previewContainerAdded.toFixed(2)}</Text>
|
||||
</div>
|
||||
</Space>
|
||||
<Form form={billForm} layout="vertical" onFinish={handleBillSave}>
|
||||
<Form.List name="stores">
|
||||
{(fields) => (
|
||||
<div className="overflow-hidden rounded border border-gray-200">
|
||||
<div className="flex bg-gray-50 px-4 py-2 text-sm text-gray-500">
|
||||
<div className="w-40 shrink-0">门店</div>
|
||||
<div className="w-28 shrink-0 text-center">商品金额</div>
|
||||
<div className="w-32 shrink-0 text-center">配送费(元)</div>
|
||||
<div className="w-32 shrink-0 text-center">周转筐(¥{billPrepare.stores[0]?.box_price ?? '0.00'})</div>
|
||||
<div className="w-32 shrink-0 text-center">周转托盘(¥{billPrepare.stores[0]?.tray_price ?? '0.00'})</div>
|
||||
<div className="w-28 shrink-0 text-center">附加金额</div>
|
||||
<div className="flex-1 text-center">账单总金额</div>
|
||||
</div>
|
||||
{fields.map((field) => {
|
||||
const row = billPrepare.stores[field.name];
|
||||
const billed = row?.bill != null;
|
||||
return (
|
||||
<div key={field.key} className="flex items-center border-t border-gray-100 px-4 py-2">
|
||||
<div className="w-40 shrink-0 pr-2 text-sm">
|
||||
<div>{row?.store_name ?? '-'}</div>
|
||||
<div className="text-xs text-gray-400">
|
||||
{row?.order_count ?? 0} 笔订单
|
||||
{billed && <Tag className="ml-1!" color="success">已生成</Tag>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-28 shrink-0 text-center">
|
||||
<Text strong>¥{row?.product_amount ?? '0.00'}</Text>
|
||||
</div>
|
||||
<Form.Item name={[field.name, 'store_id']} hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
className="m-0! w-32 shrink-0 px-1!"
|
||||
name={[field.name, 'delivery_fee']}
|
||||
rules={[{ required: true, message: '请输入配送费' }]}
|
||||
>
|
||||
<InputNumber
|
||||
className="w-full"
|
||||
min={0}
|
||||
precision={2}
|
||||
disabled={billed}
|
||||
placeholder="配送费"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
className="m-0! w-32 shrink-0 px-1!"
|
||||
name={[field.name, 'box_num']}
|
||||
rules={[{ required: true, message: '请输入周转筐数量' }]}
|
||||
>
|
||||
<InputNumber
|
||||
className="w-full"
|
||||
min={0}
|
||||
precision={0}
|
||||
disabled={billed}
|
||||
placeholder="周转筐数量"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
className="m-0! w-32 shrink-0 px-1!"
|
||||
name={[field.name, 'tray_num']}
|
||||
rules={[{ required: true, message: '请输入周转托盘数量' }]}
|
||||
>
|
||||
<InputNumber
|
||||
className="w-full"
|
||||
min={0}
|
||||
precision={0}
|
||||
disabled={billed}
|
||||
placeholder="周转托盘数量"
|
||||
/>
|
||||
</Form.Item>
|
||||
<div className="w-28 shrink-0 text-center">
|
||||
¥{(billed ? Number(row.bill!.added_amount) : billPreview[field.name]?.added ?? 0).toFixed(2)}
|
||||
</div>
|
||||
<div className="flex-1 text-center">
|
||||
<Text strong type="danger">
|
||||
¥{(billed ? Number(row.bill!.total_amount) : billPreview[field.name]?.total ?? 0).toFixed(2)}
|
||||
</Text>
|
||||
{billed && (
|
||||
<div className="text-xs text-gray-400">{row.bill!.bill_no}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</Form.List>
|
||||
</Form>
|
||||
</>
|
||||
)}
|
||||
</Spin>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user