diff --git a/packages/app-client/src/components/purchase/module/PurchasePreview.tsx b/packages/app-client/src/components/purchase/module/PurchasePreview.tsx
index 18b4efa..bf63d0c 100644
--- a/packages/app-client/src/components/purchase/module/PurchasePreview.tsx
+++ b/packages/app-client/src/components/purchase/module/PurchasePreview.tsx
@@ -1,11 +1,7 @@
import { View } from "@tarojs/components";
import { useEffect, useState } from "react";
import { Table, TableColumnProps } from "@nutui/nutui-react-taro";
-import {
- formatCurrency,
- OrderSupplierCalculator,
- PurchaseOrderCalculator,
-} from "@/utils";
+import { formatCurrency, PurchaseOrderCalculator } from "@/utils";
import { Icon } from "@/components";
import { Decimal } from "decimal.js";
@@ -144,10 +140,9 @@ export default function PurchasePreview(props: IPurchasePreviewProps) {
瓜农装箱明细
{orderSupplierList.map((supplier, index) => {
- const calculator = new OrderSupplierCalculator(
- purchaseOrder as any,
- supplier as any,
- );
+ const calculator = new PurchaseOrderCalculator(
+ purchaseOrder,
+ ).getSupplierCalculator(supplier);
return (
diff --git a/packages/app-client/src/pages/main/menu/index.tsx b/packages/app-client/src/pages/main/menu/index.tsx
index 9d680bd..67e67db 100644
--- a/packages/app-client/src/pages/main/menu/index.tsx
+++ b/packages/app-client/src/pages/main/menu/index.tsx
@@ -16,6 +16,7 @@ export default hocAuth(function Page(props: CommonComponent) {
data: { data: menuTree },
} = await auth.user.userMenu({
roleMenuTreeQry: {
+ //@ts-ignore
roleId: roleIdList,
platformId: "1991353387274342401",
},
@@ -25,7 +26,7 @@ export default hocAuth(function Page(props: CommonComponent) {
useEffect(() => {
initMenuList([userRoleVO.roleId]).then();
- }, [userRoleVO]);
+ }, [userRoleVO.roleId]);
const handleMenuClick = (menu: BusinessAPI.MenuVO) => {
if (menu.path) {
diff --git a/packages/app-client/src/pages/purchase/approval/audit.tsx b/packages/app-client/src/pages/purchase/approval/audit.tsx
index 0643b67..565163a 100644
--- a/packages/app-client/src/pages/purchase/approval/audit.tsx
+++ b/packages/app-client/src/pages/purchase/approval/audit.tsx
@@ -188,7 +188,7 @@ export default hocAuth(function Page(props: CommonComponent) {
onFinish={() => {
// 返回首页
Taro.redirectTo({
- url: "/pages/purchase/approver/audit/list",
+ url: "/pages/purchase/approval/pending",
});
}}
/>
@@ -200,7 +200,7 @@ export default hocAuth(function Page(props: CommonComponent) {
onFinish={() => {
// 关闭当前页面并跳转到采购单审核通过页面
Taro.redirectTo({
- url: buildUrl(`/pages/purchase/approver/audit/result`, {
+ url: buildUrl(`/pages/purchase/approval/result`, {
orderId: purchaseOrderVO?.orderId,
}),
});
diff --git a/packages/app-client/src/pages/purchase/audit/audit.tsx b/packages/app-client/src/pages/purchase/audit/audit.tsx
index 34fa131..9dd215b 100644
--- a/packages/app-client/src/pages/purchase/audit/audit.tsx
+++ b/packages/app-client/src/pages/purchase/audit/audit.tsx
@@ -691,8 +691,10 @@ export default hocAuth(function Page(props: CommonComponent) {
}}
>
+ {purchaseOrderVO.state === "REJECTED" &&
+ purchaseOrderVO.auditState === "BOSS_REJECTED" && }
{/* 顶部导航 */}
-
+
{purchaseOrderVO?.orderDealer?.shortName || "-"} 第
@@ -723,7 +725,8 @@ export default hocAuth(function Page(props: CommonComponent) {
disabled={
userRoleVO.slug === "boss" ||
(userRoleVO.slug === "reviewer" &&
- purchaseOrderVO.state !== "WAITING_AUDIT")
+ purchaseOrderVO.state !== "WAITING_AUDIT" &&
+ purchaseOrderVO.state !== "REJECTED")
}
placeholder="请输入产地负责人姓名"
value={originPrincipal || purchaseOrderVO.createdByName}
@@ -754,7 +757,10 @@ export default hocAuth(function Page(props: CommonComponent) {
className={`overflow-x-auto rounded-md rounded-b-lg bg-white p-2.5 shadow-sm`}
>
- {purchaseOrderVO.state === "WAITING_AUDIT" &&
- purchaseOrderVO.auditState === "PENDING_QUOTE_APPROVAL" && (
- <>
-
-
-
-
-
-
- >
- )}
+ {((purchaseOrderVO.state === "WAITING_AUDIT" &&
+ purchaseOrderVO.auditState === "PENDING_QUOTE_APPROVAL") ||
+ (purchaseOrderVO.state === "REJECTED" &&
+ purchaseOrderVO.auditState === "BOSS_REJECTED")) && (
+ <>
+
+
+
+
+
+
+ >
+ )}
{purchaseOrderVO.state === "WAITING_AUDIT" &&
purchaseOrderVO.auditState === "PENDING_BOSS_APPROVAL" && (
<>
diff --git a/packages/app-client/src/pages/purchase/enter/create.tsx b/packages/app-client/src/pages/purchase/enter/create.tsx
index a2c038e..b2b8f20 100644
--- a/packages/app-client/src/pages/purchase/enter/create.tsx
+++ b/packages/app-client/src/pages/purchase/enter/create.tsx
@@ -23,9 +23,10 @@ import {
WeighRef,
} from "@/components";
import { business } from "@/services";
-import { buildUrl, generateShortId, SupplierWeightCalculator } from "@/utils";
+import { buildUrl, generateShortId } from "@/utils";
import Taro from "@tarojs/taro";
import { Button } from "@nutui/nutui-react-taro";
+import { WeightCalculationService } from "@/utils/classes/calculators";
const defaultSupplierList: Partial[] = [
{
@@ -290,9 +291,10 @@ export default hocAuth(function Page(props: CommonComponent) {
return {
...prev!,
...purchaseOrder,
- orderSupplierList: new SupplierWeightCalculator(
- purchaseOrder?.orderSupplierList,
- ).calculate(),
+ orderSupplierList:
+ WeightCalculationService.calculateWeights(
+ purchaseOrder?.orderSupplierList,
+ ),
};
});
}}
@@ -324,9 +326,10 @@ export default hocAuth(function Page(props: CommonComponent) {
return {
...prev!,
...purchaseOrder,
- orderSupplierList: new SupplierWeightCalculator(
- purchaseOrder?.orderSupplierList,
- ).calculate(),
+ orderSupplierList:
+ WeightCalculationService.calculateWeights(
+ purchaseOrder?.orderSupplierList,
+ ),
};
});
}}
@@ -358,9 +361,10 @@ export default hocAuth(function Page(props: CommonComponent) {
return {
...prev!,
...purchaseOrder,
- orderSupplierList: new SupplierWeightCalculator(
- purchaseOrder?.orderSupplierList,
- ).calculate(),
+ orderSupplierList:
+ WeightCalculationService.calculateWeights(
+ purchaseOrder.orderSupplierList,
+ ),
};
});
}}
diff --git a/packages/app-client/src/utils/classes/calculators/OrderSupplierCalculator.ts b/packages/app-client/src/utils/classes/calculators/OrderSupplierCalculator.ts
deleted file mode 100644
index 4fca00f..0000000
--- a/packages/app-client/src/utils/classes/calculators/OrderSupplierCalculator.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-import { Decimal } from "decimal.js";
-
-/**
- * 采购订单计算器类
- * 封装所有与采购订单相关的计算逻辑
- */
-export class OrderSupplierCalculator {
- // @ts-ignore
- private purchaseOrderVO: BusinessAPI.PurchaseOrderVO;
- private orderSupplier: BusinessAPI.OrderSupplier;
-
- constructor(
- purchaseOrderVO: BusinessAPI.PurchaseOrderVO,
- orderSupplier: BusinessAPI.OrderSupplier,
- ) {
- this.purchaseOrderVO = purchaseOrderVO;
- this.orderSupplier = orderSupplier;
- this.init();
- }
-
- /**
- * 初始化计算规则
- */
- private init() {
- Decimal.set({
- precision: 20,
- rounding: Decimal.ROUND_HALF_UP, // 使用常量更清晰
- toExpNeg: -7,
- toExpPos: 21,
- });
- }
-
- /**
- * 毛重
- */
- getGrossWeight(): number {
- return new Decimal(this.orderSupplier.grossWeight || 0).toNumber();
- }
-
- /**
- * 净重
- */
- getNetWeight(): number {
- return new Decimal(this.orderSupplier.netWeight || 0).toNumber();
- }
-
- /**
- * 箱重
- */
- getBoxWeight(): number {
- return new Decimal(this.getGrossWeight())
- .sub(this.getNetWeight())
- .toNumber();
- }
-
- /**
- * 采购单价
- */
- getPurchasePrice(): number {
- return new Decimal(this.orderSupplier.purchasePrice || 0).toNumber();
- }
-
- /**
- * 合计金额
- */
- getTotalAmount(): number {
- if (
- this.orderSupplier.orderPackageList?.some((pkg) => pkg.boxType === "USED")
- ) {
- return new Decimal(this.getNetWeight())
- .mul(this.getPurchasePrice())
- .toNumber();
- } else {
- return new Decimal(this.getGrossWeight())
- .mul(this.getPurchasePrice())
- .toNumber();
- }
- }
-
- /**
- * 押金支付金额
- */
- getDepositPaidAmount(): number {
- if (!this.orderSupplier.isDepositPaid) {
- return 0;
- }
- return new Decimal(this.orderSupplier.depositAmount || 0).toNumber();
- }
-
- /**
- * 计算纸箱的总重量(斤)
- * @param {string} boxType - 箱子类型 ('USED' 或 'EXTRA',不传则计算所有)
- * @returns {number} 总重量(斤)
- */
- calculateBoxesTotalWeight(boxType?: any): number {
- return (
- this.orderSupplier.orderPackageList
- ?.filter((pkg) => pkg.boxType === boxType)
- .reduce((sum, pkg) => {
- // 纸箱重量单位是斤,直接使用
- const boxWeight = pkg.boxProductWeight || 0;
- return new Decimal(sum)
- .add(
- new Decimal(pkg.boxCount || 0).mul(boxWeight).toDecimalPlaces(0),
- )
- .toNumber();
- }, 0) || 0
- );
- }
-}
diff --git a/packages/app-client/src/utils/classes/calculators/PurchaseOrderCalculator.ts b/packages/app-client/src/utils/classes/calculators/PurchaseOrderCalculator.ts
index dcf5134..6de0419 100644
--- a/packages/app-client/src/utils/classes/calculators/PurchaseOrderCalculator.ts
+++ b/packages/app-client/src/utils/classes/calculators/PurchaseOrderCalculator.ts
@@ -3,6 +3,9 @@ import { CostCalculator } from "./modules/CostCalculator";
import { SalesCalculator } from "./modules/SalesCalculator";
import { ProfitCalculator } from "./modules/ProfitCalculator";
import { PackagingCalculator } from "./modules/PackagingCalculator";
+import { OrderSupplierCalculator } from "./modules/OrderSupplierCalculator";
+import { SupplierWeightCalculator } from "./modules/SupplierWeightCalculator";
+import { WeightCalculationService } from "./services/WeightCalculationService";
/**
* 采购订单计算器
@@ -249,6 +252,55 @@ export class PurchaseOrderCalculator {
return this.packagingCalculator.calculateBoxSaleAmount();
}
+ // ==================== 供应商相关计算 ====================
+
+ /**
+ * 获取供应商计算器
+ */
+ getSupplierCalculator(supplier: BusinessAPI.OrderSupplier): OrderSupplierCalculator {
+ return new OrderSupplierCalculator(supplier, this.rules);
+ }
+
+ /**
+ * 获取所有供应商的计算器
+ */
+ getAllSupplierCalculators(): OrderSupplierCalculator[] {
+ if (!this.rules.order.orderSupplierList?.length) {
+ return [];
+ }
+
+ return this.rules.order.orderSupplierList.map(supplier =>
+ new OrderSupplierCalculator(supplier, this.rules)
+ );
+ }
+
+ /**
+ * 计算所有供应商的总采购金额
+ */
+ getTotalSupplierPurchaseAmount(): number {
+ return this.getAllSupplierCalculators().reduce((total, calc) => {
+ return total + calc.calculatePurchaseAmount();
+ }, 0);
+ }
+
+ /**
+ * 计算所有供应商的总销售金额
+ */
+ getTotalSupplierSalesAmount(): number {
+ return this.getAllSupplierCalculators().reduce((total, calc) => {
+ return total + calc.calculateSalesAmount();
+ }, 0);
+ }
+
+ /**
+ * 计算所有供应商的总毛利
+ */
+ getTotalSupplierProfit(): number {
+ return this.getAllSupplierCalculators().reduce((total, calc) => {
+ return total + calc.calculateProfit();
+ }, 0);
+ }
+
/**
* 获取总纸箱数量
*/
@@ -313,4 +365,64 @@ export class PurchaseOrderCalculator {
getPackagingCalculator(): PackagingCalculator {
return this.packagingCalculator;
}
+
+ /**
+ * 获取原始订单数据
+ */
+ getOrder(): BusinessAPI.PurchaseOrderVO {
+ return this.rules.order;
+ }
+
+ // ==================== 重量计算相关 ====================
+
+ /**
+ * 计算供应商重量
+ * @returns 计算后的订单数据
+ */
+ calculateSupplierWeights(): BusinessAPI.PurchaseOrderVO {
+ if (!this.rules.order.orderSupplierList?.length) {
+ return this.rules.order;
+ }
+
+ // 使用重量计算服务处理供应商重量
+ this.rules.order.orderSupplierList = WeightCalculationService.calculateWeights(
+ this.rules.order.orderSupplierList
+ );
+
+ return this.rules.order;
+ }
+
+ /**
+ * 获取重量计算器
+ */
+ getWeightCalculator(): SupplierWeightCalculator | null {
+ if (!this.rules.order.orderSupplierList?.length) {
+ return null;
+ }
+
+ return WeightCalculationService.createCalculator(this.rules.order.orderSupplierList);
+ }
+
+ /**
+ * 获取重量统计信息
+ */
+ getWeightStatistics() {
+ if (!this.rules.order.orderSupplierList?.length) {
+ return null;
+ }
+
+ return WeightCalculationService.getWeightStatistics(this.rules.order.orderSupplierList);
+ }
+
+ /**
+ * 验证重量计算结果
+ */
+ validateWeightCalculation(): { isValid: boolean; errors: string[] } | null {
+ const calculator = this.getWeightCalculator();
+ if (!calculator) {
+ return null;
+ }
+
+ return calculator.validate();
+ }
}
diff --git a/packages/app-client/src/utils/classes/calculators/README.md b/packages/app-client/src/utils/classes/calculators/README.md
index c14ba80..ef179de 100644
--- a/packages/app-client/src/utils/classes/calculators/README.md
+++ b/packages/app-client/src/utils/classes/calculators/README.md
@@ -15,7 +15,13 @@ calculators/
│ ├── CostCalculator.ts # 成本计算模块
│ ├── SalesCalculator.ts # 销售计算模块
│ ├── ProfitCalculator.ts # 利润计算模块
-│ └── PackagingCalculator.ts # 包装计算模块
+│ ├── PackagingCalculator.ts # 包装计算模块
+│ ├── OrderSupplierCalculator.ts # 供应商计算模块
+│ └── SupplierWeightCalculator.ts # 供应商重量计算模块
+├── services/ # 服务层
+│ └── WeightCalculationService.ts # 重量计算服务
+├── types/ # 类型定义
+│ └── index.ts
├── PurchaseOrderCalculator.ts # 主计算器类
└── README.md # 文档
```
@@ -37,6 +43,11 @@ calculators/
- **SalesCalculator**: 销售相关计算
- **ProfitCalculator**: 利润相关计算
- **PackagingCalculator**: 包装相关计算
+- **OrderSupplierCalculator**: 单个供应商相关计算
+- **SupplierWeightCalculator**: 供应商重量计算
+
+### 4. 服务层
+- **WeightCalculationService**: 重量计算服务,提供简化的 API 和批量操作功能
## 使用方法
@@ -57,6 +68,12 @@ const marketPrice = calculator.getMarketPrice();
// 计算单斤成本
const costPerJin = calculator.getSingleCost();
+
+// 计算供应商重量
+const updatedOrder = calculator.calculateSupplierWeights();
+
+// 获取重量统计
+const weightStats = calculator.getWeightStatistics();
```
## 主要改进
diff --git a/packages/app-client/src/utils/classes/calculators/SupplierWeightCalculator.ts b/packages/app-client/src/utils/classes/calculators/SupplierWeightCalculator.ts
deleted file mode 100644
index 650f533..0000000
--- a/packages/app-client/src/utils/classes/calculators/SupplierWeightCalculator.ts
+++ /dev/null
@@ -1,181 +0,0 @@
-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: Decimal.ROUND_HALF_UP, // 使用常量更清晰
- toExpNeg: -7,
- toExpPos: 21,
- });
- }
-
- /**
- * 计算所有农户的重量信息
- * @returns {Array} 包含净重、毛重和空磅的农户数据
- */
- calculate(): BusinessAPI.OrderSupplier[] {
- console.log("开始计算采购订单的农户重量信息...", this.suppliers);
- if (!this.suppliers || this.suppliers.length === 0) {
- return this.suppliers;
- }
-
- // 使用第一个瓜农的空磅重量作为初始空磅重量
- const initialEmptyWeight = this.suppliers[0].emptyWeight || 0;
- 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 || 0;
- }
-
- // 计算本次使用纸箱的总重量(斤)
- const usedBoxesWeight = this.calculateBoxesTotalWeight(supplier.orderPackageList || [], "USED")
-
- if (!supplier.isPaper) {
- // 如果不是纸箱包装,直接使用原始重量(kg转斤)
- supplier.grossWeight = new Decimal(supplier.totalWeight || 0)
- .sub(supplier.emptyWeight || 0)
- .mul(2)
- .toNumber();
-
- supplier.netWeight = new Decimal(supplier.grossWeight || 0)
- .sub(usedBoxesWeight)
- .toNumber();
-
- previousTotalWeight = supplier.totalWeight;
- supplier.invoiceAmount = new Decimal(supplier.netWeight || 0)
- .mul(supplier.purchasePrice || 0)
- .toDecimalPlaces(0)
- .toNumber();
- continue;
- }
-
- // 计算额外配送的纸箱总重量(斤)
- const extraBoxesWeight = this.calculateBoxesTotalWeight(
- supplier.orderPackageList || [],
- "EXTRA",
- );
-
- // 计算车上剩余纸箱(斤)
- const remainingBoxesWeight = this.calculateBoxesTotalWeight(
- supplier.orderPackageList || [],
- "REMAIN",
- );
-
- // 计算额外配送的已使用纸箱总重量(斤)
- const extraUsedBoxesWeight = this.calculateBoxesTotalWeight(
- supplier.orderPackageList || [],
- "EXTRA_USED",
- );
-
- if (isFirstSupplier && isLastSupplier) {
- // 既是第一个也是最后一个瓜农(单个瓜农情况)- 优先使用最后一个瓜农算法
- // 净重 = (总磅 - 空磅) * 2 + 剩余空箱子重量 - 已使用额外纸箱重量
- supplier.netWeight = new Decimal(supplier.totalWeight || 0)
- .sub(initialEmptyWeight)
- .mul(2)
- .add(remainingBoxesWeight)
- .sub(extraUsedBoxesWeight)
- .toNumber();
- // 毛重 = 净重 + 本次使用纸箱重量
- supplier.grossWeight = new Decimal(supplier.netWeight || 0)
- .add(usedBoxesWeight)
- .toNumber();
- } else if (isLastSupplier) {
- // 最后一个农户(根据isLast标识判断)
- // 净重 = (总磅 - 前一个总磅) * 2 + 剩余空箱子重量 - 已使用额外纸箱重量
- supplier.netWeight = new Decimal(supplier.totalWeight || 0)
- .sub(previousTotalWeight)
- .mul(2)
- .add(remainingBoxesWeight)
- .sub(extraUsedBoxesWeight)
- .toNumber();
- // 毛重 = 净重 + 本次使用纸箱重量
- supplier.grossWeight = new Decimal(supplier.netWeight || 0)
- .add(usedBoxesWeight)
- .toNumber();
- } else if (isFirstSupplier) {
- // 第一个农户(但不是最后一个)
- // 净重 = (总磅 - 空磅) * 2 - 额外纸箱重量
- supplier.netWeight = new Decimal(supplier.totalWeight || 0)
- .sub(initialEmptyWeight)
- .mul(2)
- .sub(extraBoxesWeight)
- .toNumber();
- // 毛重 = 净重 + 本次使用纸箱重量
- supplier.grossWeight = new Decimal(supplier.netWeight || 0)
- .add(usedBoxesWeight)
- .toNumber();
- } else {
- // 中间农户
- // 净重 = (总磅 - 前一个总磅) * 2 - 额外纸箱重量
- supplier.netWeight = new Decimal(supplier.totalWeight || 0)
- .sub(previousTotalWeight)
- .mul(2)
- .sub(extraBoxesWeight)
- .toNumber();
- // 毛重 = 净重 + 本次使用纸箱重量
- supplier.grossWeight = new Decimal(supplier.netWeight || 0)
- .add(usedBoxesWeight)
- .toNumber();
- }
-
- previousTotalWeight = supplier.totalWeight || 0;
- supplier.invoiceAmount = new Decimal(supplier.netWeight || 0)
- .mul(supplier.purchasePrice || 0)
- .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).toDecimalPlaces(0))
- .toNumber();
- }, 0);
- }
-}
diff --git a/packages/app-client/src/utils/classes/calculators/core/BusinessRules.ts b/packages/app-client/src/utils/classes/calculators/core/BusinessRules.ts
index 8e4f01c..63a4401 100644
--- a/packages/app-client/src/utils/classes/calculators/core/BusinessRules.ts
+++ b/packages/app-client/src/utils/classes/calculators/core/BusinessRules.ts
@@ -4,7 +4,7 @@
* 将业务判断逻辑集中管理
*/
export class PurchaseOrderRules {
- constructor(private order: BusinessAPI.PurchaseOrderVO) {}
+ constructor(public order: BusinessAPI.PurchaseOrderVO) {}
/**
* 是否包含运费成本
diff --git a/packages/app-client/src/utils/classes/calculators/index.ts b/packages/app-client/src/utils/classes/calculators/index.ts
index 827d0c7..e8fbfc3 100644
--- a/packages/app-client/src/utils/classes/calculators/index.ts
+++ b/packages/app-client/src/utils/classes/calculators/index.ts
@@ -6,5 +6,4 @@
*/
export { PurchaseOrderCalculator } from './PurchaseOrderCalculator'
-export { OrderSupplierCalculator } from './OrderSupplierCalculator'
-export { SupplierWeightCalculator } from './SupplierWeightCalculator'
+export { WeightCalculationService } from './services/WeightCalculationService'
diff --git a/packages/app-client/src/utils/classes/calculators/modules/OrderSupplierCalculator.ts b/packages/app-client/src/utils/classes/calculators/modules/OrderSupplierCalculator.ts
new file mode 100644
index 0000000..6e60dea
--- /dev/null
+++ b/packages/app-client/src/utils/classes/calculators/modules/OrderSupplierCalculator.ts
@@ -0,0 +1,225 @@
+import { DecimalUtils } from "../core/DecimalUtils";
+import { PurchaseOrderRules } from "../core/BusinessRules";
+
+/**
+ * 供应商计算模块
+ * 负责单个供应商相关的计算
+ */
+export class OrderSupplierCalculator {
+ constructor(
+ private orderSupplier: BusinessAPI.OrderSupplier,
+ private rules: PurchaseOrderRules,
+ ) {}
+
+ /**
+ * 获取毛重
+ */
+ getGrossWeight(): number {
+ return this.orderSupplier.grossWeight || 0;
+ }
+
+ /**
+ * 获取净重
+ */
+ getNetWeight(): number {
+ return this.orderSupplier.netWeight || 0;
+ }
+
+ /**
+ * 计算箱重
+ * = 毛重 - 净重
+ */
+ getBoxWeight(): number {
+ return DecimalUtils.subtract(this.getGrossWeight(), this.getNetWeight());
+ }
+
+ /**
+ * 获取采购单价
+ */
+ getPurchasePrice(): number {
+ return this.orderSupplier.purchasePrice || 0;
+ }
+
+ /**
+ * 获取销售单价
+ */
+ getSalesPrice(): number {
+ return this.orderSupplier.salePrice || 0;
+ }
+
+ /**
+ * 根据定价方式获取重量
+ */
+ getWeightByPricingMethod(): number {
+ return this.rules.getWeightByPricingMethod(this.orderSupplier);
+ }
+
+ /**
+ * 计算采购金额
+ * = 重量 × 采购单价
+ */
+ calculatePurchaseAmount(): number {
+ const weight = this.getWeightByPricingMethod();
+ const price = this.getPurchasePrice();
+ return DecimalUtils.multiply(weight, price);
+ }
+
+ /**
+ * 计算销售金额
+ * = 重量 × 销售单价
+ */
+ calculateSalesAmount(): number {
+ const weight = this.getWeightByPricingMethod();
+ const price = this.getSalesPrice();
+ return DecimalUtils.multiply(weight, price);
+ }
+
+ /**
+ * 计算采购合计金额(兼容旧逻辑)
+ * 如果有使用中的纸箱,按净重计算;否则按毛重计算
+ */
+ getTotalAmount(): number {
+ const hasUsedBoxes = this.hasUsedBoxes();
+ const weight = hasUsedBoxes ? this.getNetWeight() : this.getGrossWeight();
+ const price = this.getPurchasePrice();
+
+ return DecimalUtils.multiply(weight, price);
+ }
+
+ /**
+ * 计算毛利
+ * = 销售金额 - 采购金额
+ */
+ calculateProfit(): number {
+ const salesAmount = this.calculateSalesAmount();
+ const purchaseAmount = this.calculatePurchaseAmount();
+ return DecimalUtils.subtract(salesAmount, purchaseAmount);
+ }
+
+ /**
+ * 获取已支付的押金金额
+ */
+ getDepositPaidAmount(): number {
+ if (!this.orderSupplier.isDepositPaid) {
+ return 0;
+ }
+ return this.orderSupplier.depositAmount || 0;
+ }
+
+ /**
+ * 获取定金金额(与押金相同,不同叫法)
+ */
+ getDepositAmount(): number {
+ return this.getDepositPaidAmount();
+ }
+
+ /**
+ * 判断是否有使用中的纸箱
+ */
+ hasUsedBoxes(): boolean {
+ return (
+ this.orderSupplier.orderPackageList?.some(
+ (pkg) => pkg.boxType === "USED",
+ ) || false
+ );
+ }
+
+ /**
+ * 计算纸箱总重量
+ * @param boxType 纸箱类型,不传则计算所有类型
+ */
+ calculateBoxesTotalWeight(boxType?: string): number {
+ if (!this.orderSupplier.orderPackageList?.length) {
+ return 0;
+ }
+
+ return this.orderSupplier.orderPackageList
+ .filter((pkg) => !boxType || pkg.boxType === boxType)
+ .reduce((total, pkg) => {
+ const boxWeight = DecimalUtils.multiply(
+ pkg.boxCount || 0,
+ pkg.boxProductWeight || 0,
+ );
+ return DecimalUtils.add(total, boxWeight);
+ }, 0);
+ }
+
+ /**
+ * 计算使用中纸箱的数量
+ */
+ getUsedBoxCount(): number {
+ if (!this.orderSupplier.orderPackageList?.length) {
+ return 0;
+ }
+
+ return this.orderSupplier.orderPackageList
+ .filter((pkg) => pkg.boxType === "USED")
+ .reduce((total, pkg) => {
+ return DecimalUtils.add(total, pkg.boxCount || 0);
+ }, 0);
+ }
+
+ /**
+ * 计算使用中纸箱的售卖金额
+ */
+ getUsedBoxesSaleAmount(): number {
+ if (!this.orderSupplier.orderPackageList?.length) {
+ return 0;
+ }
+
+ return this.orderSupplier.orderPackageList
+ .filter((pkg) => pkg.boxType === "USED")
+ .reduce((total, pkg) => {
+ const saleAmount = DecimalUtils.multiply(
+ pkg.boxCount || 0,
+ pkg.boxSalePrice || 0,
+ );
+ return DecimalUtils.add(total, saleAmount);
+ }, 0);
+ }
+
+ /**
+ * 计算毛利率
+ * = 毛利 ÷ 销售金额 × 100%
+ */
+ calculateProfitRate(): string {
+ const profit = this.calculateProfit();
+ const salesAmount = this.calculateSalesAmount();
+
+ if (salesAmount === 0) {
+ return "0.00";
+ }
+
+ const rate = DecimalUtils.percentage(profit, salesAmount);
+ return DecimalUtils.toFixed(rate, 2);
+ }
+
+ /**
+ * 计算斤价(按计价重量)
+ */
+ calculatePricePerJin(): string {
+ const purchaseAmount = this.calculatePurchaseAmount();
+ const weight = this.getWeightByPricingMethod();
+
+ if (weight === 0) {
+ return "0.00";
+ }
+
+ const pricePerJin = DecimalUtils.divide(purchaseAmount, weight);
+ return DecimalUtils.toFixed(pricePerJin, 2);
+ }
+
+ /**
+ * 获取供应商名称
+ */
+ getSupplierName(): string {
+ return this.orderSupplier.name || "";
+ }
+
+ /**
+ * 判断供应商是否有效
+ */
+ isValid(): boolean {
+ return !!this.orderSupplier.supplierId && !!this.orderSupplier.name;
+ }
+}
diff --git a/packages/app-client/src/utils/classes/calculators/modules/SalesCalculator.ts b/packages/app-client/src/utils/classes/calculators/modules/SalesCalculator.ts
index c371e51..1a4b993 100644
--- a/packages/app-client/src/utils/classes/calculators/modules/SalesCalculator.ts
+++ b/packages/app-client/src/utils/classes/calculators/modules/SalesCalculator.ts
@@ -88,9 +88,6 @@ export class SalesCalculator {
const baseCost = costCalculator.calculateBaseCost();
const totalWeight = this.calculateTotalWeight();
- console.log("基础成本:", baseCost);
- console.log("总重量:", totalWeight);
-
return DecimalUtils.toFixed(
DecimalUtils.divide(baseCost, totalWeight, 0),
2,
diff --git a/packages/app-client/src/utils/classes/calculators/modules/SupplierWeightCalculator.ts b/packages/app-client/src/utils/classes/calculators/modules/SupplierWeightCalculator.ts
new file mode 100644
index 0000000..1fb9ba3
--- /dev/null
+++ b/packages/app-client/src/utils/classes/calculators/modules/SupplierWeightCalculator.ts
@@ -0,0 +1,251 @@
+import { DecimalUtils } from "../core/DecimalUtils";
+
+/**
+ * 农户重量计算器
+ * 负责计算每个农户的净重和毛重
+ */
+export class SupplierWeightCalculator {
+ private suppliers: BusinessAPI.OrderSupplier[];
+ private initialEmptyWeight: number;
+ private previousTotalWeight: number;
+
+ constructor(suppliers: BusinessAPI.OrderSupplier[]) {
+ this.suppliers = suppliers || [];
+ this.initialEmptyWeight = this.suppliers[0]?.emptyWeight || 0;
+ this.previousTotalWeight = this.initialEmptyWeight;
+ }
+
+ /**
+ * 计算所有农户的重量信息
+ * @returns 处理后的供应商列表
+ */
+ calculate(): BusinessAPI.OrderSupplier[] {
+ if (!this.suppliers.length) {
+ return this.suppliers;
+ }
+
+ for (let i = 0; i < this.suppliers.length; i++) {
+ const supplier = this.suppliers[i];
+ this.calculateSupplierWeight(supplier, i);
+ }
+
+ return this.suppliers;
+ }
+
+ /**
+ * 计算单个供应商的重量
+ */
+ private calculateSupplierWeight(supplier: BusinessAPI.OrderSupplier, index: number): void {
+ const isFirstSupplier = index === 0;
+ const isLastSupplier = supplier.isLast;
+
+ // 设置空磅重量
+ this.setEmptyWeight(supplier, index);
+
+ // 计算各类纸箱重量
+ const weights = this.calculateAllBoxWeights(supplier.orderPackageList || []);
+
+ if (!supplier.isPaper) {
+ // 非纸箱包装的简化计算
+ this.calculateNonPaperSupplier(supplier, weights.used);
+ } else {
+ // 纸箱包装的复杂计算
+ this.calculatePaperSupplier(supplier, weights, isFirstSupplier, isLastSupplier);
+ }
+
+ // 计算发票金额
+ this.calculateInvoiceAmount(supplier);
+
+ // 更新上一个供应商的总重量
+ this.previousTotalWeight = supplier.totalWeight || 0;
+ }
+
+ /**
+ * 设置空磅重量
+ */
+ private setEmptyWeight(supplier: BusinessAPI.OrderSupplier, index: number): void {
+ if (index === 0) {
+ // 第一个农户保持原有空磅重量
+ } else {
+ // 其他农户使用前一个农户的总磅重量
+ supplier.emptyWeight = this.suppliers[index - 1]?.totalWeight || 0;
+ }
+ }
+
+ /**
+ * 计算所有类型的纸箱重量
+ */
+ private calculateAllBoxWeights(packages: BusinessAPI.OrderPackage[]) {
+ return {
+ used: this.calculateBoxWeightByType(packages, "USED"),
+ extra: this.calculateBoxWeightByType(packages, "EXTRA"),
+ remain: this.calculateBoxWeightByType(packages, "REMAIN"),
+ extraUsed: this.calculateBoxWeightByType(packages, "EXTRA_USED"),
+ };
+ }
+
+ /**
+ * 计算非纸箱包装供应商的重量
+ */
+ private calculateNonPaperSupplier(supplier: BusinessAPI.OrderSupplier, usedBoxesWeight: number): void {
+ // 毛重 = (总磅 - 空磅) × 2
+ supplier.grossWeight = DecimalUtils.multiply(
+ DecimalUtils.subtract(supplier.totalWeight || 0, supplier.emptyWeight || 0),
+ 2
+ );
+
+ // 净重 = 毛重 - 使用纸箱重量
+ supplier.netWeight = DecimalUtils.subtract(
+ supplier.grossWeight || 0,
+ usedBoxesWeight
+ );
+ }
+
+ /**
+ * 计算纸箱包装供应商的重量
+ */
+ private calculatePaperSupplier(
+ supplier: BusinessAPI.OrderSupplier,
+ weights: { used: number; extra: number; remain: number; extraUsed: number },
+ isFirstSupplier: boolean,
+ isLastSupplier: boolean
+ ): void {
+ const weightDiff = isFirstSupplier
+ ? DecimalUtils.subtract(supplier.totalWeight || 0, this.initialEmptyWeight)
+ : DecimalUtils.subtract(supplier.totalWeight || 0, this.previousTotalWeight);
+
+ const weightDiffInJin = DecimalUtils.multiply(weightDiff, 2);
+
+ if (isFirstSupplier && isLastSupplier) {
+ // 单个农户情况
+ supplier.netWeight = DecimalUtils.add(
+ DecimalUtils.subtract(
+ DecimalUtils.add(weightDiffInJin, weights.remain),
+ weights.extraUsed
+ )
+ );
+ } else if (isLastSupplier) {
+ // 最后一个农户
+ supplier.netWeight = DecimalUtils.add(
+ DecimalUtils.subtract(
+ weightDiffInJin,
+ weights.extraUsed
+ ),
+ weights.remain
+ );
+ } else {
+ // 中间农户(包括第一个但不是最后一个)
+ supplier.netWeight = DecimalUtils.subtract(weightDiffInJin, weights.extra);
+ }
+
+ // 毛重 = 净重 + 本次使用纸箱重量
+ supplier.grossWeight = DecimalUtils.add(
+ supplier.netWeight || 0,
+ weights.used
+ );
+ }
+
+ /**
+ * 计算发票金额
+ */
+ private calculateInvoiceAmount(supplier: BusinessAPI.OrderSupplier): void {
+ supplier.invoiceAmount = DecimalUtils.multiply(
+ supplier.netWeight || 0,
+ supplier.purchasePrice || 0
+ );
+ }
+
+ /**
+ * 根据类型计算纸箱总重量(斤)
+ */
+ private calculateBoxWeightByType(
+ packages: BusinessAPI.OrderPackage[],
+ boxType?: string
+ ): number {
+ const filteredPackages = boxType
+ ? packages.filter(pkg => pkg.boxType === boxType)
+ : packages;
+
+ return filteredPackages.reduce((total, pkg) => {
+ const weight = DecimalUtils.multiply(
+ pkg.boxCount || 0,
+ pkg.boxProductWeight || 0
+ );
+ return DecimalUtils.add(total, weight);
+ }, 0);
+ }
+
+ /**
+ * 获取计算后的供应商列表
+ */
+ getSuppliers(): BusinessAPI.OrderSupplier[] {
+ return this.suppliers;
+ }
+
+ /**
+ * 获取指定供应商的净重
+ */
+ getSupplierNetWeight(supplierId: string | number): number {
+ const supplier = this.suppliers.find(s => s.supplierId === supplierId);
+ return supplier?.netWeight || 0;
+ }
+
+ /**
+ * 获取指定供应商的毛重
+ */
+ getSupplierGrossWeight(supplierId: string | number): number {
+ const supplier = this.suppliers.find(s => s.supplierId === supplierId);
+ return supplier?.grossWeight || 0;
+ }
+
+ /**
+ * 获取总净重
+ */
+ getTotalNetWeight(): number {
+ return this.suppliers.reduce((total, supplier) => {
+ return DecimalUtils.add(total, supplier.netWeight || 0);
+ }, 0);
+ }
+
+ /**
+ * 获取总毛重
+ */
+ getTotalGrossWeight(): number {
+ return this.suppliers.reduce((total, supplier) => {
+ return DecimalUtils.add(total, supplier.grossWeight || 0);
+ }, 0);
+ }
+
+ /**
+ * 获取总发票金额
+ */
+ getTotalInvoiceAmount(): number {
+ return this.suppliers.reduce((total, supplier) => {
+ return DecimalUtils.add(total, supplier.invoiceAmount || 0);
+ }, 0);
+ }
+
+ /**
+ * 验证计算结果的合理性
+ */
+ validate(): { isValid: boolean; errors: string[] } {
+ const errors: string[] = [];
+
+ for (const supplier of this.suppliers) {
+ if ((supplier.netWeight || 0) < 0) {
+ errors.push(`供应商 ${supplier.name} 的净重为负数`);
+ }
+ if ((supplier.grossWeight || 0) < 0) {
+ errors.push(`供应商 ${supplier.name} 的毛重为负数`);
+ }
+ if ((supplier.grossWeight || 0) < (supplier.netWeight || 0)) {
+ errors.push(`供应商 ${supplier.name} 的毛重小于净重`);
+ }
+ }
+
+ return {
+ isValid: errors.length === 0,
+ errors
+ };
+ }
+}
diff --git a/packages/app-client/src/utils/classes/calculators/services/WeightCalculationService.ts b/packages/app-client/src/utils/classes/calculators/services/WeightCalculationService.ts
new file mode 100644
index 0000000..43ba68b
--- /dev/null
+++ b/packages/app-client/src/utils/classes/calculators/services/WeightCalculationService.ts
@@ -0,0 +1,61 @@
+import { SupplierWeightCalculator } from "../modules/SupplierWeightCalculator";
+
+/**
+ * 重量计算服务
+ * 统一管理供应商重量计算逻辑
+ */
+export class WeightCalculationService {
+ /**
+ * 计算供应商重量(静态方法)
+ * @param suppliers 供应商列表
+ * @returns 计算后的供应商列表
+ */
+ static calculateWeights(suppliers: BusinessAPI.OrderSupplier[]): BusinessAPI.OrderSupplier[] {
+ const calculator = new SupplierWeightCalculator(suppliers);
+ return calculator.calculate();
+ }
+
+ /**
+ * 创建重量计算器实例
+ * @param suppliers 供应商列表
+ * @returns 计算器实例
+ */
+ static createCalculator(suppliers: BusinessAPI.OrderSupplier[]): SupplierWeightCalculator {
+ return new SupplierWeightCalculator(suppliers);
+ }
+
+ /**
+ * 批量计算多个订单的重量
+ * @param orders 订单列表
+ * @returns 处理后的订单列表
+ */
+ static calculateBatchWeights(
+ orders: Array<{ orderSupplierList?: BusinessAPI.OrderSupplier[] }>
+ ): Array<{ orderSupplierList?: BusinessAPI.OrderSupplier[] }> {
+ return orders.map(order => {
+ if (order.orderSupplierList?.length) {
+ order.orderSupplierList = this.calculateWeights(order.orderSupplierList);
+ }
+ return order;
+ });
+ }
+
+ /**
+ * 获取重量统计信息
+ * @param suppliers 供应商列表
+ * @returns 统计信息
+ */
+ static getWeightStatistics(suppliers: BusinessAPI.OrderSupplier[]) {
+ const calculator = new SupplierWeightCalculator(suppliers);
+ calculator.calculate();
+
+ return {
+ supplierCount: suppliers.length,
+ totalNetWeight: calculator.getTotalNetWeight(),
+ totalGrossWeight: calculator.getTotalGrossWeight(),
+ totalInvoiceAmount: calculator.getTotalInvoiceAmount(),
+ averageNetWeight: calculator.getTotalNetWeight() / suppliers.length,
+ averageGrossWeight: calculator.getTotalGrossWeight() / suppliers.length,
+ };
+ }
+}
diff --git a/packages/app-client/src/utils/classes/index.ts b/packages/app-client/src/utils/classes/index.ts
index 9a5996b..eda3488 100644
--- a/packages/app-client/src/utils/classes/index.ts
+++ b/packages/app-client/src/utils/classes/index.ts
@@ -6,7 +6,7 @@
*/
// 计算器类
-export { PurchaseOrderCalculator, OrderSupplierCalculator, SupplierWeightCalculator } from './calculators'
+export { PurchaseOrderCalculator, WeightCalculationService } from './calculators'
// 模板类
-export { PdfTemplate } from './templates'
\ No newline at end of file
+export { PdfTemplate } from './templates'