修复BUG
This commit is contained in:
@@ -25,12 +25,16 @@ export interface PurchaseCellUpdateParams {
|
||||
weight?: number;
|
||||
}
|
||||
|
||||
/** 生成账单:单个门店的配送费/周转筐/托盘数量 */
|
||||
/** 生成账单:单个门店的配送费/周转筐/托盘数量与售后/备注 */
|
||||
export interface BillGenerateStoreParams {
|
||||
store_id: number;
|
||||
delivery_fee: number;
|
||||
box_num: number;
|
||||
tray_num: number;
|
||||
/** 售后说明(文本,仅记录) */
|
||||
after_sale?: string;
|
||||
/** 备注 */
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import type {XinTableProps, XinTableInstance, RequestParams, FormMode} from "./t
|
||||
import SearchForm from "./SearchForm";
|
||||
import XinForm, {type XinFormRef} from "@/components/XinForm";
|
||||
import {type ReactNode, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from "react";
|
||||
import dayjs from "dayjs";
|
||||
import type {FormColumn} from "@/components/XinFormField/FieldRender/typings";
|
||||
import {Delete, List, Create, Update} from "@/api/common/table.ts";
|
||||
import {isArray, isEmpty, omit} from "lodash";
|
||||
@@ -60,6 +61,7 @@ export default function XinTable<T extends Record<string, any> = any>(props: Xin
|
||||
editShow = true,
|
||||
deleteShow = true,
|
||||
searchShow = true,
|
||||
searchDefaultOpen = false,
|
||||
operateShow = true,
|
||||
paginationShow = true,
|
||||
keywordSearchShow = true,
|
||||
@@ -89,7 +91,7 @@ export default function XinTable<T extends Record<string, any> = any>(props: Xin
|
||||
|
||||
const [searchRef] = Form.useForm<T>();
|
||||
const [columnsChecked, setColumnsChecked] = useState<any[]>([]);
|
||||
const [searchRender, setSearchRender] = useState<boolean>(false);
|
||||
const [searchRender, setSearchRender] = useState<boolean>(searchDefaultOpen);
|
||||
|
||||
// 表单模式状态
|
||||
const [formMode, setFormMode] = useState<FormMode>('create');
|
||||
@@ -219,9 +221,17 @@ export default function XinTable<T extends Record<string, any> = any>(props: Xin
|
||||
/** 搜索表单提交 */
|
||||
const handleSearch = async () => {
|
||||
const searchValues: T = searchRef.getFieldsValue();
|
||||
const dateSearch: any = {}
|
||||
// 移除 空值
|
||||
Object.keys(searchValues).forEach((key) => {
|
||||
if (searchValues[key] === '' || searchValues[key] === undefined) {
|
||||
const value = searchValues[key];
|
||||
if (value === '' || value === undefined || value === null) {
|
||||
delete searchValues[key];
|
||||
} else if (dayjs.isDayjs(value)) {
|
||||
dateSearch[key] = value.format('YYYY-MM-DD');
|
||||
delete searchValues[key];
|
||||
} else if (isArray(value) && value.length > 0 && value.every((item) => dayjs.isDayjs(item))) {
|
||||
dateSearch[key] = value.map((item) => item.format('YYYY-MM-DD'));
|
||||
delete searchValues[key];
|
||||
}
|
||||
});
|
||||
@@ -229,6 +239,7 @@ export default function XinTable<T extends Record<string, any> = any>(props: Xin
|
||||
page: 1,
|
||||
...requestParams,
|
||||
...searchValues,
|
||||
...dateSearch,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -256,9 +267,11 @@ export default function XinTable<T extends Record<string, any> = any>(props: Xin
|
||||
.map(column => omit(column, ['hideInTable', 'hideInForm', 'hideInSearch', 'search']));
|
||||
}, [columns]);
|
||||
|
||||
/** 初始化列设置树数据 */
|
||||
/** 初始化列设置树数据(defaultHiddenInTable 的列默认不勾选,可在列设置中手动开启) */
|
||||
useEffect(() => {
|
||||
const dataIndexList = defaultTableColumns.map(item => item.dataIndex!);
|
||||
const dataIndexList = defaultTableColumns
|
||||
.filter(item => !item.defaultHiddenInTable)
|
||||
.map(item => item.dataIndex!);
|
||||
setColumnsChecked(dataIndexList);
|
||||
}, [defaultTableColumns]);
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ export type XinTableColumn<T = any> = Omit<TableColumnType<T>, 'dataIndex'> & {
|
||||
hideInSearch?: boolean;
|
||||
hideInForm?: boolean;
|
||||
hideInTable?: boolean;
|
||||
/** 列设置中默认不勾选(表格默认隐藏,用户可在列设置中手动开启) */
|
||||
defaultHiddenInTable?: boolean;
|
||||
hideInUpdate?: boolean;
|
||||
hideInCreate?: boolean;
|
||||
search?: FormColumn<T>;
|
||||
@@ -127,6 +129,8 @@ export interface XinTableProps<T = any> extends Omit<TableProps<T>, 'columns' |
|
||||
deleteShow?: boolean | ((record: T) => boolean);
|
||||
/** 搜索栏显示 */
|
||||
searchShow?: boolean;
|
||||
/** 搜索栏默认展开 */
|
||||
searchDefaultOpen?: boolean;
|
||||
/** 表格操作列显示 */
|
||||
operateShow?: boolean;
|
||||
/** 分页显示 */
|
||||
|
||||
@@ -29,6 +29,8 @@ export default interface IProduct {
|
||||
spec?: string;
|
||||
/** 计价单位 */
|
||||
unit?: string;
|
||||
/** 单价单位(如:元/斤、元/箱) */
|
||||
price_unit?: string;
|
||||
/** 封面 */
|
||||
image_ids?: string;
|
||||
images_arr?: ISysFileInfo[];
|
||||
|
||||
@@ -25,7 +25,7 @@ export interface IPurchaseDetailRow {
|
||||
quantity: number;
|
||||
/** 合计实际称重 */
|
||||
weight: number;
|
||||
/** 合计订货金额(Σ明细 amount;参考零售价 = amount÷quantity÷包规数值) */
|
||||
/** 合计订货金额(Σ明细 amount;单价 = amount÷quantity÷包规数值) */
|
||||
amount: string;
|
||||
cells: Record<number, number>;
|
||||
}
|
||||
@@ -122,6 +122,8 @@ export interface IBill {
|
||||
paid_operator_id?: number;
|
||||
order_count?: number;
|
||||
remark?: string;
|
||||
/** 售后说明(生成账单时按门店填写) */
|
||||
after_sale?: string;
|
||||
created_at?: string;
|
||||
/** 门店账单列表/详情接口附带 */
|
||||
store?: { id: number; name: string; address?: string; contact?: string; phone?: string } | null;
|
||||
|
||||
@@ -185,6 +185,7 @@ const StoreOrderPage: React.FC = () => {
|
||||
dataIndex: 'items',
|
||||
hideInForm: true,
|
||||
hideInSearch: true,
|
||||
defaultHiddenInTable: true,
|
||||
width: 550,
|
||||
render: (_, record) => {
|
||||
return (
|
||||
@@ -212,7 +213,7 @@ const StoreOrderPage: React.FC = () => {
|
||||
hideInForm: true,
|
||||
hideInTable: true,
|
||||
fieldProps: {
|
||||
options: stores.map((s) => ({ label: s.name, value: s.id })),
|
||||
options: [{ label: '全部', value: '' }, ...stores.map((s) => ({ label: s.name, value: s.id }))],
|
||||
showSearch: true,
|
||||
optionFilterProp: 'label',
|
||||
},
|
||||
@@ -243,6 +244,15 @@ const StoreOrderPage: React.FC = () => {
|
||||
hideInForm: true,
|
||||
hideInTable: true,
|
||||
align: 'center',
|
||||
fieldProps: {
|
||||
presets: [
|
||||
{ label: '今天', value: [dayjs(), dayjs()] },
|
||||
{ label: '昨天', value: [dayjs().subtract(1, 'day'), dayjs().subtract(1, 'day')] },
|
||||
{ label: '前天', value: [dayjs().subtract(2, 'day'), dayjs().subtract(2, 'day')] },
|
||||
{ label: '本周', value: [dayjs().startOf('week'), dayjs().endOf('week')] },
|
||||
{ label: '本月', value: [dayjs().startOf('month'), dayjs().endOf('month')] },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '附加信息',
|
||||
@@ -281,17 +291,32 @@ const StoreOrderPage: React.FC = () => {
|
||||
align: 'center',
|
||||
hideInTable: true,
|
||||
fieldProps: {
|
||||
options: Object.entries(STORE_ORDER_STATUS_MAP).map(([value, item]) => ({
|
||||
value: Number(value),
|
||||
label: item.text,
|
||||
})),
|
||||
options: [
|
||||
{ label: '全部', value: '' },
|
||||
...Object.entries(STORE_ORDER_STATUS_MAP).map(([value, item]) => ({
|
||||
value: Number(value),
|
||||
label: item.text,
|
||||
})),
|
||||
],
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '采购单号',
|
||||
dataIndex: 'purchase_no',
|
||||
valueType: 'text',
|
||||
hideInForm: true,
|
||||
hideInTable: true,
|
||||
fieldProps: {
|
||||
placeholder: '采购单号',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '采购单',
|
||||
dataIndex: 'purchase_id',
|
||||
valueType: 'digit',
|
||||
hideInForm: true,
|
||||
hideInSearch: true,
|
||||
render: (_, record) => record.purchase ? (
|
||||
<Space orientation={'vertical'}>
|
||||
<div>
|
||||
@@ -393,6 +418,7 @@ const StoreOrderPage: React.FC = () => {
|
||||
tableRef,
|
||||
operateShow: false,
|
||||
formProps: false,
|
||||
searchDefaultOpen: true,
|
||||
rowSelection: {
|
||||
selectedRowKeys,
|
||||
onChange: (keys) => setSelectedRowKeys(keys),
|
||||
|
||||
@@ -387,6 +387,15 @@ const ProductGoodsPage: React.FC = () => {
|
||||
initialValue: '斤',
|
||||
align: "center",
|
||||
},
|
||||
{
|
||||
title: '单价单位',
|
||||
dataIndex: 'price_unit',
|
||||
valueType: 'text',
|
||||
hideInTable: true,
|
||||
hideInSearch: true,
|
||||
initialValue: '元/斤',
|
||||
fieldProps: { placeholder: '如:元/斤、元/箱', maxLength: 20 },
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sort',
|
||||
|
||||
@@ -127,6 +127,8 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
|
||||
// 供应商采购明细页签
|
||||
const [supplierId, setSupplierId] = useState<number>(0);
|
||||
/** 市场筛选('' = 全部市场) */
|
||||
const [marketFilter, setMarketFilter] = useState<string>('');
|
||||
|
||||
// 导出弹窗:商品明细(供应商筛选)/ 门店购买详情 / 供应商采购明细
|
||||
const [itemExportOpen, setItemExportOpen] = useState(false);
|
||||
@@ -147,11 +149,23 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
return Array.from(map, ([id, name]) => ({ id, name }));
|
||||
}, [detail?.items]);
|
||||
|
||||
/** 供应商采购明细行:按供应商过滤矩阵行,金额=数量×成本价(成本口径) */
|
||||
/** 当前供应商在采购单内出现的市场列表(市场筛选下拉选项) */
|
||||
const supplierMarkets = useMemo(() => {
|
||||
const markets = new Set<string>();
|
||||
(detail?.items ?? []).forEach((row) => {
|
||||
if (row.supplier_id === supplierId && row.market) {
|
||||
markets.add(row.market);
|
||||
}
|
||||
});
|
||||
return Array.from(markets);
|
||||
}, [detail?.items, supplierId]);
|
||||
|
||||
/** 供应商采购明细行:按供应商+市场过滤矩阵行,金额=数量×成本价(成本口径) */
|
||||
const supplierItems = useMemo<IPurchaseSupplierItem[]>(() => {
|
||||
if (!detail || supplierId <= 0) return [];
|
||||
return detail.items
|
||||
.filter((row) => row.supplier_id === supplierId)
|
||||
.filter((row) => marketFilter === '' || (row.market ?? '') === marketFilter)
|
||||
.map((row) => ({
|
||||
product_id: row.product_id,
|
||||
product_name: row.product_name,
|
||||
@@ -163,7 +177,7 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
weight: String(row.weight),
|
||||
amount: (row.quantity * Number(row.cost_price)).toFixed(2),
|
||||
}));
|
||||
}, [detail, supplierId]);
|
||||
}, [detail, supplierId, marketFilter]);
|
||||
|
||||
// 生成账单(已完成采购单按门店生成:填写配送费/周转筐/托盘数量,金额只读)
|
||||
const [billOpen, setBillOpen] = useState(false);
|
||||
@@ -426,6 +440,8 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
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,
|
||||
after_sale: row.bill?.after_sale ?? '',
|
||||
remark: row.bill?.remark ?? '',
|
||||
})),
|
||||
});
|
||||
} finally {
|
||||
@@ -464,7 +480,7 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 明细矩阵列:品名/供应商/参考零售价/包规/单位/成本 + 每门店一列(数量)+ 合计 + 操作 */
|
||||
/** 明细矩阵列:品名/供应商/单价/包规/单位/成本 + 每门店一列(数量)+ 合计 + 操作 */
|
||||
const buildItemColumns = (): TableProps<IPurchaseDetailRow>['columns'] => {
|
||||
const fixed: NonNullable<TableProps<IPurchaseDetailRow>['columns']> = [
|
||||
{ title: '品名', dataIndex: 'product_name', width: 120, align: 'center' },
|
||||
@@ -477,7 +493,7 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
},
|
||||
{ title: '市场', dataIndex: 'market', width: 90, align: 'center', render: (v) => v || '-' },
|
||||
{
|
||||
title: '参考零售价',
|
||||
title: '单价',
|
||||
key: 'retail_price',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
@@ -578,12 +594,12 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
/** 门店购买详情列:商品/市场/购买参考零售价(单价÷包规)/包规/单位/单价/数量/预计金额 + 操作 */
|
||||
/** 门店购买详情列:商品/市场/购买单价(单价÷包规)/包规/单位/单价/数量/预计金额 + 操作 */
|
||||
const storeColumns: TableProps<IPurchaseStoreItem>['columns'] = [
|
||||
{ title: '商品', dataIndex: 'product_name', width: 160, align: 'center' },
|
||||
{ title: '市场', dataIndex: 'market', width: 90, align: 'center', render: (v) => v || '-' },
|
||||
{
|
||||
title: '参考零售价',
|
||||
title: '单价',
|
||||
key: 'retail_price',
|
||||
width: 130,
|
||||
align: 'center',
|
||||
@@ -1082,13 +1098,26 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
<Text>选择供应商:</Text>
|
||||
<Select
|
||||
value={supplierId || undefined}
|
||||
onChange={(value) => setSupplierId(value)}
|
||||
onChange={(value) => {
|
||||
setSupplierId(value);
|
||||
setMarketFilter('');
|
||||
}}
|
||||
placeholder="选择供应商"
|
||||
className="w-60!"
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
options={purchaseSuppliers.map((s) => ({ value: s.id, label: s.name }))}
|
||||
/>
|
||||
<Text>市场:</Text>
|
||||
<Select
|
||||
value={marketFilter}
|
||||
onChange={setMarketFilter}
|
||||
className="w-40!"
|
||||
options={[
|
||||
{ value: '', label: '全部市场' },
|
||||
...supplierMarkets.map((m) => ({ value: m, label: m })),
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<AuthButton auth="purchase.order.export">
|
||||
<Button
|
||||
@@ -1404,14 +1433,14 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
confirmLoading={billSaving}
|
||||
okText="确认生成"
|
||||
okButtonProps={{ disabled: billAllGenerated }}
|
||||
width={960}
|
||||
width={1280}
|
||||
destroyOnHidden
|
||||
>
|
||||
<Spin spinning={billLoading}>
|
||||
{billPrepare && (
|
||||
<>
|
||||
<div className="py-2 text-gray-500">
|
||||
每个门店单独生成一张账单;商品金额由订单汇总不可修改,请填写各门店的配送费与周转筐/托盘数量(正数=压筐附加金额,负数=回筐抵扣金额)。
|
||||
每个门店单独生成一张账单;商品金额由订单汇总不可修改,请填写各门店的配送费、周转筐/托盘数量(正数=压筐附加金额,负数=回筐抵扣金额)与售后/备注(可选,随账单存档)。
|
||||
{billAllGenerated ? '该采购单已全部生成账单,仅可查看。' : '生成后采购单中的全部订单将关联到对应门店账单。'}
|
||||
</div>
|
||||
<Form form={billForm} layout="vertical" onFinish={handleBillSave}>
|
||||
@@ -1424,6 +1453,8 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
<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-36 shrink-0 text-center">售后</div>
|
||||
<div className="w-36 shrink-0 text-center">备注</div>
|
||||
<div className="w-28 shrink-0 text-center">附加金额</div>
|
||||
<div className="flex-1 text-center">账单总金额</div>
|
||||
</div>
|
||||
@@ -1482,6 +1513,26 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
placeholder="正压负回"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
className="m-0! w-36 shrink-0 px-1!"
|
||||
name={[field.name, 'after_sale']}
|
||||
>
|
||||
<Input
|
||||
maxLength={255}
|
||||
disabled={billed}
|
||||
placeholder="售后说明(可选)"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
className="m-0! w-36 shrink-0 px-1!"
|
||||
name={[field.name, 'remark']}
|
||||
>
|
||||
<Input
|
||||
maxLength={255}
|
||||
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>
|
||||
@@ -1638,7 +1689,7 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
{/* 供应商采购明细导出:当前供应商 / 全部供应商(多工作表) */}
|
||||
{/* 供应商采购明细导出:当前供应商 / 全部供应商(按「供应商×市场」拆分工作表) */}
|
||||
<Modal
|
||||
title="导出供应商采购明细"
|
||||
open={supplierExportOpen}
|
||||
@@ -1655,13 +1706,16 @@ const PurchaseOrderPage: React.FC = () => {
|
||||
okText="导出"
|
||||
destroyOnHidden
|
||||
>
|
||||
<div className="py-2 text-gray-500">
|
||||
导出 XLSX 按「供应商 × 市场」拆分工作表,列序:品名、汇总、市场、各门店明细,有数据的单元格突出显示。
|
||||
</div>
|
||||
<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,每供应商一个工作表)' },
|
||||
{ value: 'all', label: '全部供应商(合并为一个 XLSX,每供应商×市场一个工作表)' },
|
||||
]}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
@@ -403,6 +403,9 @@ const BillPage: React.FC = () => {
|
||||
{detail.bill.pay_remark ? (
|
||||
<Descriptions.Item label="付款备注" span={3}>{detail.bill.pay_remark}</Descriptions.Item>
|
||||
) : null}
|
||||
{detail.bill.after_sale ? (
|
||||
<Descriptions.Item label="售后" span={3}>{detail.bill.after_sale}</Descriptions.Item>
|
||||
) : null}
|
||||
{detail.bill.remark ? (
|
||||
<Descriptions.Item label="备注" span={3}>{detail.bill.remark}</Descriptions.Item>
|
||||
) : null}
|
||||
|
||||
Reference in New Issue
Block a user