ERPTurbo_Client/packages/app-client/src/components/purchase/section/PackageInfoSection.tsx
shenyifei 881685a653 feat(purchase): 优化采购模块费用与箱型信息处理逻辑
- 在 PriceEditor 组件中增加 negative 属性以支持负数显示
- 优化 Step1Form 表单模块渲染结构,提升可读性
- 修正 CostCard 中价格与数量的展示顺序
- 在 CostCreate 组件中过滤掉“空箱费”和“纸箱费”的选项
- 增加 getTitle 方法动态设置费用类型的标题
- 重构 OrderOption 中费用保存的筛选逻辑
- 强化 OrderCostItem 中字段校验及数据初始化处理
- 完善 BasicInfoSection 的发货日期选择器功能
- 调整 CostDifferenceSection 中分红金额相关文案与计算方式
- 简化 DeliveryFormSection 数据初始化流程
- 移除 EmptyBoxInfoSection 和 PackageInfoSection 中冗余的成本单价和箱重编辑功能
- 在 audit 页面中优化费用项目的初始化加载逻辑并确保计提费正确附加到订单中
2025-12-11 17:14:27 +08:00

501 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { formatCurrency, validatePrice } from "@/utils";
import { useEffect, useState } from "react";
import { Button, Input, Popup, SafeArea, Table } from "@nutui/nutui-react-taro";
import { Icon } from "@/components";
import { View } from "@tarojs/components";
export default function PackageInfoSection(props: {
purchaseOrderVO: BusinessAPI.PurchaseOrderVO;
onChange?: (purchaseOrderVO: BusinessAPI.PurchaseOrderVO) => void;
readOnly?: boolean;
}) {
const { purchaseOrderVO, onChange, readOnly } = props;
const defaultColumns = [
{
title: "纸箱型号",
key: "boxProductName",
fixed: "left",
},
{
title: "个数",
key: "boxProductCount",
},
{
title: "销售单价(元/斤)",
key: "boxSalePrice",
render: (
value: BusinessAPI.OrderPackage & {
orderPackageId?: string;
isTotalRow?: boolean;
},
) => {
// 合计行不显示编辑按钮
if (value.isTotalRow) {
return formatCurrency(value.boxSalePrice as number);
}
return (
<View
className="flex w-full items-center justify-between"
onClick={(e) => {
if (!readOnly) {
e.stopPropagation();
// 设置临时编辑值为当前值
setTempEditValues((prev) => ({
...prev,
[value.orderPackageId || ""]:
editValues[value.orderPackageId || ""],
}));
setVisiblePopup((prev) => ({
...prev,
[value.orderPackageId || ""]: true,
}));
}
}}
>
<View className={!readOnly ? "cursor-pointer underline" : ""}>
{formatCurrency(value.boxSalePrice as number)}
</View>
{!readOnly && (
<View className="-m-2 ml-2 flex items-center justify-center p-2">
<Icon name={"pen-to-square"} size={16} color={"#1a73e8"} />
</View>
)}
</View>
);
},
},
{
title: "销售金额(元)",
key: "boxSalePayment",
render: (
value: BusinessAPI.OrderPackage & {
boxProductCount: number;
isTotalRow?: boolean;
},
) =>
formatCurrency(
Number((value?.boxSalePrice || 0) * value.boxProductCount) as number,
),
},
{
title: "箱重(斤)",
key: "boxProductWeight",
render: (
value: BusinessAPI.OrderPackage & {
orderPackageId?: string;
isTotalRow?: boolean;
},
) => formatCurrency(value.boxProductWeight),
},
{
title: "品牌",
key: "boxBrandName",
},
];
const [columns, setColumns] = useState<any[]>(defaultColumns);
useEffect(() => {
if (!readOnly) {
setColumns([...defaultColumns]);
}
}, [readOnly]);
// 弹窗可见状态
const [visiblePopup, setVisiblePopup] = useState<{ [key: string]: boolean }>(
{},
);
// 编辑值的状态
const [editValues, setEditValues] = useState<{
[key: string]: {
boxSalePrice?: number;
};
}>({});
// 临时编辑值的状态(用于在保存前暂存编辑的值)
const [tempEditValues, setTempEditValues] = useState<{
[key: string]: {
boxSalePrice?: number;
};
}>({});
// 初始化编辑值
const initEditValues = (pkgId: string, boxSalePrice?: number) => {
const updates: {
editValuesUpdate?: {
boxSalePrice?: number;
};
tempEditValuesUpdate?: {
boxSalePrice?: number;
};
} = {};
if (!editValues[pkgId]) {
updates.editValuesUpdate = {
boxSalePrice,
};
}
// 同时初始化临时编辑值
if (!tempEditValues[pkgId]) {
updates.tempEditValuesUpdate = {
boxSalePrice,
};
}
return updates;
};
// 将所有包装信息合并并按品牌、型号、规格分组
const groupedPackageData = purchaseOrderVO.orderSupplierList?.reduce(
(acc, supplier) => {
supplier.orderPackageList
?.filter((pkg) => pkg.boxType === "USED")
?.forEach((pkg) => {
// 生成分组键
const key = `${pkg.boxBrandName}-${pkg.boxProductName}-${pkg.boxSpecId}`;
// 转换规格字段
const boxSpecId = pkg.boxSpecId;
const boxSpecName = pkg.boxSpecName;
if (!acc[key]) {
acc[key] = {
...pkg,
boxSpecId,
boxSpecName,
boxProductCount: 0,
};
}
// 累加数量
acc[key].boxProductCount += pkg.boxCount || 0;
});
return acc;
},
{} as Record<string, any>,
);
// 转换为数组格式
const packageData = Object.values(groupedPackageData || {});
// 计算合计数据
const calculateTotals = () => {
if (!packageData || packageData.length === 0) {
return {};
}
// 计算各项合计
let totalBoxProductCount = 0;
let totalBoxSalePayment = 0;
let totalBoxProductWeight = 0;
packageData.forEach((pkg: any) => {
totalBoxProductCount += pkg.boxProductCount || 0;
totalBoxSalePayment +=
Number((pkg?.boxSalePrice || 0) * pkg.boxProductCount) || 0;
totalBoxProductWeight +=
Number((pkg?.boxProductWeight || 0) * pkg.boxProductCount) || 0;
});
return {
boxProductName: "合计",
boxProductCount: totalBoxProductCount,
boxSalePayment: totalBoxSalePayment,
boxProductWeight: totalBoxProductWeight,
isTotalRow: true, // 标记这是合计行
};
};
const totalsData = calculateTotals();
// 初始化所有包装项的编辑值
useEffect(() => {
const newEditValues = { ...editValues };
const newTempEditValues = { ...tempEditValues };
let hasEditValuesChanged = false;
let hasTempEditValuesChanged = false;
packageData.forEach((pkg: BusinessAPI.OrderPackage) => {
const pkgId = pkg.orderPackageId || "";
const updates = initEditValues(pkgId, pkg.boxSalePrice);
if (updates.editValuesUpdate) {
newEditValues[pkgId] = updates.editValuesUpdate;
hasEditValuesChanged = true;
}
if (updates.tempEditValuesUpdate) {
newTempEditValues[pkgId] = updates.tempEditValuesUpdate;
hasTempEditValuesChanged = true;
}
});
if (hasEditValuesChanged) {
setEditValues(newEditValues);
}
if (hasTempEditValuesChanged) {
setTempEditValues(newTempEditValues);
}
}, [packageData]);
// 当editValues发生变化时更新purchaseOrderVO
useEffect(() => {
// 只有当onChange存在时才更新
if (onChange) {
// 创建新的purchaseOrderVO对象
const newPurchaseOrderVO = { ...purchaseOrderVO };
// 更新供应商列表中的包装信息
if (newPurchaseOrderVO.orderSupplierList) {
newPurchaseOrderVO.orderSupplierList =
newPurchaseOrderVO.orderSupplierList.map((supplier) => {
if (supplier.orderPackageList) {
const newOrderPackageList = supplier.orderPackageList.map(
(pkg) => {
const editValue = editValues[pkg.orderPackageId || ""];
if (editValue) {
return {
...pkg,
boxSalePrice:
editValue.boxSalePrice !== undefined
? editValue.boxSalePrice
: pkg.boxSalePrice,
};
}
return pkg;
},
);
return {
...supplier,
orderPackageList: newOrderPackageList,
};
}
return supplier;
});
}
// 调用onChange回调
onChange(newPurchaseOrderVO);
}
}, [editValues]);
// 自定义合计行渲染
const renderTableWithTotals = () => {
// 创建包含合计行的数据
const dataWithTotals = [...packageData];
if (Object.keys(totalsData).length > 0) {
dataWithTotals.push(totalsData);
}
// 自定义列配置,对合计行特殊处理
const columnsWithTotalsRender = columns.map((column) => {
if (column.key === "boxProductName") {
// 品牌列显示"合计"
return {
...column,
render: (rowData: any) => {
if (rowData.isTotalRow) {
return (
<span style={{ fontWeight: "bold" }}>
{rowData.boxProductName}
</span>
);
}
return rowData.boxProductName;
},
};
} else if (column.key === "boxProductCount") {
// 个数列显示合计
return {
...column,
render: (rowData: any) => {
if (rowData.isTotalRow) {
return (
<span style={{ fontWeight: "bold" }}>
{rowData.boxProductCount}
</span>
);
}
return rowData.boxProductCount;
},
};
} else if (column.key === "boxSalePrice") {
// 销售单价列合计行处理
return {
...column,
render: (rowData: any) => {
if (rowData.isTotalRow) {
return (
<span style={{ fontWeight: "bold" }}>
{formatCurrency(rowData.boxSalePrice as number)}
</span>
);
}
return column.render(rowData, rowData);
},
};
} else if (column.key === "boxSalePayment") {
// 销售金额列显示合计
return {
...column,
render: (rowData: any) => {
if (rowData.isTotalRow) {
return (
<span style={{ fontWeight: "bold" }}>
{formatCurrency(rowData.boxSalePayment)}
</span>
);
}
return formatCurrency(
Number(
(rowData?.boxSalePrice || 0) * rowData.boxProductCount,
) as number,
);
},
};
} else if (column.key === "boxProductWeight") {
// 重量列合计行处理
return {
...column,
render: (rowData: any) => {
if (rowData.isTotalRow) {
return (
<span style={{ fontWeight: "bold" }}>
{formatCurrency(rowData.boxProductWeight)}
</span>
);
}
return column.render(rowData, rowData);
},
};
}
// 其他列保持原有render函数或者默认显示
return column;
});
return (
<Table
className={"table-sum"}
columns={columnsWithTotalsRender}
data={dataWithTotals}
striped
/>
);
};
return (
<>
{renderTableWithTotals()}
{/* 纸箱价格编辑弹窗 */}
{packageData.map((pkg: BusinessAPI.OrderPackage) => (
<Popup
duration={150}
style={{
minHeight: "auto",
}}
key={pkg.orderPackageId}
visible={visiblePopup[pkg.orderPackageId || ""]}
position="bottom"
title="编辑纸箱信息"
onClose={() =>
setVisiblePopup((prev) => ({
...prev,
[pkg.orderPackageId || ""]: false,
}))
}
onOverlayClick={() =>
setVisiblePopup((prev) => ({
...prev,
[pkg.orderPackageId || ""]: false,
}))
}
lockScroll
>
<View className="flex flex-col gap-3 p-2.5">
<View className="text-neutral-darkest flex flex-row text-sm font-medium">
<Icon name="money-bill" size={16} className="mr-1" />
</View>
<View className="border-neutral-base flex flex-row items-center rounded-md border border-solid">
<Input
className="placeholder:text-neutral-dark"
placeholder="请输入销售单价"
type="digit"
value={
tempEditValues[
pkg.orderPackageId || ""
]?.boxSalePrice?.toString() || ""
}
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setTempEditValues((prev) => ({
...prev,
[pkg.orderPackageId || ""]: {
...prev[pkg.orderPackageId || ""],
boxSalePrice: numValue as number,
},
}));
}
}}
/>
<View className="mr-2"></View>
</View>
</View>
<View className="flex w-full flex-col bg-white">
<View className="flex flex-row gap-2 p-3">
<View className="flex-1">
<Button
size="large"
block
type="default"
onClick={() =>
setVisiblePopup((prev) => ({
...prev,
[pkg.orderPackageId || ""]: false,
}))
}
>
</Button>
</View>
<View className="flex-1">
<Button
size="large"
block
type="primary"
onClick={() => {
// 保存时才更新editValues状态
setEditValues((prev) => ({
...prev,
[pkg.orderPackageId || ""]: {
...tempEditValues[pkg.orderPackageId || ""],
},
}));
// 关闭弹窗
setVisiblePopup((prev) => ({
...prev,
[pkg.orderPackageId || ""]: false,
}));
}}
>
</Button>
</View>
</View>
</View>
<SafeArea position="bottom" />
</Popup>
))}
</>
);
}