refactor(purchase): 重构纸箱规格相关逻辑

- 将 BoxCategory 相关类型和逻辑替换为 BoxSpec
- 更新 OrderPackage 组件中的分类展示和数量统计逻辑
- 修改采购预览页面的规格字段展示方式
- 调整配送单据中的纸箱规格数据结构
- 新增 BoxSpec 相关服务接口和类型定义
- 移除旧的 calculateSupplierWeights 工具函数
- 更新组件内部变量命名以匹配新规范
- 修正空箱信息区域组件的属性传递问题
This commit is contained in:
shenyifei 2025-11-18 00:15:12 +08:00
parent 323fe4c83d
commit 5eb4e0f4bb
15 changed files with 766 additions and 620 deletions

View File

@ -12,7 +12,7 @@ import {
import classNames from "classnames";
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
import { business } from "@/services";
import { BoxBrand, BoxCategory, BoxProduct, SupplierVO } from "@/types/typings";
import { BoxBrand, BoxProduct, BoxSpec, SupplierVO } from "@/types/typings";
import { generateShortId } from "@/utils/generateShortId";
import {
convertBoxBrandToOrderPackages,
@ -183,20 +183,15 @@ export default forwardRef<OrderPackageRef, IOrderPackageProps>(
)
.map((boxBrand) => {
// 将产品按规格分类
const boxCategoryList: BoxCategory[] = [
{
id: generateShortId(),
boxCategoryId: "FOUR_GRAIN",
boxCategoryName: "4粒装",
boxProductList: [],
},
{
id: generateShortId(),
boxCategoryId: "TWO_GRAIN",
boxCategoryName: "2粒装",
boxProductList: [],
},
];
const boxSpecList: BoxSpec[] =
boxBrand.boxSpecVOList?.map((item) => {
return {
id: generateShortId(),
boxSpecId: item.specId,
boxSpecName: item.name,
boxProductList: [],
};
}) || [];
const boxProductList = boxBrand.boxProductVOList?.map(
(boxProductVO) => {
const boxProduct: BoxProduct = {
@ -204,7 +199,8 @@ export default forwardRef<OrderPackageRef, IOrderPackageProps>(
boxProductId: boxProductVO.productId,
boxProductName: boxProductVO.name,
boxProductWeight: boxProductVO.weight,
boxCategoryId: boxProductVO.specType,
boxSpecId: boxProductVO.specId,
boxSpecName: boxProductVO.specName,
boxCostPrice: boxProductVO.costPrice,
boxSalePrice: boxProductVO.salePrice,
boxBrandId: boxBrand.brandId,
@ -222,25 +218,24 @@ export default forwardRef<OrderPackageRef, IOrderPackageProps>(
boxBrandName: boxBrand.name,
boxBrandImage: boxBrand.image,
boxBrandType: boxBrand.type,
boxCategoryList:
boxCategoryList
.map((boxCategory) => {
boxSpecList:
boxSpecList
.map((boxSpec) => {
return {
id: generateShortId(),
boxCategoryId: boxCategory.boxCategoryId,
boxCategoryName: boxCategory.boxCategoryName,
boxSpecId: boxSpec.boxSpecId,
boxSpecName: boxSpec.boxSpecName,
boxProductList:
boxProductList?.filter(
(boxProduct) =>
boxProduct.boxCategoryId ===
boxCategory.boxCategoryId,
boxProduct.boxSpecId === boxSpec.boxSpecId,
) || [],
};
})
.filter(
(boxCategory) =>
boxCategory.boxProductList &&
boxCategory.boxProductList.length > 0,
(boxSpec) =>
boxSpec.boxProductList &&
boxSpec.boxProductList.length > 0,
) || [],
};
}) || [];
@ -311,8 +306,8 @@ export default forwardRef<OrderPackageRef, IOrderPackageProps>(
setSelectedBrand(brand);
// 初始化所有产品的数量为0
const initialCounts = new Map<string, number>();
brand.boxCategoryList?.forEach((category) => {
category.boxProductList.forEach((product) => {
brand.boxSpecList?.forEach((boxSpec) => {
boxSpec.boxProductList.forEach((product) => {
initialCounts.set(product.id, 0);
});
});
@ -331,17 +326,15 @@ export default forwardRef<OrderPackageRef, IOrderPackageProps>(
if (selectedBrand) {
const updatedBrand = { ...selectedBrand };
updatedBrand.boxCategoryList = updatedBrand.boxCategoryList?.map(
(category) => {
const updatedProducts = category.boxProductList.map((product) => {
if (product.id === productId) {
return { ...product, boxCount: count };
}
return product;
});
return { ...category, boxProductList: updatedProducts };
},
);
updatedBrand.boxSpecList = updatedBrand.boxSpecList?.map((boxSpec) => {
const updatedProducts = boxSpec.boxProductList.map((product) => {
if (product.id === productId) {
return { ...product, boxCount: count };
}
return product;
});
return { ...boxSpec, boxProductList: updatedProducts };
});
setSelectedBrand(updatedBrand);
}
};
@ -361,7 +354,7 @@ export default forwardRef<OrderPackageRef, IOrderPackageProps>(
// 过滤掉数量为0的项目
const filteredOrderPackages = newOrderPackages.filter((pkg) => {
// 从productCounts中获取对应产品的数量
const product = selectedBrand.boxCategoryList
const product = selectedBrand.boxSpecList
?.flatMap((c) => c.boxProductList)
.find((p) => p.id === pkg.orderPackageId);
const count = product ? productCounts.get(product.id) || 0 : 0;
@ -371,7 +364,7 @@ export default forwardRef<OrderPackageRef, IOrderPackageProps>(
// 更新数量信息
const updatedOrderPackages = filteredOrderPackages.map((pkg) => {
// 从productCounts中获取对应产品的数量
const product = selectedBrand.boxCategoryList
const product = selectedBrand.boxSpecList
?.flatMap((c) => c.boxProductList)
.find((p) => p.id === pkg.orderPackageId);
const count = product ? productCounts.get(product.id) || 0 : 0;
@ -453,17 +446,17 @@ export default forwardRef<OrderPackageRef, IOrderPackageProps>(
? {
...fullBrandInfo,
boxType: item.boxType, // 保持boxType
boxCategoryList: fullBrandInfo.boxCategoryList?.map((category) => {
boxSpecList: fullBrandInfo.boxSpecList?.map((boxSpec) => {
// 找到对应的已存在分类数据
const existingCategory = brandToEdit?.boxCategoryList?.find(
(cat) => cat.boxCategoryId === category.boxCategoryId,
const existingSpec = brandToEdit?.boxSpecList?.find(
(spec) => spec.boxSpecId === boxSpec.boxSpecId,
);
return {
...category,
boxProductList: category.boxProductList.map((product) => {
...boxSpec,
boxProductList: boxSpec.boxProductList.map((product) => {
// 找到对应的产品数据
const existingProduct = existingCategory?.boxProductList.find(
const existingProduct = existingSpec?.boxProductList.find(
(prod) => prod.boxProductId === product.boxProductId,
);
@ -486,7 +479,7 @@ export default forwardRef<OrderPackageRef, IOrderPackageProps>(
// 更新编辑项中的数量
const updateEditingItemCount = (
categoryIndex: number,
specIndex: number,
detailIndex: number,
count: number,
) => {
@ -501,25 +494,23 @@ export default forwardRef<OrderPackageRef, IOrderPackageProps>(
const updatedItem = { ...editingItem };
// 确保boxCategoryList存在
if (!updatedItem.boxCategoryList) {
updatedItem.boxCategoryList = fullBrandInfo.boxCategoryList.map(
(category) => ({
...category,
boxProductList: category.boxProductList.map((product) => ({
...product,
boxCount: 0,
})),
}),
);
// 确保boxSpecList存在
if (!updatedItem.boxSpecList) {
updatedItem.boxSpecList = fullBrandInfo.boxSpecList.map((boxSpec) => ({
...boxSpec,
boxProductList: boxSpec.boxProductList.map((product) => ({
...product,
boxCount: 0,
})),
}));
}
// 确保category存在
if (!updatedItem.boxCategoryList[categoryIndex]) {
updatedItem.boxCategoryList[categoryIndex] = {
...fullBrandInfo.boxCategoryList[categoryIndex],
boxProductList: fullBrandInfo.boxCategoryList[
categoryIndex
// 确保spec存在
if (!updatedItem.boxSpecList[specIndex]) {
updatedItem.boxSpecList[specIndex] = {
...fullBrandInfo.boxSpecList[specIndex],
boxProductList: fullBrandInfo.boxSpecList[
specIndex
].boxProductList.map((product) => ({
...product,
boxCount: 0,
@ -528,26 +519,24 @@ export default forwardRef<OrderPackageRef, IOrderPackageProps>(
}
// 更新对应的产品数量
const category = { ...updatedItem.boxCategoryList[categoryIndex] };
const boxSpec = { ...updatedItem.boxSpecList[specIndex] };
// 确保boxProductList存在
if (!category.boxProductList) {
category.boxProductList = fullBrandInfo.boxCategoryList[
categoryIndex
if (!boxSpec.boxProductList) {
boxSpec.boxProductList = fullBrandInfo.boxSpecList[
specIndex
].boxProductList.map((product) => ({
...product,
boxCount: 0,
}));
}
const boxProductList = [...category.boxProductList];
const boxProductList = [...boxSpec.boxProductList];
// 确保产品存在
if (!boxProductList[detailIndex]) {
boxProductList[detailIndex] = {
...fullBrandInfo.boxCategoryList[categoryIndex].boxProductList[
detailIndex
],
...fullBrandInfo.boxSpecList[specIndex].boxProductList[detailIndex],
boxCount: 0,
};
}
@ -557,10 +546,10 @@ export default forwardRef<OrderPackageRef, IOrderPackageProps>(
boxCount: count,
};
category.boxProductList = boxProductList;
const updatedCategories = [...updatedItem.boxCategoryList];
updatedCategories[categoryIndex] = category;
updatedItem.boxCategoryList = updatedCategories;
boxSpec.boxProductList = boxProductList;
const updatedSpecList = [...updatedItem.boxSpecList];
updatedSpecList[specIndex] = boxSpec;
updatedItem.boxSpecList = updatedSpecList;
setEditingItem(updatedItem);
};
@ -686,17 +675,17 @@ export default forwardRef<OrderPackageRef, IOrderPackageProps>(
{/* 详细信息展示,按分类显示 */}
<View className="space-y-2">
{item.boxCategoryList &&
item.boxCategoryList.map((categoryData, categoryIndex) => (
{item.boxSpecList &&
item.boxSpecList.map((boxSpec, boxIndex) => (
<View
key={categoryIndex}
key={boxIndex}
className="rounded-lg bg-gray-50 p-3 text-sm text-gray-600"
>
<View className="mb-1 font-medium text-gray-700">
{categoryData.boxCategoryName}
{boxSpec.boxSpecName}
</View>
<View>
{categoryData.boxProductList
{boxSpec.boxProductList
.map(
(detail) =>
`${detail.boxProductName}${detail.boxCount}`,
@ -854,13 +843,13 @@ export default forwardRef<OrderPackageRef, IOrderPackageProps>(
<View className="mb-2 text-sm text-gray-600">
+/- 0使
</View>
{selectedBrand.boxCategoryList?.map((category) => (
<View key={category.id} className="mb-4">
{selectedBrand.boxSpecList?.map((boxSpec) => (
<View key={boxSpec.id} className="mb-4">
<View className="mb-2 text-base font-medium">
{category.boxCategoryName}
{boxSpec.boxSpecName}
</View>
<View className="space-y-3">
{category.boxProductList.map((boxProduct) => {
{boxSpec.boxProductList.map((boxProduct) => {
const currentCount =
productCounts.get(boxProduct.id) || 0;
return (
@ -1005,91 +994,81 @@ export default forwardRef<OrderPackageRef, IOrderPackageProps>(
<View className="mb-4">
<View className="mb-2 text-sm text-gray-600"></View>
<View className="space-y-3">
{editingItem?.boxCategoryList &&
editingItem.boxCategoryList.map(
(categoryData, categoryIndex) => (
<View
key={categoryIndex}
className="rounded-lg bg-gray-50 p-3"
>
<View className="mb-2 text-base font-medium text-gray-800">
{categoryData.boxCategoryName}
</View>
<View className="space-y-2">
{categoryData.boxProductList.map(
(detail, detailIndex) => {
const currentCount = detail?.boxCount || 0;
return (
<View
key={detailIndex}
className="flex items-center justify-between py-1"
>
<View className="text-gray-600">
{detail.boxProductName}
</View>
<View className="flex items-center">
<View
className="flex h-8 w-8 items-center justify-center rounded-l bg-gray-200"
onClick={() => {
const newCount = Math.max(
0,
currentCount - 1,
);
updateEditingItemCount(
categoryIndex,
detailIndex,
newCount,
);
}}
>
<Icon name="minus" size={16} />
</View>
<View className="flex h-8 w-12 items-center justify-center border-y border-gray-200">
<Input
type="number"
value={currentCount.toString()}
align={"center"}
className="!h-8 !w-12 !p-0 !text-center"
onChange={(value) => {
const num = Number(value);
if (
!Number.isNaN(num) &&
num >= 0
) {
updateEditingItemCount(
categoryIndex,
detailIndex,
num,
);
}
}}
/>
</View>
<View
className="flex h-8 w-8 items-center justify-center rounded-r bg-gray-200"
onClick={() => {
updateEditingItemCount(
categoryIndex,
detailIndex,
currentCount + 1,
);
}}
>
<Icon name="plus" size={16} />
</View>
<View className="ml-2 font-medium text-gray-800">
</View>
</View>
</View>
);
},
)}
</View>
{editingItem?.boxSpecList &&
editingItem.boxSpecList.map((boxSpec, specId) => (
<View key={specId} className="rounded-lg bg-gray-50 p-3">
<View className="mb-2 text-base font-medium text-gray-800">
{boxSpec.boxSpecName}
</View>
),
)}
<View className="space-y-2">
{boxSpec.boxProductList.map((detail, detailIndex) => {
const currentCount = detail?.boxCount || 0;
return (
<View
key={detailIndex}
className="flex items-center justify-between py-1"
>
<View className="text-gray-600">
{detail.boxProductName}
</View>
<View className="flex items-center">
<View
className="flex h-8 w-8 items-center justify-center rounded-l bg-gray-200"
onClick={() => {
const newCount = Math.max(
0,
currentCount - 1,
);
updateEditingItemCount(
specId,
detailIndex,
newCount,
);
}}
>
<Icon name="minus" size={16} />
</View>
<View className="flex h-8 w-12 items-center justify-center border-y border-gray-200">
<Input
type="number"
value={currentCount.toString()}
align={"center"}
className="!h-8 !w-12 !p-0 !text-center"
onChange={(value) => {
const num = Number(value);
if (!Number.isNaN(num) && num >= 0) {
updateEditingItemCount(
specId,
detailIndex,
num,
);
}
}}
/>
</View>
<View
className="flex h-8 w-8 items-center justify-center rounded-r bg-gray-200"
onClick={() => {
updateEditingItemCount(
specId,
detailIndex,
currentCount + 1,
);
}}
>
<Icon name="plus" size={16} />
</View>
<View className="ml-2 font-medium text-gray-800">
</View>
</View>
</View>
);
})}
</View>
</View>
))}
</View>
</View>
</ScrollView>

View File

@ -68,18 +68,7 @@ export default function PurchasePreview(props: IPurchasePreviewProps) {
const [columns] = useState<Array<TableColumnProps>>([
{
title: "规格",
key: "boxCategoryId",
render: (record: any) => {
return (
<View>
{record.boxCategoryId === "FOUR_GRAIN"
? "4粒"
: record.boxCategoryId === "TWO_GRAIN"
? "2粒"
: "其他"}
</View>
);
},
key: "boxSpecName",
fixed: "left",
},
{

View File

@ -1,12 +1,10 @@
import { Table } from "@nutui/nutui-react-taro";
import { formatCurrency } from "@/utils/format";
export default function EmptyBoxInfoSection(props: {
export default function EmptyBoxInfoSection(_props: {
purchaseOrderVO: BusinessAPI.PurchaseOrderVO;
readOnly?: boolean;
}) {
const { purchaseOrderVO } = props;
// 将所有空箱信息合并并按品牌、型号、规格分组
// const groupedEmptyBoxData = purchaseOrderVO.orderSupplierList?.reduce(
// (acc, supplier) => {

View File

@ -4,7 +4,6 @@ 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;
@ -20,7 +19,7 @@ export default function PackageInfoSection(props: {
},
{
title: "规格",
key: "boxCategoryName",
key: "boxSpecName",
},
{
title: "纸箱型号",
@ -33,22 +32,28 @@ export default function PackageInfoSection(props: {
{
title: "销售单价(元/斤)",
key: "boxSalePrice",
render: (value: BusinessAPI.OrderPackage & { orderPackageId?: string, isTotalRow?: boolean }) => {
render: (
value: BusinessAPI.OrderPackage & {
orderPackageId?: string;
isTotalRow?: boolean;
},
) => {
// 合计行不显示编辑按钮
if (value.isTotalRow) {
return formatCurrency(value.boxSalePrice as number);
}
return (
<View
className="flex items-center justify-between w-full"
<View
className="flex w-full items-center justify-between"
onClick={(e) => {
if (!readOnly) {
e.stopPropagation();
// 设置临时编辑值为当前值
setTempEditValues((prev) => ({
...prev,
[value.orderPackageId || ""]: editValues[value.orderPackageId || ""],
[value.orderPackageId || ""]:
editValues[value.orderPackageId || ""],
}));
setVisiblePopup((prev) => ({
...prev,
@ -57,9 +62,11 @@ export default function PackageInfoSection(props: {
}
}}
>
<View className={!readOnly ? "underline cursor-pointer" : ""}>{formatCurrency(value.boxSalePrice as number)}</View>
<View className={!readOnly ? "cursor-pointer underline" : ""}>
{formatCurrency(value.boxSalePrice as number)}
</View>
{!readOnly && (
<View className="flex items-center justify-center ml-2 p-2 -m-2">
<View className="-m-2 ml-2 flex items-center justify-center p-2">
<Icon name={"pen-to-square"} size={16} color={"#1a73e8"} />
</View>
)}
@ -70,7 +77,12 @@ export default function PackageInfoSection(props: {
{
title: "销售金额(元)",
key: "boxSalePayment",
render: (value: BusinessAPI.OrderPackage & { boxProductCount: number, isTotalRow?: boolean }) =>
render: (
value: BusinessAPI.OrderPackage & {
boxProductCount: number;
isTotalRow?: boolean;
},
) =>
formatCurrency(
Number((value?.boxSalePrice || 0) * value.boxProductCount) as number,
),
@ -78,22 +90,28 @@ export default function PackageInfoSection(props: {
{
title: "箱重(斤)",
key: "boxProductWeight",
render: (value: BusinessAPI.OrderPackage & { orderPackageId?: string, isTotalRow?: boolean }) => {
render: (
value: BusinessAPI.OrderPackage & {
orderPackageId?: string;
isTotalRow?: boolean;
},
) => {
// 合计行不显示编辑按钮
if (value.isTotalRow) {
return formatCurrency(value.boxProductWeight);
}
return (
<View
className="flex items-center justify-between w-full"
<View
className="flex w-full items-center justify-between"
onClick={(e) => {
if (!readOnly) {
e.stopPropagation();
// 设置临时编辑值为当前值
setTempEditValues((prev) => ({
...prev,
[value.orderPackageId || ""]: editValues[value.orderPackageId || ""],
[value.orderPackageId || ""]:
editValues[value.orderPackageId || ""],
}));
setVisiblePopup((prev) => ({
...prev,
@ -102,9 +120,11 @@ export default function PackageInfoSection(props: {
}
}}
>
<View className={!readOnly ? "underline cursor-pointer" : ""}>{formatCurrency(value.boxProductWeight)}</View>
<View className={!readOnly ? "cursor-pointer underline" : ""}>
{formatCurrency(value.boxProductWeight)}
</View>
{!readOnly && (
<View className="flex items-center justify-center ml-2 p-2 -m-2">
<View className="-m-2 ml-2 flex items-center justify-center p-2">
<Icon name={"pen-to-square"} size={16} color={"#1a73e8"} />
</View>
)}
@ -115,22 +135,28 @@ export default function PackageInfoSection(props: {
{
title: "成本单价(元)",
key: "boxCostPrice",
render: (value: BusinessAPI.OrderPackage & { orderPackageId?: string, isTotalRow?: boolean }) => {
render: (
value: BusinessAPI.OrderPackage & {
orderPackageId?: string;
isTotalRow?: boolean;
},
) => {
// 合计行不显示编辑按钮
if (value.isTotalRow) {
return formatCurrency(value.boxCostPrice as number);
}
return (
<View
className="flex items-center justify-between w-full"
<View
className="flex w-full items-center justify-between"
onClick={(e) => {
if (!readOnly) {
e.stopPropagation();
// 设置临时编辑值为当前值
setTempEditValues((prev) => ({
...prev,
[value.orderPackageId || ""]: editValues[value.orderPackageId || ""],
[value.orderPackageId || ""]:
editValues[value.orderPackageId || ""],
}));
setVisiblePopup((prev) => ({
...prev,
@ -139,9 +165,11 @@ export default function PackageInfoSection(props: {
}
}}
>
<View className={!readOnly ? "underline cursor-pointer" : ""}>{formatCurrency(value.boxCostPrice as number)}</View>
<View className={!readOnly ? "cursor-pointer underline" : ""}>
{formatCurrency(value.boxCostPrice as number)}
</View>
{!readOnly && (
<View className="flex items-center justify-center ml-2 p-2 -m-2">
<View className="-m-2 ml-2 flex items-center justify-center p-2">
<Icon name={"pen-to-square"} size={16} color={"#1a73e8"} />
</View>
)}
@ -226,20 +254,17 @@ export default function PackageInfoSection(props: {
(acc, supplier) => {
supplier.orderPackageList?.forEach((pkg) => {
// 生成分组键
const key = `${pkg.boxBrandName}-${pkg.boxProductName}-${pkg.boxCategoryId}`;
const key = `${pkg.boxBrandName}-${pkg.boxProductName}-${pkg.boxSpecId}`;
// 转换规格字段
const boxCategoryName =
pkg.boxCategoryId === "FOUR_GRAIN"
? "4粒"
: pkg.boxCategoryId === "TWO_GRAIN"
? "2粒"
: "其他";
const boxSpecId = pkg.boxSpecId;
const boxSpecName = pkg.boxSpecName;
if (!acc[key]) {
acc[key] = {
...pkg,
boxCategoryName,
boxSpecId,
boxSpecName,
boxProductCount: 0,
};
}

View File

@ -213,7 +213,8 @@ export default hocAuth(function Page(props: CommonComponent) {
packingSpec: {
data:
shipOrderVO.shipOrderPackageList?.map((item) => ({
boxCategory: item.boxCategory || "",
boxSpecId: item.boxSpecId || "",
boxSpecName: item.boxSpecName || "",
boxType: item.boxProduct || "",
quantity: item.quantity?.toString() || "",
unitPrice: item.unitPrice?.toString() || "",
@ -1078,13 +1079,7 @@ export default hocAuth(function Page(props: CommonComponent) {
htmlString += `
<div class="${gridClass} ${index > 0 ? "border-t-0" : ""}">
<div class="flex items-end justify-center">
${
item.boxCategory === "FOUR_GRAIN"
? "4粒装"
: item.boxCategory === "TWO_GRAIN"
? "2粒装"
: "未知"
}
${item.boxSpecName}
</div>
`;
@ -2126,11 +2121,7 @@ export default hocAuth(function Page(props: CommonComponent) {
)}
>
<View className={"flex items-end justify-center"}>
{item.boxCategory === "FOUR_GRAIN"
? "4粒装"
: item.boxCategory === "TWO_GRAIN"
? "2粒装"
: "未知"}
{item.boxSpecName}
</View>
<View className={"flex items-end justify-center"}>

View File

@ -151,7 +151,8 @@ export default hocAuth(function Page(props: CommonComponent) {
packingSpec: {
data:
shipOrderVO.shipOrderPackageList?.map((item) => ({
boxCategory: item.boxCategory || "",
boxSpecId: item.boxSpecId || "",
boxSpecName: item.boxSpecName || "",
boxType: item.boxProduct || "",
quantity: item.quantity?.toString() || "",
unitPrice: item.unitPrice?.toString() || "",
@ -1006,13 +1007,7 @@ export default hocAuth(function Page(props: CommonComponent) {
htmlString += `
<div class="${gridClass} ${index > 0 ? "border-t-0" : ""}">
<div class="flex items-end justify-center">
${
item.boxCategory === "FOUR_GRAIN"
? "4粒装"
: item.boxCategory === "TWO_GRAIN"
? "2粒装"
: "未知"
}
${item.boxSpecName}
</div>
`;
@ -1948,11 +1943,7 @@ export default hocAuth(function Page(props: CommonComponent) {
)}
>
<View className={"flex items-end justify-center"}>
{item.boxCategory === "FOUR_GRAIN"
? "4粒装"
: item.boxCategory === "TWO_GRAIN"
? "2粒装"
: "未知"}
{item.boxSpecName}
</View>
<View className={"flex items-end justify-center"}>

View File

@ -25,11 +25,11 @@ import {
Weigh,
WeighRef,
} from "@/components";
import { calculateSupplierWeights } from "@/utils/calculateSupplierWeights";
import { business } from "@/services";
import { generateShortId } from "@/utils/generateShortId";
import Taro from "@tarojs/taro";
import buildUrl from "@/utils/buildUrl";
import { SupplierWeightCalculator } from "@/utils/SupplierWeightCalculator";
const defaultSupplierList: SupplierVO[] = [
{
@ -191,10 +191,15 @@ export default hocAuth(function Page(props: CommonComponent) {
}, [active]);
useEffect(() => {
setPurchaseOrder((prev) => ({
...prev!,
orderSupplierList: calculateSupplierWeights(orderSupplierList),
}));
if (orderSupplierList) {
const supplierWeightCalculator = new SupplierWeightCalculator(
orderSupplierList as any,
);
setPurchaseOrder((prev) => ({
...prev!,
orderSupplierList: supplierWeightCalculator.calculate(),
}));
}
}, [orderSupplierList]);
useEffect(() => {

View File

@ -0,0 +1,126 @@
// @ts-ignore
/* eslint-disable */
import request from "../request";
/** 创建纸箱规格 POST /operation/createBoxSpec */
export async function createBoxSpec(
body: BusinessAPI.BoxSpecCreateCmd,
options?: { [key: string]: any },
) {
return request<BusinessAPI.SingleResponseBoxSpecVO>(
"/operation/createBoxSpec",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
},
);
}
/** 纸箱规格删除 DELETE /operation/destroyBoxSpec */
export async function destroyBoxSpec(
body: BusinessAPI.BoxSpecDestroyCmd,
options?: { [key: string]: any },
) {
return request<BusinessAPI.Response>("/operation/destroyBoxSpec", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
});
}
/** 纸箱规格列表 GET /operation/listBoxSpec */
export async function listBoxSpec(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: BusinessAPI.listBoxSpecParams,
options?: { [key: string]: any },
) {
return request<BusinessAPI.MultiResponseBoxSpecVO>("/operation/listBoxSpec", {
method: "GET",
params: {
...params,
boxSpecListQry: undefined,
...params["boxSpecListQry"],
},
...(options || {}),
});
}
/** 纸箱规格列表 GET /operation/pageBoxSpec */
export async function pageBoxSpec(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: BusinessAPI.pageBoxSpecParams,
options?: { [key: string]: any },
) {
return request<BusinessAPI.PageResponseBoxSpecVO>("/operation/pageBoxSpec", {
method: "GET",
params: {
...params,
boxSpecPageQry: undefined,
...params["boxSpecPageQry"],
},
...(options || {}),
});
}
/** 纸箱规格详情 GET /operation/showBoxSpec */
export async function showBoxSpec(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: BusinessAPI.showBoxSpecParams,
options?: { [key: string]: any },
) {
return request<BusinessAPI.SingleResponseBoxSpecVO>(
"/operation/showBoxSpec",
{
method: "GET",
params: {
...params,
boxSpecShowQry: undefined,
...params["boxSpecShowQry"],
},
...(options || {}),
},
);
}
/** 纸箱规格更新 PUT /operation/updateBoxSpec */
export async function updateBoxSpec(
body: BusinessAPI.BoxSpecUpdateCmd,
options?: { [key: string]: any },
) {
return request<BusinessAPI.SingleResponseBoxSpecVO>(
"/operation/updateBoxSpec",
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
},
);
}
/** 纸箱规格更新 PATCH /operation/updateBoxSpec */
export async function updateBoxSpec1(
body: BusinessAPI.BoxSpecUpdateCmd,
options?: { [key: string]: any },
) {
return request<BusinessAPI.SingleResponseBoxSpecVO>(
"/operation/updateBoxSpec",
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
},
);
}

View File

@ -23,12 +23,14 @@ import * as costItem from "./costItem";
import * as company from "./company";
import * as companyPaymentAccount from "./companyPaymentAccount";
import * as channel from "./channel";
import * as boxSpec from "./boxSpec";
import * as boxProduct from "./boxProduct";
import * as boxBrand from "./boxBrand";
import * as agreement from "./agreement";
import * as role from "./role";
import * as permission from "./permission";
import * as extraction from "./extraction";
export default {
user,
supplier,
@ -51,6 +53,7 @@ export default {
company,
companyPaymentAccount,
channel,
boxSpec,
boxProduct,
boxBrand,
agreement,

View File

@ -103,12 +103,14 @@ declare namespace BusinessAPI {
name: string;
/** 品牌图片URL */
image?: string;
/** 纸箱规格ID */
specIds?: number[];
/** 备注 */
remark?: string;
/** 状态1_启用0_禁用 */
status: boolean;
/** 品牌类型1_我方纸箱2_瓜农纸箱3_第三方纸箱 */
type: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX";
/** 品牌类型1_我方纸箱2_瓜农纸箱3_第三方纸箱4_礼盒 */
type: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX" | "GIFT_BOX";
};
type BoxBrandDestroyCmd = {
@ -132,8 +134,8 @@ declare namespace BusinessAPI {
brandId?: string;
/** 是否包含纸箱产品 */
withProduct?: boolean;
/** 品牌类型1_我方纸箱2_瓜农纸箱3_第三方纸箱 */
type?: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX";
/** 品牌类型1_我方纸箱2_瓜农纸箱3_第三方纸箱4_礼盒 */
type?: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX" | "GIFT_BOX";
};
type BoxBrandPageQry = {
@ -153,8 +155,8 @@ declare namespace BusinessAPI {
status?: boolean;
/** 纸箱品牌ID */
brandId?: string;
/** 品牌类型1_我方纸箱2_瓜农纸箱3_第三方纸箱 */
type?: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX";
/** 品牌类型1_我方纸箱2_瓜农纸箱3_第三方纸箱4_礼盒 */
type?: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX" | "GIFT_BOX";
offset?: number;
};
@ -172,12 +174,14 @@ declare namespace BusinessAPI {
name: string;
/** 品牌图片URL */
image?: string;
/** 纸箱规格ID */
specIds?: number[];
/** 备注 */
remark?: string;
/** 状态1_启用0_禁用 */
status: boolean;
/** 品牌类型1_我方纸箱2_瓜农纸箱3_第三方纸箱 */
type: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX";
/** 品牌类型1_我方纸箱2_瓜农纸箱3_第三方纸箱4_礼盒 */
type: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX" | "GIFT_BOX";
};
type BoxBrandVO = {
@ -187,14 +191,18 @@ declare namespace BusinessAPI {
name: string;
/** 品牌图片URL */
image?: string;
/** 纸箱规格ID */
specIds?: number[];
/** 备注 */
remark?: string;
/** 状态1_启用0_禁用 */
status: boolean;
/** 品牌类型1_我方纸箱2_瓜农纸箱3_第三方纸箱 */
type: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX";
/** 品牌类型1_我方纸箱2_瓜农纸箱3_第三方纸箱4_礼盒 */
type: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX" | "GIFT_BOX";
/** 纸箱产品列表 */
boxProductVOList?: BoxProductVO[];
/** 纸箱规格列表 */
boxSpecVOList?: BoxSpecVO[];
/** 创建时间 */
createdAt?: string;
};
@ -210,16 +218,18 @@ declare namespace BusinessAPI {
costPrice?: number;
/** 销售价 */
salePrice?: number;
/** 规格1_2粒装2_4粒装 */
specType: "TWO_GRAIN" | "FOUR_GRAIN";
/** 规格ID */
specId?: string;
/** 规格名称 */
specName?: string;
/** 品牌ID */
brandId: string;
/** 备注 */
remark?: string;
/** 状态1_启用0_禁用 */
status: boolean;
/** 品牌类型1_我方纸箱2_瓜农纸箱3_第三方纸箱 */
type: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX";
/** 品牌类型1_我方纸箱2_瓜农纸箱3_第三方纸箱4_礼盒 */
type: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX" | "GIFT_BOX";
};
type BoxProductDestroyCmd = {
@ -245,10 +255,10 @@ declare namespace BusinessAPI {
name?: string;
/** 纸箱品牌ID */
brandId?: string;
/** 规格1_2粒装2_4粒装 */
specType?: "TWO_GRAIN" | "FOUR_GRAIN";
/** 品牌类型1_我方纸箱2_瓜农纸箱3_第三方纸箱 */
type?: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX";
/** 规格ID */
specId?: string;
/** 品牌类型1_我方纸箱2_瓜农纸箱3_第三方纸箱4_礼盒 */
type?: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX" | "GIFT_BOX";
};
type BoxProductPageQry = {
@ -272,10 +282,10 @@ declare namespace BusinessAPI {
name?: string;
/** 纸箱品牌ID */
brandId?: string;
/** 规格1_2粒装2_4粒装 */
specType?: "TWO_GRAIN" | "FOUR_GRAIN";
/** 品牌类型1_我方纸箱2_瓜农纸箱3_第三方纸箱 */
type?: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX";
/** 规格ID */
specId?: string;
/** 品牌类型1_我方纸箱2_瓜农纸箱3_第三方纸箱4_礼盒 */
type?: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX" | "GIFT_BOX";
offset?: number;
};
@ -297,16 +307,18 @@ declare namespace BusinessAPI {
costPrice?: number;
/** 销售价 */
salePrice?: number;
/** 规格1_2粒装2_4粒装 */
specType: "TWO_GRAIN" | "FOUR_GRAIN";
/** 规格ID */
specId?: string;
/** 规格名称 */
specName?: string;
/** 品牌ID */
brandId: string;
/** 备注 */
remark?: string;
/** 状态1_启用0_禁用 */
status: boolean;
/** 品牌类型1_我方纸箱2_瓜农纸箱3_第三方纸箱 */
type: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX";
/** 品牌类型1_我方纸箱2_瓜农纸箱3_第三方纸箱4_礼盒 */
type: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX" | "GIFT_BOX";
};
type BoxProductVO = {
@ -320,12 +332,94 @@ declare namespace BusinessAPI {
costPrice: number;
/** 销售价 */
salePrice: number;
/** 规格1_2粒装2_4粒装 */
specType: "TWO_GRAIN" | "FOUR_GRAIN";
/** 规格ID */
specId: string;
/** 规格名称 */
specName: string;
/** 品牌ID */
brandId: string;
/** 品牌类型1_我方纸箱2_瓜农纸箱3_第三方纸箱 */
type: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX";
/** 品牌类型1_我方纸箱2_瓜农纸箱3_第三方纸箱4_礼盒 */
type: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX" | "GIFT_BOX";
/** 备注 */
remark?: string;
/** 状态1_启用0_禁用 */
status: boolean;
/** 创建时间 */
createdAt?: string;
};
type BoxSpecCreateCmd = {
/** 规格ID */
specId: string;
/** 规格名称 */
name: string;
/** 排序号 */
sort: number;
/** 备注 */
remark?: string;
/** 状态1_启用0_禁用 */
status: boolean;
};
type BoxSpecDestroyCmd = {
/** 纸箱规格ID */
specId: string;
};
type BoxSpecListQry = {
/** 状态1_启用0_禁用 */
status?: boolean;
/** 纸箱规格ID */
specId?: string;
};
type BoxSpecPageQry = {
pageSize?: number;
pageIndex?: number;
orderBy?: string;
orderDirection?: string;
groupBy?: string;
needTotalCount?: boolean;
/** 自定义字段key */
customFieldKey?: string;
/** 自定义字段value */
customFieldValue?: string;
/** 备注 */
remark?: string;
/** 状态1_启用0_禁用 */
status?: boolean;
/** 纸箱规格ID */
specId?: string;
offset?: number;
};
type BoxSpecShowQry = {
/** 状态1_启用0_禁用 */
status?: boolean;
/** 纸箱规格ID */
specId?: string;
};
type BoxSpecUpdateCmd = {
/** 纸箱规格ID */
specId: string;
/** 规格名称 */
name: string;
/** 排序号 */
sort: number;
/** 备注 */
remark?: string;
/** 状态1_启用0_禁用 */
status: boolean;
};
type BoxSpecVO = {
/** 规格ID */
specId: string;
/** 规格名称 */
name: string;
/** 排序号 */
sort: number;
/** 备注 */
remark?: string;
/** 状态1_启用0_禁用 */
@ -878,6 +972,8 @@ declare namespace BusinessAPI {
| "FIXED_COST"
| "WORKER_ADVANCE"
| "PRODUCTION_ADVANCE";
/** 是否在录入时显示 */
showInEntry?: boolean;
/** 项目名称 */
name?: string;
offset?: number;
@ -1823,6 +1919,10 @@ declare namespace BusinessAPI {
boxProductListQry: BoxProductListQry;
};
type listBoxSpecParams = {
boxSpecListQry: BoxSpecListQry;
};
type listCompanyParams = {
companyListQry: CompanyListQry;
};
@ -2145,6 +2245,15 @@ declare namespace BusinessAPI {
notEmpty?: boolean;
};
type MultiResponseBoxSpecVO = {
success?: boolean;
errCode?: string;
errMessage?: string;
data?: BoxSpecVO[];
empty?: boolean;
notEmpty?: boolean;
};
type MultiResponseCompanyPaymentAccountVO = {
success?: boolean;
errCode?: string;
@ -2384,6 +2493,8 @@ declare namespace BusinessAPI {
payerType?: "US" | "OTHER";
/** 负责人 */
principal?: string;
/** 是否需要数量和价格 */
requireQuantityAndPrice?: boolean;
/** 费用类型1_包装材料2_人工费用3_其他费用4_固定费用5_工头垫付6_产地垫付 */
costType:
| "PACKAGING_MATERIALS"
@ -2392,8 +2503,6 @@ declare namespace BusinessAPI {
| "FIXED_COST"
| "WORKER_ADVANCE"
| "PRODUCTION_ADVANCE";
/** 是否需要填写数量和单价 */
requireQuantityAndPrice: boolean;
};
type OrderDealer = {
@ -2448,8 +2557,12 @@ declare namespace BusinessAPI {
boxBrandName: string;
/** 箱子品牌图片 */
boxBrandImage?: string;
/** 箱子分类ID */
boxCategoryId: string;
/** 箱子品牌类型1_我方纸箱2_瓜农纸箱3_第三方纸箱4_礼盒 */
boxBrandType: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX" | "GIFT_BOX";
/** 箱子规格ID */
boxSpecId: string;
/** 箱子规格名称 */
boxSpecName: string;
/** 箱子产品ID */
boxProductId: string;
/** 箱子产品名称 */
@ -2596,6 +2709,10 @@ declare namespace BusinessAPI {
boxProductPageQry: BoxProductPageQry;
};
type pageBoxSpecParams = {
boxSpecPageQry: BoxSpecPageQry;
};
type pageChannelParams = {
channelPageQry: ChannelPageQry;
};
@ -2699,6 +2816,19 @@ declare namespace BusinessAPI {
totalPages?: number;
};
type PageResponseBoxSpecVO = {
success?: boolean;
errCode?: string;
errMessage?: string;
totalCount?: number;
pageSize?: number;
pageIndex?: number;
data?: BoxSpecVO[];
empty?: boolean;
notEmpty?: boolean;
totalPages?: number;
};
type PageResponseChannelVO = {
success?: boolean;
errCode?: string;
@ -3752,8 +3882,10 @@ declare namespace BusinessAPI {
orderPackageId: string;
/** 发货单ID */
shipOrderId: string;
/** 箱型ID */
boxSpecId?: number;
/** 箱型 */
boxCategory?: string;
boxSpecName?: string;
/** 箱号 */
boxProduct?: string;
/** 数量 */
@ -3921,7 +4053,7 @@ declare namespace BusinessAPI {
shipOrderItemList?: ShipOrderItem[];
/** 发货单子项表 */
shipOrderPackageList?: ShipOrderPackage[];
/** 发货单费用项 */
/** 发货单成本项目信息 */
orderCostList?: OrderCost[];
};
@ -3937,6 +4069,10 @@ declare namespace BusinessAPI {
boxProductShowQry: BoxProductShowQry;
};
type showBoxSpecParams = {
boxSpecShowQry: BoxSpecShowQry;
};
type showChannelParams = {
channelShowQry: ChannelShowQry;
};
@ -4050,6 +4186,13 @@ declare namespace BusinessAPI {
data?: BoxProductVO;
};
type SingleResponseBoxSpecVO = {
success?: boolean;
errCode?: string;
errMessage?: string;
data?: BoxSpecVO;
};
type SingleResponseCategoryVO = {
success?: boolean;
errCode?: string;

View File

@ -13,7 +13,9 @@ export interface CommonComponent {
user: AuthAPI.UserVO;
shareOptions: any;
role: "origin-entry" | "market-buyer" | "reviewer" | "boss";
setRole: (role: "origin-entry" | "market-buyer" | "reviewer" | "boss") => void;
setRole: (
role: "origin-entry" | "market-buyer" | "reviewer" | "boss",
) => void;
router: Router;
options: Taro.getLaunchOptionsSync.LaunchOptions;
isInitialized: boolean;
@ -33,9 +35,9 @@ export interface BoxBrand {
boxBrandId: string;
boxBrandName: string;
boxBrandImage: string;
boxBrandType: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX";
boxBrandType: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX" | "GIFT_BOX";
boxType: "USED" | "EXTRA" | "EXTRA_USED" | "REMAIN" | "OWN" | "DEFAULT";
boxCategoryList: BoxCategory[];
boxSpecList: BoxSpec[];
}
export interface BoxProduct {
@ -46,15 +48,16 @@ export interface BoxProduct {
boxProductName: string;
boxCount: number;
boxProductWeight: number;
boxCategoryId: "FOUR_GRAIN" | "TWO_GRAIN";
boxSpecId: string;
boxSpecName: string;
boxCostPrice: number;
boxSalePrice: number;
}
export interface BoxCategory {
export interface BoxSpec {
id: string;
boxCategoryId: "FOUR_GRAIN" | "TWO_GRAIN";
boxCategoryName: string;
boxSpecId: string;
boxSpecName: string;
boxProductList: BoxProduct[];
}

View File

@ -0,0 +1,189 @@
import { Decimal } from "decimal.js";
/**
*
*
*/
export class SupplierWeightCalculator {
private suppliers: BusinessAPI.OrderSupplier[];
constructor(suppliers: BusinessAPI.OrderSupplier[]) {
this.suppliers = suppliers;
this.init();
}
/**
*
*/
private init() {
Decimal.set({
precision: 20,
rounding: 0, // 向下舍入(更保守的计算方式)
toExpNeg: -7,
toExpPos: 21,
});
}
/**
*
* @returns {Array}
*/
calculate(): BusinessAPI.OrderSupplier[] {
if (!this.suppliers || this.suppliers.length === 0) {
return this.suppliers;
}
// 使用第一个瓜农的空磅重量作为初始空磅重量
const initialEmptyWeight = this.suppliers[0].emptyWeight;
let previousTotalWeight = initialEmptyWeight; // 上一个农户的总磅重量(kg)
for (let i = 0; i < this.suppliers.length; i++) {
const supplier = this.suppliers[i];
const isFirstSupplier = i === 0;
const isLastSupplier = supplier.isLast;
// 设置空磅重量(第一个农户使用自己的空磅重量,其他使用前一个农户的总磅重量)
if (isFirstSupplier) {
// 第一个农户的空磅重量已经是正确的
} else {
supplier.emptyWeight = this.suppliers[i - 1].totalWeight;
}
// 计算本次使用纸箱的总重量(斤)
const usedBoxesWeight = new Decimal(
this.calculateBoxesTotalWeight(supplier.orderPackageList || [], "USED"),
)
.add(
this.calculateBoxesTotalWeight(
supplier.orderPackageList || [],
"OWN",
),
)
.toNumber();
// 计算额外配送的已使用纸箱总重量(斤)
const extraUsedBoxesWeight = this.calculateBoxesTotalWeight(
supplier.orderPackageList || [],
"EXTRA_USED",
);
if (!supplier.isPaper) {
// 如果不是纸箱包装直接使用原始重量kg转斤
supplier.grossWeight = new Decimal(supplier.totalWeight)
.sub(supplier.emptyWeight)
.mul(2)
.toNumber();
supplier.netWeight = new Decimal(supplier.grossWeight)
.sub(usedBoxesWeight)
.sub(extraUsedBoxesWeight)
.toNumber();
previousTotalWeight = supplier.totalWeight;
supplier.invoiceAmount = new Decimal(supplier.netWeight)
.mul(supplier.purchasePrice)
.toNumber();
continue;
}
// 计算额外配送的纸箱总重量(斤)
const extraBoxesWeight = this.calculateBoxesTotalWeight(
supplier.orderPackageList || [],
"EXTRA",
);
// 计算车上剩余纸箱(斤)
const remainingBoxesWeight = this.calculateBoxesTotalWeight(
supplier.orderPackageList || [],
"REMAIN",
);
if (isFirstSupplier && isLastSupplier) {
// 既是第一个也是最后一个瓜农(单个瓜农情况)- 优先使用最后一个瓜农算法
// 净重 = (总磅 - 空磅) * 2 + 剩余空箱子重量 - 已使用额外纸箱重量
supplier.netWeight = new Decimal(supplier.totalWeight)
.sub(initialEmptyWeight)
.mul(2)
.add(remainingBoxesWeight)
.sub(extraUsedBoxesWeight)
.toNumber();
// 毛重 = 净重 + 本次使用纸箱重量
supplier.grossWeight = new Decimal(supplier.netWeight)
.add(usedBoxesWeight)
.toNumber();
} else if (isLastSupplier) {
// 最后一个农户根据isLast标识判断
// 净重 = (总磅 - 前一个总磅) * 2 + 剩余空箱子重量 - 额外纸箱重量
supplier.netWeight = new Decimal(supplier.totalWeight)
.sub(previousTotalWeight)
.mul(2)
.add(remainingBoxesWeight)
.sub(extraBoxesWeight)
.toNumber();
// 毛重 = 净重 + 本次使用纸箱重量
supplier.grossWeight = new Decimal(supplier.netWeight)
.add(usedBoxesWeight)
.toNumber();
} else if (isFirstSupplier) {
// 第一个农户(但不是最后一个)
// 净重 = (总磅 - 空磅) * 2 - 额外纸箱重量
supplier.netWeight = new Decimal(supplier.totalWeight)
.sub(initialEmptyWeight)
.mul(2)
.sub(extraBoxesWeight)
.toNumber();
// 毛重 = 净重 + 本次使用纸箱重量
supplier.grossWeight = new Decimal(supplier.netWeight)
.add(usedBoxesWeight)
.toNumber();
} else {
// 中间农户
// 净重 = (总磅 - 前一个总磅) * 2 - 额外纸箱重量
supplier.netWeight = new Decimal(supplier.totalWeight)
.sub(previousTotalWeight)
.mul(2)
.sub(extraBoxesWeight)
.toNumber();
// 毛重 = 净重 + 本次使用纸箱重量
supplier.grossWeight = new Decimal(supplier.netWeight)
.add(usedBoxesWeight)
.toNumber();
}
previousTotalWeight = supplier.totalWeight;
supplier.invoiceAmount = new Decimal(supplier.netWeight)
.mul(supplier.purchasePrice)
.toNumber();
}
return this.suppliers;
}
/**
*
* @param {Array} orderPackageList -
* @param {string} boxType - ('USED' 'EXTRA')
* @returns {number}
*/
private calculateBoxesTotalWeight(
orderPackageList: BusinessAPI.OrderPackage[],
boxType?: any,
): number {
if (!orderPackageList) return 0;
let filteredPackages = orderPackageList;
if (boxType) {
filteredPackages = orderPackageList.filter(
(pkg) => pkg.boxType === boxType,
);
}
return filteredPackages.reduce((sum, pkg) => {
// 纸箱重量单位是斤,直接使用
const boxWeight = pkg.boxProductWeight || 0;
return new Decimal(sum)
.add(new Decimal(pkg.boxCount || 0).mul(boxWeight))
.toNumber();
}, 0);
}
}

View File

@ -1,293 +0,0 @@
/**
*
* @param {Array} suppliers -
* @returns {Array}
*/
export function calculateSupplierWeights(suppliers) {
if (!suppliers || suppliers.length === 0) {
return suppliers;
}
// 使用第一个瓜农的空磅重量作为初始空磅重量
const initialEmptyWeight = suppliers[0].emptyWeight;
let previousTotalWeight = initialEmptyWeight; // 上一个农户的总磅重量(kg)
for (let i = 0; i < suppliers.length; i++) {
const supplier = suppliers[i];
const isFirstSupplier = i === 0;
const isLastSupplier = supplier.isLast === true;
// 设置空磅重量(第一个农户使用自己的空磅重量,其他使用前一个农户的总磅重量)
if (isFirstSupplier) {
// 第一个农户的空磅重量已经是正确的
} else {
supplier.emptyWeight = suppliers[i - 1].totalWeight;
}
// 计算本次使用纸箱的总重量(斤)
const usedBoxesWeight = calculateBoxesTotalWeight(supplier.orderPackageList, 'USED') + calculateBoxesTotalWeight(supplier.orderPackageList, 'OWN');
if (!supplier.isPaper) {
// 如果不是纸箱包装直接使用原始重量kg转斤
supplier.grossWeight = (supplier.totalWeight - supplier.emptyWeight) * 2;
supplier.netWeight = supplier.grossWeight - usedBoxesWeight;
previousTotalWeight = supplier.totalWeight;
supplier.invoiceAmount = supplier.netWeight * supplier.purchasePrice;
continue;
}
// 计算额外配送的纸箱总重量(斤)
const extraBoxesWeight = calculateBoxesTotalWeight(
supplier.orderPackageList,
'EXTRA'
);
// 计算额外配送的已使用纸箱总重量(斤)
const extraUsedBoxesWeight = calculateBoxesTotalWeight(
supplier.orderPackageList,
'EXTRA_USED'
);
// 计算车上剩余纸箱(斤)
const remainingBoxesWeight = calculateBoxesTotalWeight(
supplier.orderPackageList,
'REMAIN'
);
if (isFirstSupplier && isLastSupplier) {
// 既是第一个也是最后一个瓜农(单个瓜农情况)- 优先使用最后一个瓜农算法
// 净重 = (总磅 - 空磅) * 2 + 剩余空箱子重量 - 已使用额外纸箱重量
supplier.netWeight = (supplier.totalWeight - initialEmptyWeight) * 2 + remainingBoxesWeight - extraUsedBoxesWeight;
// 毛重 = 净重 + 本次使用纸箱重量
supplier.grossWeight = supplier.netWeight + usedBoxesWeight;
} else if (isLastSupplier) {
// 最后一个农户根据isLast标识判断
// 净重 = (总磅 - 前一个总磅) * 2 + 剩余空箱子重量 - 额外纸箱重量
supplier.netWeight = (supplier.totalWeight - previousTotalWeight) * 2 + remainingBoxesWeight - extraBoxesWeight;
// 毛重 = 净重 + 本次使用纸箱重量
supplier.grossWeight = supplier.netWeight + usedBoxesWeight;
} else if (isFirstSupplier) {
// 第一个农户(但不是最后一个)
// 净重 = (总磅 - 空磅) * 2 - 额外纸箱重量
supplier.netWeight = (supplier.totalWeight - initialEmptyWeight) * 2 - extraBoxesWeight;
// 毛重 = 净重 + 本次使用纸箱重量
supplier.grossWeight = supplier.netWeight + usedBoxesWeight;
} else {
// 中间农户
// 净重 = (总磅 - 前一个总磅) * 2 - 额外纸箱重量
supplier.netWeight = (supplier.totalWeight - previousTotalWeight) * 2 - extraBoxesWeight;
// 毛重 = 净重 + 本次使用纸箱重量
supplier.grossWeight = supplier.netWeight + usedBoxesWeight;
}
previousTotalWeight = supplier.totalWeight;
supplier.invoiceAmount = supplier.netWeight * supplier.purchasePrice;
}
return suppliers;
}
/**
*
* @param {Array} orderPackageList -
* @param {string} boxType - ('used' 'extra')
* @returns {number}
*/
function calculateBoxesTotalWeight(orderPackageList, boxType?: any) {
if (!orderPackageList) return 0;
let filteredPackages = orderPackageList;
if (boxType) {
filteredPackages = orderPackageList.filter((pkg) => pkg.boxType === boxType);
}
return filteredPackages.reduce((sum, pkg) => {
// 纸箱重量单位是斤,直接使用
const boxWeight = pkg.boxProductWeight || 0;
return sum + pkg.boxCount * boxWeight;
}, 0);
}
// // 使用示例 - 单个瓜农(既是第一个也是最后一个)
// const singleSupplier = [
// {
// id: "1",
// name: "瓜农1",
// isPaper: true,
// isLast: true, // 单个瓜农也是最后一个
// emptyWeight: 25, // kg
// totalWeight: 50, // kg
// orderPackageList: [
// {
// boxBrandId: "brand1",
// boxCategoryId: "TWO_GRAIN",
// boxProductId: "product1",
// boxProductWeight: 0.6, // 斤
// boxCount: 2,
// boxType: "used"
// },
// {
// boxBrandId: "brand1",
// boxCategoryId: "TWO_GRAIN",
// boxProductId: "product1",
// boxProductWeight: 0.6, // 斤
// boxCount: 1,
// boxType: "extra-used"
// }
// ]
// }
// ];
//
// // 使用示例 - 多个瓜农
// const multipleSuppliers = [
// {
// id: "1",
// name: "瓜农1",
// isPaper: true,
// emptyWeight: 25, // kg
// totalWeight: 50, // kg
// orderPackageList: [
// {
// boxBrandId: "brand1",
// boxCategoryId: "TWO_GRAIN",
// boxProductId: "product1",
// boxProductWeight: 0.6, // 斤
// boxCount: 2,
// boxType: "used"
// },
// {
// boxBrandId: "brand1",
// boxCategoryId: "TWO_GRAIN",
// boxProductId: "product1",
// boxProductWeight: 0.6, // 斤
// boxCount: 1,
// boxType: "extra"
// }
// ]
// },
// {
// id: "2",
// name: "瓜农2",
// isPaper: true,
// isLast: true, // 标记为最后一个瓜农
// totalWeight: 80, // kg
// orderPackageList: [
// {
// boxBrandId: "brand2",
// boxCategoryId: "THREE_GRAIN",
// boxProductId: "product2",
// boxProductWeight: 1.0, // 斤
// boxCount: 2,
// boxType: "used"
// }
// ]
// }
// ];
//
// // 使用示例 - 第一个但不是最后一个
// const firstNotLastSuppliers = [
// {
// id: "1",
// name: "瓜农1",
// isPaper: true,
// emptyWeight: 25, // kg
// totalWeight: 50, // kg
// orderPackageList: [
// {
// boxBrandId: "brand1",
// boxCategoryId: "TWO_GRAIN",
// boxProductId: "product1",
// boxProductWeight: 0.6, // 斤
// boxCount: 2,
// boxType: "used"
// }
// ]
// },
// {
// id: "2",
// name: "瓜农2",
// isPaper: true,
// // 没有isLast标记当作中间农户
// totalWeight: 80, // kg
// orderPackageList: [
// {
// boxBrandId: "brand2",
// boxCategoryId: "THREE_GRAIN",
// boxProductId: "product2",
// boxProductWeight: 1.0, // 斤
// boxCount: 2,
// boxType: "used"
// }
// ]
// }
// ];
//
// console.log("=== 单个瓜农测试(既是第一个也是最后一个)===");
// const result1 = calculateSupplierWeights(singleSupplier);
// console.log(JSON.stringify(result1, null, 2));
//
// console.log("\n=== 多个瓜农测试 ===");
// const result2 = calculateSupplierWeights(multipleSuppliers);
// console.log(JSON.stringify(result2, null, 2));
//
// console.log("\n=== 第一个但不是最后一个测试 ===");
// const result3 = calculateSupplierWeights(firstNotLastSuppliers);
// console.log(JSON.stringify(result3, null, 2));
//
// // 详细显示结果
// function displayResults(suppliers, title) {
// console.log(`\n${title}:`);
// suppliers.forEach((supplier, index) => {
//
// const isFirstSupplier = index === 0;
// const isLastSupplier = supplier.isLast === true;
//
// console.log(`\n农户${index + 1} ${supplier.name}:`);
// console.log(` 是否第一个: ${index === 0 ? '是' : '否'}`);
// console.log(` 是否最后一个: ${supplier.isLast ? '是' : '否'}`);
// console.log(` 空磅重量: ${supplier.emptyWeight} kg`);
// console.log(` 总磅重量: ${supplier.totalWeight} kg`);
// console.log(` 净重: ${supplier.netWeight?.toFixed(2) || 'N/A'} 斤`);
// console.log(` 毛重: ${supplier.grossWeight?.toFixed(2) || 'N/A'} 斤`);
//
// if (supplier.isPaper) {
// const usedBoxesWeight = calculateBoxesTotalWeight(supplier.orderPackageList, 'used');
// const extraUsedBoxesWeight = calculateBoxesTotalWeight(supplier.orderPackageList, 'extra-used');
// const extraBoxesWeight = calculateBoxesTotalWeight(supplier.orderPackageList, 'extra');
// const remainingEmptyBoxesWeight = calculateBoxesTotalWeight(supplier.orderPackageList, 'remaining');
//
// if (isFirstSupplier && isLastSupplier) {
// console.log(` 本次使用纸箱重量: ${usedBoxesWeight.toFixed(2)} 斤`);
// console.log(` 已用额外运输纸箱重量: ${extraUsedBoxesWeight.toFixed(2)} 斤`);
// console.log(` 车上剩余纸箱重量: ${remainingEmptyBoxesWeight.toFixed(2)} 斤`);
// console.log(` 净重 = (总磅${supplier.totalWeight} - 空磅${supplier.emptyWeight}) × 2 + 车上剩余纸箱重量${remainingEmptyBoxesWeight.toFixed(2)} - 已用额外运输纸箱重量${extraUsedBoxesWeight.toFixed(2)} = ${supplier.netWeight?.toFixed(2)}斤`);
// console.log(` 毛重 = 净重${supplier.netWeight?.toFixed(2)} + 本次使用纸箱重量${usedBoxesWeight.toFixed(2)} = ${supplier.grossWeight?.toFixed(2)}斤`);
// } else if (isFirstSupplier) {
// // 第一个瓜农
// console.log(` 本次使用纸箱重量: ${usedBoxesWeight.toFixed(2)} 斤`);
// console.log(` 额外运输纸箱重量: ${extraBoxesWeight.toFixed(2)} 斤`);
// console.log(` 净重 = (总磅${supplier.totalWeight} - 空磅${supplier.emptyWeight}) × 2 - 额外运输纸箱重量${extraBoxesWeight.toFixed(2)} = ${supplier.netWeight?.toFixed(2)}斤`);
// console.log(` 毛重 = 净重${supplier.netWeight?.toFixed(2)} + 本次使用纸箱重量${usedBoxesWeight.toFixed(2)} = ${supplier.grossWeight?.toFixed(2)}斤`);
// } else if (isLastSupplier) {
// // 最后一个瓜农
// console.log(` 本次使用纸箱重量: ${usedBoxesWeight.toFixed(2)} 斤`);
// console.log(` 已用额外运输纸箱重量: ${extraUsedBoxesWeight.toFixed(2)} 斤`);
// console.log(` 车上剩余纸箱重量: ${remainingEmptyBoxesWeight.toFixed(2)} 斤`);
// console.log(` 净重 = (总磅${supplier.totalWeight} - 空磅${suppliers[index-1]?.totalWeight}) × 2 + 车上剩余纸箱重量${remainingEmptyBoxesWeight.toFixed(2)} - 已用额外运输纸箱重量${extraUsedBoxesWeight.toFixed(2)} = ${supplier.netWeight?.toFixed(2)}斤`);
// console.log(` 毛重 = 净重${supplier.netWeight?.toFixed(2)} + 本次使用纸箱重量${usedBoxesWeight.toFixed(2)} = ${supplier.grossWeight?.toFixed(2)}斤`);
// } else {
// console.log(` 本次使用纸箱重量: ${usedBoxesWeight.toFixed(2)} 斤`);
// console.log(` 额外运输纸箱重量: ${extraBoxesWeight.toFixed(2)} 斤`);
// console.log(` 净重 = (总磅${supplier.totalWeight} - 空磅${suppliers[index-1]?.totalWeight}) × 2 - 额外运输纸箱重量${extraBoxesWeight.toFixed(2)} = ${supplier.netWeight?.toFixed(2)}斤`);
// console.log(` 毛重 = 净重${supplier.netWeight?.toFixed(2)} + 本次使用纸箱重量${usedBoxesWeight.toFixed(2)} = ${supplier.grossWeight?.toFixed(2)}斤`);
// }
// }
// });
// }
//
// displayResults(result1, "单个瓜农结果");
// displayResults(result2, "多个瓜农结果");
// displayResults(result3, "第一个但不是最后一个结果");

View File

@ -1,9 +1,9 @@
// 将BoxBrand转换为OrderPackage数组
import { BoxBrand, BoxCategory, BoxProduct } from "@/types/typings";
import { BoxBrand, BoxProduct, BoxSpec } from "@/types/typings";
import { generateShortId } from "@/utils/generateShortId";
// 添加一个辅助函数用于分组
const groupBy = <T,>(
const groupBy = <T>(
array: T[],
keyFn: (item: T) => string,
): Record<string, T[]> => {
@ -22,19 +22,21 @@ const groupBy = <T,>(
export const convertBoxBrandToOrderPackages = (
boxBrand: BoxBrand,
boxType: BusinessAPI.OrderPackage["boxType"] = "USED"
boxType: BusinessAPI.OrderPackage["boxType"] = "USED",
): BusinessAPI.OrderPackage[] => {
const orderPackages: BusinessAPI.OrderPackage[] = [];
boxBrand.boxCategoryList?.forEach((category) => {
boxBrand.boxSpecList?.forEach((category) => {
category.boxProductList.forEach((product) => {
orderPackages.push({
orderPackageId: product.id,
boxBrandId: product.boxBrandId,
boxBrandName: product.boxBrandName,
boxBrandImage: boxBrand.boxBrandImage,
boxCategoryId: product.boxCategoryId,
boxSpecId: product.boxSpecId,
boxSpecName: product.boxSpecName,
boxProductId: product.boxProductId,
boxBrandType: boxBrand.boxBrandType,
boxProductName: product.boxProductName,
boxProductWeight: product.boxProductWeight,
boxCount: product.boxCount || 0,
@ -50,54 +52,48 @@ export const convertBoxBrandToOrderPackages = (
// 将OrderPackage数组转换为BoxBrand数组
export const convertOrderPackagesToBoxBrands = (
orderPackages: BusinessAPI.OrderPackage[]
orderPackages: BusinessAPI.OrderPackage[],
): BoxBrand[] => {
// 按品牌ID分组
const packagesByBrand = groupBy(
orderPackages,
(pkg) => pkg.boxBrandId
);
const packagesByBrand = groupBy(orderPackages, (pkg) => pkg.boxBrandId);
// 转换为BoxBrand数组
return Object.entries(packagesByBrand).map(([brandId, packages]) => {
const firstPackage = packages[0];
// 按分类ID分组
const packagesByCategory = groupBy(
packages,
(pkg) => pkg.boxCategoryId
);
const packagesBySpec = groupBy(packages, (pkg) => pkg.boxSpecId);
// 创建BoxCategory数组
const boxCategories: BoxCategory[] = Object.entries(packagesByCategory).map(
([categoryId, categoryPackages]) => {
const firstCategoryPackage = categoryPackages[0];
const boxSpecList: BoxSpec[] = Object.entries(packagesBySpec).map(
([specId, specList]) => {
const firstCategoryPackage = specList[0];
// 创建BoxProduct数组
const boxProducts: BoxProduct[] = categoryPackages.map((pkg) => ({
id: pkg.orderPackageId || generateShortId(),
boxBrandId: pkg.boxBrandId,
boxBrandName: pkg.boxBrandName,
boxProductId: pkg.boxProductId,
boxProductName: pkg.boxProductName,
boxProductWeight: pkg.boxProductWeight,
boxCategoryId: pkg.boxCategoryId,
boxCostPrice: pkg.boxCostPrice,
boxSalePrice: pkg.boxSalePrice,
boxCount: pkg.boxCount,
} as BoxProduct));
const boxProducts: BoxProduct[] = specList.map(
(pkg) =>
({
id: pkg.orderPackageId || generateShortId(),
boxBrandId: pkg.boxBrandId,
boxBrandName: pkg.boxBrandName,
boxProductId: pkg.boxProductId,
boxProductName: pkg.boxProductName,
boxProductWeight: pkg.boxProductWeight,
boxSpecId: pkg.boxSpecId,
boxSpecName: pkg.boxSpecName,
boxCostPrice: pkg.boxCostPrice,
boxSalePrice: pkg.boxSalePrice,
boxCount: pkg.boxCount,
}) as BoxProduct,
);
return {
id: generateShortId(),
boxCategoryId: categoryId as "FOUR_GRAIN" | "TWO_GRAIN",
boxCategoryName: firstCategoryPackage.boxCategoryId === "FOUR_GRAIN"
? "4粒装"
: firstCategoryPackage.boxCategoryId === "TWO_GRAIN"
? "2粒装"
: "未知",
boxSpecId: specId,
boxSpecName: firstCategoryPackage.boxSpecName,
boxProductList: boxProducts,
};
}
},
);
return {
@ -105,8 +101,9 @@ export const convertOrderPackagesToBoxBrands = (
boxBrandId: brandId,
boxType: firstPackage.boxType,
boxBrandName: firstPackage.boxBrandName,
boxBrandImage: firstPackage.boxBrandImage,
boxCategoryList: boxCategories,
boxBrandImage: firstPackage.boxBrandImage!,
boxBrandType: firstPackage.boxBrandType,
boxSpecList: boxSpecList,
};
});
};

File diff suppressed because one or more lines are too long