From cbde9caac1472e61ca10f80869d77ed01dcf7d93 Mon Sep 17 00:00:00 2001 From: shenyifei Date: Mon, 17 Nov 2025 10:43:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(purchase):=20=E4=BC=98=E5=8C=96=E9=87=87?= =?UTF-8?q?=E8=B4=AD=E8=AE=A2=E5=8D=95=E8=B4=B9=E7=94=A8=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除调试日志输出 - 新增 requireQuantityAndPrice 字段支持 - 调整页面样式间距统一为 p-2.5 - 交换工头垫付与产地垫付显示逻辑 - 完善费用弹窗编辑功能,支持数量单价分别输入 - 增加新手引导 tour 功能 - 优化订单成本列表更新逻辑,避免重复项 - 导出新增的费用 section 组件 - 替换 LaborInfoSection为 WorkerAdvanceSection - 引入 ProductionAdvanceSection 组件 --- .../src/components/purchase/index.ts | 2 + .../components/purchase/module/OrderCost.tsx | 8 +- .../purchase/module/OrderVehicle.tsx | 6 +- .../purchase/section/BasicInfoSection.tsx | 6 +- .../purchase/section/LaborInfoSection.tsx | 766 +++++++++--------- .../purchase/section/MaterialCostSection.tsx | 310 ++++--- .../section/ProductionAdvanceSection.tsx | 741 +++++++++++++++++ .../purchase/section/WorkerAdvanceSection.tsx | 762 +++++++++++++++++ .../src/pages/purchase/purchaser/create.tsx | 93 ++- .../src/pages/purchase/reviewer/audit.tsx | 448 +++++----- .../src/services/business/typings.d.ts | 2 + 11 files changed, 2459 insertions(+), 685 deletions(-) create mode 100644 packages/app-client/src/components/purchase/section/ProductionAdvanceSection.tsx create mode 100644 packages/app-client/src/components/purchase/section/WorkerAdvanceSection.tsx diff --git a/packages/app-client/src/components/purchase/index.ts b/packages/app-client/src/components/purchase/index.ts index 03ce5e0..d77dd93 100644 --- a/packages/app-client/src/components/purchase/index.ts +++ b/packages/app-client/src/components/purchase/index.ts @@ -34,3 +34,5 @@ export { default as TaxSubsidySection } from "./section/TaxSubsidySection"; export { default as TaxProvisionSection } from "./section/TaxProvisionSection"; export { default as CostDifferenceSection } from "./section/CostDifferenceSection"; export { default as MaterialCostSection } from "./section/MaterialCostSection"; +export { default as ProductionAdvanceSection } from "./section/ProductionAdvanceSection"; +export { default as WorkerAdvanceSection } from "./section/WorkerAdvanceSection"; diff --git a/packages/app-client/src/components/purchase/module/OrderCost.tsx b/packages/app-client/src/components/purchase/module/OrderCost.tsx index a0a743b..ce3b398 100644 --- a/packages/app-client/src/components/purchase/module/OrderCost.tsx +++ b/packages/app-client/src/components/purchase/module/OrderCost.tsx @@ -53,8 +53,6 @@ export default forwardRef( (costItem) => costItem.itemId === item.itemId, ); - console.log("existingItem", existingItem, value) - return { orderCostId: existingItem ? existingItem.orderCostId @@ -68,6 +66,9 @@ export default forwardRef( payerType: existingItem ? existingItem.payerType : undefined, principal: existingItem ? existingItem.principal : "", costType: item.costType, + requireQuantityAndPrice: existingItem + ? existingItem.requireQuantityAndPrice + : false, }; }) || []; @@ -91,6 +92,7 @@ export default forwardRef( payerType: item.payerType, principal: item.principal, costType: item.costType, + requireQuantityAndPrice: item.requireQuantityAndPrice, }; }) || []), ]); @@ -421,7 +423,7 @@ export default forwardRef( }; return ( - + 人工费用 diff --git a/packages/app-client/src/components/purchase/module/OrderVehicle.tsx b/packages/app-client/src/components/purchase/module/OrderVehicle.tsx index 0ca8c41..f9583af 100644 --- a/packages/app-client/src/components/purchase/module/OrderVehicle.tsx +++ b/packages/app-client/src/components/purchase/module/OrderVehicle.tsx @@ -537,7 +537,10 @@ export default forwardRef( className="min-h-20 w-full !border !border-none p-2.5 !text-lg" placeholder="「粘贴识别」获输入文本,智能拆分车牌号、司机姓名、联系电话、出发地、目的地和运费" /> - + {text ? ( - - ) : ( - <> - - 金额 + + - - { - const numValue = validatePrice(value); - if (numValue !== undefined) { - setTempEditValues((prev) => ({ - ...prev, - [item.orderCostId]: { - ...prev[item.orderCostId], - price: numValue as number, - count: 1, - }, - })); + + - - )} - - - - - - - - - - - - - - - )})} + + + ); + })} {/* 工头垫付 */} {workerAdvanceCosts.map((item) => { // 获取费用项目的 requireQuantityAndPrice 属性 const costItem = costItems.find((cost) => cost.itemId === item.itemId); - const requireQuantityAndPrice = costItem?.requireQuantityAndPrice ?? false; + const requireQuantityAndPrice = + costItem?.requireQuantityAndPrice ?? false; return ( - - setVisiblePopup((prev) => ({ ...prev, [item.orderCostId]: false })) - } - onOverlayClick={() => - setVisiblePopup((prev) => ({ ...prev, [item.orderCostId]: false })) - } - lockScroll - > - - {requireQuantityAndPrice ? ( - <> - - 人数 - - - { - const numValue = validatePrice(value); - if (numValue !== undefined) { - setTempEditValues((prev) => ({ - ...prev, - [item.orderCostId]: { - ...prev[item.orderCostId], - count: numValue as number, - }, - })); + + setVisiblePopup((prev) => ({ + ...prev, + [item.orderCostId]: false, + })) + } + onOverlayClick={() => + setVisiblePopup((prev) => ({ + ...prev, + [item.orderCostId]: false, + })) + } + lockScroll + > + + {requireQuantityAndPrice ? ( + <> + + 人数 + + + - - + onChange={(value) => { + const numValue = validatePrice(value); + if (numValue !== undefined) { + setTempEditValues((prev) => ({ + ...prev, + [item.orderCostId]: { + ...prev[item.orderCostId], + count: numValue as number, + }, + })); + } + }} + /> + + - - 单价 - - - { - const numValue = validatePrice(value); - if (numValue !== undefined) { - setTempEditValues((prev) => ({ - ...prev, - [item.orderCostId]: { - ...prev[item.orderCostId], - price: numValue as number, - }, - })); + + 单价 + + + { + const numValue = validatePrice(value); + if (numValue !== undefined) { + setTempEditValues((prev) => ({ + ...prev, + [item.orderCostId]: { + ...prev[item.orderCostId], + price: numValue as number, + }, + })); + } + }} + /> + + + + ) : ( + <> + + 金额 + + + { + const numValue = validatePrice(value); + if (numValue !== undefined) { + setTempEditValues((prev) => ({ + ...prev, + [item.orderCostId]: { + ...prev[item.orderCostId], + price: numValue as number, + count: 1, + }, + })); + } + }} + /> + + + + )} + + + + + - - ) : ( - <> - - 金额 + + - - { - const numValue = validatePrice(value); - if (numValue !== undefined) { - setTempEditValues((prev) => ({ - ...prev, - [item.orderCostId]: { - ...prev[item.orderCostId], - price: numValue as number, - count: 1, - }, - })); + + - - )} - - - - - - - - - - - - - - - )})} + + + ); + })} ); } diff --git a/packages/app-client/src/components/purchase/section/MaterialCostSection.tsx b/packages/app-client/src/components/purchase/section/MaterialCostSection.tsx index 09a0ed4..13a70be 100644 --- a/packages/app-client/src/components/purchase/section/MaterialCostSection.tsx +++ b/packages/app-client/src/components/purchase/section/MaterialCostSection.tsx @@ -606,123 +606,207 @@ export default function MaterialCostSection(props: { {/* 辅料费编辑弹窗 */} - {packagingMaterials.map((item) => ( - - setVisiblePopup((prev) => ({ ...prev, [item.orderCostId]: false })) - } - onOverlayClick={() => - setVisiblePopup((prev) => ({ ...prev, [item.orderCostId]: false })) - } - lockScroll - > - - - 数量 - - - { - const numValue = validatePrice(value); - if (numValue !== undefined) { - setTempEditValues((prev) => ({ - ...prev, - [item.orderCostId]: { - ...prev[item.orderCostId], - count: numValue as number, - }, - })); - } - }} - /> - {item.unit} - + {packagingMaterials.map((item) => { + // 获取费用项目的 requireQuantityAndPrice 属性 + const costItem = costItems.find((cost) => cost.itemId === item.itemId); + const requireQuantityAndPrice = + costItem?.requireQuantityAndPrice ?? false; - - 单价 - - - { - const numValue = validatePrice(value); - if (numValue !== undefined) { - setTempEditValues((prev) => ({ - ...prev, - [item.orderCostId]: { - ...prev[item.orderCostId], - price: numValue as number, - }, - })); - } - }} - /> - - - - - - - - - - + + 单价 + + + { + const numValue = validatePrice(value); + if (numValue !== undefined) { + setTempEditValues((prev) => ({ + ...prev, + [item.orderCostId]: { + ...prev[item.orderCostId], + price: numValue as number, + }, + })); + } + }} + /> + + + + ) : ( + <> + + 金额 + + + { + const numValue = validatePrice(value); + if (numValue !== undefined) { + setTempEditValues((prev) => ({ + ...prev, + [item.orderCostId]: { + ...prev[item.orderCostId], + price: numValue as number, + count: 1, + }, + })); + } + }} + /> + + + + )} + + + + + + + + + + + + - - - - ))} + + + ); + })} ); } diff --git a/packages/app-client/src/components/purchase/section/ProductionAdvanceSection.tsx b/packages/app-client/src/components/purchase/section/ProductionAdvanceSection.tsx new file mode 100644 index 0000000..f1cb07b --- /dev/null +++ b/packages/app-client/src/components/purchase/section/ProductionAdvanceSection.tsx @@ -0,0 +1,741 @@ +import { ScrollView, View } from "@tarojs/components"; +import { + Button, + Input, + Picker, + Popup, + SafeArea, +} from "@nutui/nutui-react-taro"; +import { useEffect, useState } from "react"; +import { Icon } from "@/components"; +import { business } from "@/services"; +import { formatCurrency, validatePrice } from "@/utils/format"; +import { generateShortId } from "@/utils/generateShortId"; + +export default function ProductionAdvanceSection(props: { + purchaseOrderVO: BusinessAPI.PurchaseOrderVO; + onChange?: (purchaseOrderVO: BusinessAPI.PurchaseOrderVO) => void; + readOnly?: boolean; +}) { + const { purchaseOrderVO, onChange, readOnly } = props; + + // 弹窗相关状态 + const [visiblePopup, setVisiblePopup] = useState<{ [key: string]: boolean }>( + {}, + ); + + // 新增人工费弹窗状态 + const [showAddCostPopup, setShowAddCostPopup] = useState(false); + + // 新增人工费表单数据 + const [newCostData, setNewCostData] = useState({ + costType: "PRODUCTION_ADVANCE", // 费用类型 + itemId: "", // 费用项目ID + name: "", // 费用名称 + quantity: 0, // 数量(人数) + unit: "", // 单位 + unitPrice: "", // 单价 + amount: "", // 金额 + payerType: "US", // 费用承担方,默认我方 + principal: "", // 工头姓名 + requireQuantityAndPrice: false, + }); + + console.log("newCostData", newCostData); + + // Picker可见状态 + const [pickerVisible, setPickerVisible] = useState({ + costType: false, // 费用类型Picker + costItem: false, // 费用项目Picker + }); + + // 费用项目列表 + const [costItems, setCostItems] = useState([]); + + // 编辑值的状态 + const [editValues, setEditValues] = useState<{ + [key: string]: { + count: number; + price: number; + name?: string; + principal?: string; + payerType?: "US" | "OTHER"; + }; + }>({}); + + // 临时编辑值的状态(用于在保存前暂存编辑的值) + const [tempEditValues, setTempEditValues] = useState<{ + [key: string]: { + count: number; + price: number; + name?: string; + principal?: string; + payerType?: "US" | "OTHER"; + }; + }>({}); + + // 初始化编辑值 + const initEditValues = ( + itemId: string, + count: number, + price: number, + name?: string, + principal?: string, + payerType?: "US" | "OTHER", + ) => { + if (!editValues[itemId]) { + setEditValues((prev) => ({ + ...prev, + [itemId]: { count, price, name, principal, payerType }, + })); + } + + // 同时初始化临时编辑值 + if (!tempEditValues[itemId]) { + setTempEditValues((prev) => ({ + ...prev, + [itemId]: { count, price, name, principal, payerType }, + })); + } + }; + + // 获取费用项目列表 + useEffect(() => { + const fetchCostItems = async () => { + try { + const { data } = await business.costItem.listCostItem({ + costItemListQry: { + status: true, + showInEntry: true, + }, + }); + setCostItems(data.data || []); + } catch (error) { + console.error("获取费用项目列表失败:", error); + } + }; + + fetchCostItems(); + }, []); + + // 产地垫付 + const productionAdvanceCosts = + purchaseOrderVO.orderCostList?.filter( + (item) => item.costType === "PRODUCTION_ADVANCE", + ) || []; + + // 当editValues发生变化时,更新purchaseOrderVO + useEffect(() => { + // 只有当onChange存在时才更新 + if (onChange) { + // 获取现有的orderCostList + const existingOrderCostList = purchaseOrderVO.orderCostList || []; + + // 更新已有的成本项 + const updatedOrderCostList = existingOrderCostList.map((item) => { + if (editValues[item.orderCostId]) { + return { + ...item, + price: + editValues[item.orderCostId].price !== undefined + ? editValues[item.orderCostId].price + : item.price, + count: + editValues[item.orderCostId].count !== undefined + ? editValues[item.orderCostId].count + : item.count, + name: + editValues[item.orderCostId].name !== undefined + ? editValues[item.orderCostId].name + : item.name, + principal: + editValues[item.orderCostId].principal !== undefined + ? editValues[item.orderCostId].principal + : item.principal, + payerType: + editValues[item.orderCostId].payerType !== undefined + ? editValues[item.orderCostId].payerType + : item.payerType, + }; + } + // 未修改的成本项保持原样 + return item; + }); + + // 创建新的purchaseOrderVO对象 + const newPurchaseOrderVO = { + ...purchaseOrderVO, + orderCostList: updatedOrderCostList, + }; + + // 调用onChange回调 + onChange(newPurchaseOrderVO as any); + } + }, [editValues]); + + return ( + <> + + + + 产地负责人:{purchaseOrderVO.originPrincipal} + + {/* 产地垫付 */} + {productionAdvanceCosts.map((item, index) => { + // 初始化编辑值,包括费用承担方和工头姓名 + initEditValues( + item.orderCostId, + item.count, + item.price, + item.name, + item.principal, + item.payerType, + ); + + return ( + + setVisiblePopup((prev) => ({ + ...prev, + [item.orderCostId]: true, + })) + } + > + + {index + 1}-{item.name} + {item.principal && `(${item.principal})`} + + + {formatCurrency( + Number( + (editValues[item.orderCostId]?.price || item.price) * + (editValues[item.orderCostId]?.count || item.count), + ), + )} + {!readOnly && ( + + + + )} + + + ); + })} + + + {!readOnly && ( + + )} + + + {/* 新增其他产地垫付费用 */} + setShowAddCostPopup(false)} + onOverlayClick={() => setShowAddCostPopup(false)} + lockScroll + > + + + {/* 垫付项目 */} + {newCostData.costType && ( + <> + + 垫付项目 + + + setPickerVisible((prev) => ({ ...prev, costItem: true })) + } + > + + + + item.costType === newCostData.costType) + .filter((item) => { + // 检查该项目是否已经被选择 + return !productionAdvanceCosts.some( + (hc) => hc.itemId === item.itemId, + ); + }) + .map((item) => ({ + label: item.name, + value: item.itemId, + })), + ]} + onConfirm={(_, values) => { + const selectedValue = values[0] as string; + const selectedItem = costItems.find( + (item) => item.itemId === selectedValue, + ); + if (selectedItem) { + setNewCostData((prev) => ({ + ...prev, + itemId: selectedValue, + name: selectedItem.name, + payerType: "US", + quantity: selectedItem.requireQuantityAndPrice ? 0 : 1, // 数量(人数) + unit: selectedItem.requireQuantityAndPrice + ? selectedItem.unit + : "元", // 单位 + unitPrice: "", // 单价 + amount: "", // 金额 + principal: "", // 工头姓名 + requireQuantityAndPrice: + selectedItem.requireQuantityAndPrice, + })); + } + setPickerVisible((prev) => ({ ...prev, costItem: false })); + }} + onCancel={() => + setPickerVisible((prev) => ({ ...prev, costItem: false })) + } + onClose={() => + setPickerVisible((prev) => ({ ...prev, costItem: false })) + } + /> + + )} + + {/* 数量输入 */} + {newCostData.itemId && + newCostData.costType && + newCostData.requireQuantityAndPrice && ( + <> + + 数量 + + + { + const numValue = validatePrice(value); + if (numValue !== undefined) { + setNewCostData((prev) => ({ + ...prev, + quantity: numValue as any, + })); + } + }} + /> + {newCostData.unit} + + + {/* 单价输入 */} + + 单价 + + + { + const numValue = validatePrice(value); + if (numValue !== undefined) { + setNewCostData((prev) => ({ + ...prev, + unitPrice: numValue as any, + })); + } + }} + /> + + + + {/* 金额展示 */} + + + 金额 + + + {formatCurrency( + Number(newCostData.quantity || 0) * + Number(newCostData.unitPrice || 0), + )}{" "} + 元 + + + + )} + + {newCostData.itemId && + newCostData.costType && + !newCostData.requireQuantityAndPrice && ( + <> + + 金额 + + + { + const numValue = validatePrice(value); + if (numValue !== undefined) { + setNewCostData((prev) => ({ + ...prev, + unitPrice: numValue as any, + amount: numValue as any, + })); + } + }} + /> + + + + )} + + + + + + + + + + + + + + + + {/* 产地垫付 */} + {productionAdvanceCosts.map((item) => { + return ( + + setVisiblePopup((prev) => ({ + ...prev, + [item.orderCostId]: false, + })) + } + onOverlayClick={() => + setVisiblePopup((prev) => ({ + ...prev, + [item.orderCostId]: false, + })) + } + lockScroll + > + + {item?.requireQuantityAndPrice ? ( + <> + + 人数 + + + { + const numValue = validatePrice(value); + if (numValue !== undefined) { + setTempEditValues((prev) => ({ + ...prev, + [item.orderCostId]: { + ...prev[item.orderCostId], + count: numValue as number, + }, + })); + } + }} + /> + + + + + 单价 + + + { + const numValue = validatePrice(value); + if (numValue !== undefined) { + setTempEditValues((prev) => ({ + ...prev, + [item.orderCostId]: { + ...prev[item.orderCostId], + price: numValue as number, + }, + })); + } + }} + /> + + + + ) : ( + <> + + 金额 + + + { + const numValue = validatePrice(value); + if (numValue !== undefined) { + setTempEditValues((prev) => ({ + ...prev, + [item.orderCostId]: { + ...prev[item.orderCostId], + price: numValue as number, + count: 1, + }, + })); + } + }} + /> + + + + )} + + + + + + + + + + + + + + + + + ); + })} + + ); +} diff --git a/packages/app-client/src/components/purchase/section/WorkerAdvanceSection.tsx b/packages/app-client/src/components/purchase/section/WorkerAdvanceSection.tsx new file mode 100644 index 0000000..c617030 --- /dev/null +++ b/packages/app-client/src/components/purchase/section/WorkerAdvanceSection.tsx @@ -0,0 +1,762 @@ +import { ScrollView, View } from "@tarojs/components"; +import { + Button, + Input, + Picker, + Popup, + SafeArea, +} from "@nutui/nutui-react-taro"; +import { useEffect, useState } from "react"; +import { Icon } from "@/components"; +import { business } from "@/services"; +import { formatCurrency, validatePrice } from "@/utils/format"; +import { generateShortId } from "@/utils/generateShortId"; + +export default function WorkerAdvanceSection(props: { + purchaseOrderVO: BusinessAPI.PurchaseOrderVO; + onChange?: (purchaseOrderVO: BusinessAPI.PurchaseOrderVO) => void; + readOnly?: boolean; +}) { + const { purchaseOrderVO, onChange, readOnly } = props; + + // 弹窗相关状态 + const [visiblePopup, setVisiblePopup] = useState<{ [key: string]: boolean }>( + {}, + ); + + // 新增人工费弹窗状态 + const [showAddCostPopup, setShowAddCostPopup] = useState(false); + + // 新增人工费表单数据 + const [newCostData, setNewCostData] = useState({ + costType: "WORKER_ADVANCE", // 费用类型 + itemId: "", // 费用项目ID + name: "", // 费用名称 + quantity: 0, // 数量(人数) + unit: "", // 单位 + unitPrice: "", // 单价 + amount: "", // 金额 + payerType: "US", // 费用承担方,默认我方 + principal: "", // 工头姓名 + requireQuantityAndPrice: false, + }); + + console.log("newCostData", newCostData); + + // Picker可见状态 + const [pickerVisible, setPickerVisible] = useState({ + costType: false, // 费用类型Picker + costItem: false, // 费用项目Picker + }); + + // 费用项目列表 + const [costItems, setCostItems] = useState([]); + + // 编辑值的状态 + const [editValues, setEditValues] = useState<{ + [key: string]: { + count: number; + price: number; + name?: string; + principal?: string; + payerType?: "US" | "OTHER"; + }; + }>({}); + + // 临时编辑值的状态(用于在保存前暂存编辑的值) + const [tempEditValues, setTempEditValues] = useState<{ + [key: string]: { + count: number; + price: number; + name?: string; + principal?: string; + payerType?: "US" | "OTHER"; + }; + }>({}); + + // 初始化编辑值 + const initEditValues = ( + itemId: string, + count: number, + price: number, + name?: string, + principal?: string, + payerType?: "US" | "OTHER", + ) => { + if (!editValues[itemId]) { + setEditValues((prev) => ({ + ...prev, + [itemId]: { count, price, name, principal, payerType }, + })); + } + + // 同时初始化临时编辑值 + if (!tempEditValues[itemId]) { + setTempEditValues((prev) => ({ + ...prev, + [itemId]: { count, price, name, principal, payerType }, + })); + } + }; + + // 获取费用项目列表 + useEffect(() => { + const fetchCostItems = async () => { + try { + const { data } = await business.costItem.listCostItem({ + costItemListQry: { + status: true, + showInEntry: true, + }, + }); + setCostItems(data.data || []); + } catch (error) { + console.error("获取费用项目列表失败:", error); + } + }; + + fetchCostItems(); + }, []); + + // 人工费 + const humanCosts = + purchaseOrderVO.orderCostList?.filter( + (item) => + item.costType === "HUMAN_COST" && + item.payerType === "US" && + item.count > 0, + ) || []; + + // 工头垫付 + const workerAdvanceCosts = + purchaseOrderVO.orderCostList?.filter( + (item) => item.costType === "WORKER_ADVANCE", + ) || []; + + // 当editValues发生变化时,更新purchaseOrderVO + useEffect(() => { + // 只有当onChange存在时才更新 + if (onChange) { + // 获取现有的orderCostList + const existingOrderCostList = purchaseOrderVO.orderCostList || []; + + // 更新已有的成本项 + const updatedOrderCostList = existingOrderCostList.map((item) => { + if (editValues[item.orderCostId]) { + return { + ...item, + price: + editValues[item.orderCostId].price !== undefined + ? editValues[item.orderCostId].price + : item.price, + count: + editValues[item.orderCostId].count !== undefined + ? editValues[item.orderCostId].count + : item.count, + name: + editValues[item.orderCostId].name !== undefined + ? editValues[item.orderCostId].name + : item.name, + principal: + editValues[item.orderCostId].principal !== undefined + ? editValues[item.orderCostId].principal + : item.principal, + payerType: + editValues[item.orderCostId].payerType !== undefined + ? editValues[item.orderCostId].payerType + : item.payerType, + }; + } + // 未修改的成本项保持原样 + return item; + }); + + // 创建新的purchaseOrderVO对象 + const newPurchaseOrderVO = { + ...purchaseOrderVO, + orderCostList: updatedOrderCostList, + }; + + // 调用onChange回调 + onChange(newPurchaseOrderVO as any); + } + }, [editValues]); + + return ( + <> + + + + + 工头:{humanCosts?.[0].principal} + + + {humanCosts.map((item, index) => { + return ( + {`${item.name} ${item.count}人`} + ); + })} + + + {/* 工头垫付 */} + {workerAdvanceCosts.map((item, index) => { + // 初始化编辑值,包括费用承担方和工头姓名 + initEditValues( + item.orderCostId, + item.count, + item.price, + item.name, + item.principal, + item.payerType, + ); + + return ( + + setVisiblePopup((prev) => ({ + ...prev, + [item.orderCostId]: true, + })) + } + > + + {index + 1}-{item.name} + {item.principal && `(${item.principal})`} + + + {formatCurrency( + Number( + (editValues[item.orderCostId]?.price || item.price) * + (editValues[item.orderCostId]?.count || item.count), + ), + )} + {!readOnly && ( + + + + )} + + + ); + })} + + + {!readOnly && ( + + )} + + + {/* 新增其他工头垫付费用 */} + setShowAddCostPopup(false)} + onOverlayClick={() => setShowAddCostPopup(false)} + lockScroll + > + + + {/* 垫付项目 */} + {newCostData.costType && ( + <> + + 垫付项目 + + + setPickerVisible((prev) => ({ ...prev, costItem: true })) + } + > + + + + item.costType === newCostData.costType) + .filter((item) => { + return !workerAdvanceCosts.some( + (hc) => hc.itemId === item.itemId, + ); + }) + .map((item) => ({ + label: item.name, + value: item.itemId, + })), + ]} + onConfirm={(_, values) => { + const selectedValue = values[0] as string; + const selectedItem = costItems.find( + (item) => item.itemId === selectedValue, + ); + if (selectedItem) { + setNewCostData((prev) => ({ + ...prev, + itemId: selectedValue, + name: selectedItem.name, + payerType: "US", + quantity: selectedItem.requireQuantityAndPrice ? 0 : 1, // 数量(人数) + unit: selectedItem.requireQuantityAndPrice + ? selectedItem.unit + : "元", // 单位 + unitPrice: "", // 单价 + amount: "", // 金额 + principal: "", // 工头姓名 + requireQuantityAndPrice: + selectedItem.requireQuantityAndPrice, + })); + } + setPickerVisible((prev) => ({ ...prev, costItem: false })); + }} + onCancel={() => + setPickerVisible((prev) => ({ ...prev, costItem: false })) + } + onClose={() => + setPickerVisible((prev) => ({ ...prev, costItem: false })) + } + /> + + )} + + {/* 数量输入 */} + {newCostData.itemId && + newCostData.costType && + newCostData.requireQuantityAndPrice && ( + <> + + 数量 + + + { + const numValue = validatePrice(value); + if (numValue !== undefined) { + setNewCostData((prev) => ({ + ...prev, + quantity: numValue as any, + })); + } + }} + /> + {newCostData.unit} + + + {/* 单价输入 */} + + 单价 + + + { + const numValue = validatePrice(value); + if (numValue !== undefined) { + setNewCostData((prev) => ({ + ...prev, + unitPrice: numValue as any, + })); + } + }} + /> + + + + {/* 金额展示 */} + + + 金额 + + + {formatCurrency( + Number(newCostData.quantity || 0) * + Number(newCostData.unitPrice || 0), + )}{" "} + 元 + + + + )} + + {newCostData.itemId && + newCostData.costType && + !newCostData.requireQuantityAndPrice && ( + <> + + 金额 + + + { + const numValue = validatePrice(value); + if (numValue !== undefined) { + setNewCostData((prev) => ({ + ...prev, + unitPrice: numValue as any, + amount: numValue as any, + })); + } + }} + /> + + + + )} + + + + + + + + + + + + + + + + {/* 工头垫付 */} + {workerAdvanceCosts.map((item) => { + return ( + + setVisiblePopup((prev) => ({ + ...prev, + [item.orderCostId]: false, + })) + } + onOverlayClick={() => + setVisiblePopup((prev) => ({ + ...prev, + [item.orderCostId]: false, + })) + } + lockScroll + > + + {item?.requireQuantityAndPrice ? ( + <> + + 人数 + + + { + const numValue = validatePrice(value); + if (numValue !== undefined) { + setTempEditValues((prev) => ({ + ...prev, + [item.orderCostId]: { + ...prev[item.orderCostId], + count: numValue as number, + }, + })); + } + }} + /> + + + + + 单价 + + + { + const numValue = validatePrice(value); + if (numValue !== undefined) { + setTempEditValues((prev) => ({ + ...prev, + [item.orderCostId]: { + ...prev[item.orderCostId], + price: numValue as number, + }, + })); + } + }} + /> + + + + ) : ( + <> + + 金额 + + + { + const numValue = validatePrice(value); + if (numValue !== undefined) { + setTempEditValues((prev) => ({ + ...prev, + [item.orderCostId]: { + ...prev[item.orderCostId], + price: numValue as number, + count: 1, + }, + })); + } + }} + /> + + + + )} + + + + + + + + + + + + + + + + + ); + })} + + ); +} diff --git a/packages/app-client/src/pages/purchase/purchaser/create.tsx b/packages/app-client/src/pages/purchase/purchaser/create.tsx index 47e06bf..5765b41 100644 --- a/packages/app-client/src/pages/purchase/purchaser/create.tsx +++ b/packages/app-client/src/pages/purchase/purchaser/create.tsx @@ -1,7 +1,14 @@ import hocAuth from "@/hocs/auth"; import { CommonComponent, CostItem, SupplierVO } from "@/types/typings"; import { View } from "@tarojs/components"; -import { Button, Dialog, SafeArea, Toast } from "@nutui/nutui-react-taro"; +import { + Button, + Dialog, + SafeArea, + Toast, + Tour, + TourList, +} from "@nutui/nutui-react-taro"; import { purchase } from "@/constant"; import { useEffect, useRef, useState } from "react"; import { @@ -61,6 +68,26 @@ export default hocAuth(function Page(props: CommonComponent) { const defaultStep = router.params.step as number; const defaultSupplierId = router.params.supplierId as string; + const [showTour, setShowTour] = useState(true); + + const closeTour = () => { + setShowTour(false); + }; + + const steps: TourList[] = [ + { + content: "粘贴识别", + target: "target1", + popoverOffset: [0, 12], + arrowOffset: 0, + }, + { + content: "选择经销商", + target: "target2", + location: "bottom-left", + }, + ]; + const vehicleRef = useRef(null); // 创建MelonFarmer组件的ref数组 const melonFarmerRefs = useRef([]); @@ -622,29 +649,54 @@ export default hocAuth(function Page(props: CommonComponent) { }), ]); setPurchaseOrder((prev) => { + const costItemVOList = productVO.costItemVOList; + + // 将 orderCostList 中 不存在与 costItemVOList 的项 删除,剩余项保留 + const orderCostList = prev?.orderCostList.filter((item) => { + return ( + (item.costType === "WORKER_ADVANCE" || + item.costType === "PRODUCTION_ADVANCE" || + item.costType === "PACKAGING_MATERIALS") && + costItemVOList?.find( + (costItemVO) => costItemVO.itemId === item.itemId, + ) + ); + }); + + // 添加 costItemVOList 中 不存在与 orderCostList 的项 + costItemVOList?.forEach((item) => { + if ( + !orderCostList?.find( + (costItemVO) => costItemVO.itemId === item.itemId, + ) + ) { + orderCostList?.push({ + orderCostId: generateShortId(), + orderId: purchaseOrder?.orderId, + itemId: item.itemId, + name: item.name, + price: item.price, + unit: item.unit, + count: 0, + payerType: "US" as any, + principal: "", + costType: item.costType, + requireQuantityAndPrice: item.requireQuantityAndPrice, + }); + } + }); + return { ...prev!, orderCostList: [ ...(prev?.orderCostList?.filter((item) => { return ( item.costType !== "WORKER_ADVANCE" && - item.costType !== "PRODUCTION_ADVANCE" + item.costType !== "PRODUCTION_ADVANCE" && + item.costType !== "PACKAGING_MATERIALS" ); }) || []), - ...(productVO.costItemVOList?.map((item) => { - return { - orderCostId: generateShortId(), - orderId: purchaseOrder?.orderId, - itemId: item.itemId, - name: item.name, - price: item.price, - unit: item.unit, - count: 0, - payerType: "US" as any, - principal: "", - costType: item.costType, - }; - }) || []), + ...(orderCostList || []), ], }; }); @@ -866,6 +918,15 @@ export default hocAuth(function Page(props: CommonComponent) { + + ); }); diff --git a/packages/app-client/src/pages/purchase/reviewer/audit.tsx b/packages/app-client/src/pages/purchase/reviewer/audit.tsx index 971a34c..e81abff 100644 --- a/packages/app-client/src/pages/purchase/reviewer/audit.tsx +++ b/packages/app-client/src/pages/purchase/reviewer/audit.tsx @@ -5,13 +5,7 @@ import { business } from "@/services"; import { useEffect, useState } from "react"; import { View } from "@tarojs/components"; import purchaseOrder from "@/constant/purchaseOrder"; -import { - Button, - Dialog, - Input, - SafeArea, - Toast, -} from "@nutui/nutui-react-taro"; +import { ActionSheet, Button, Dialog, Input, Popup, SafeArea, TextArea, Toast } from "@nutui/nutui-react-taro"; import { BasicInfoSection, CompanyInfoSection, @@ -19,16 +13,16 @@ import { CostSummarySection, DealerInfoSection, EmptyBoxInfoSection, - LaborInfoSection, MarketPriceSection, MaterialCostSection, PackageInfoSection, PackagingCostSection, - PurchaseOrderRejectApprove, + ProductionAdvanceSection, RebateCalcSection, State, TaxProvisionSection, TaxSubsidySection, + WorkerAdvanceSection } from "@/components"; import buildUrl from "@/utils/buildUrl"; import { PurchaseOrderCalculator } from "@/utils/PurchaseOrderCalculator"; @@ -38,113 +32,86 @@ const sections = { marketPrice: { component: MarketPriceSection, title: "市场报价", - containerClass: "border-l-8 border-l-lime-500", - contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto", }, // 我方入账公司 supplierInfo: { component: CompanyInfoSection, title: "我方入账公司", - containerClass: "border-l-8 border-l-blue-500", - contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto", }, // 下游经销商信息 dealerInfo: { component: DealerInfoSection, title: "下游经销商", - containerClass: "border-l-8 border-l-green-500", - contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto", }, // 基础信息 basicInfo: { component: BasicInfoSection, title: "基础信息", - containerClass: "border-l-8 border-l-yellow-500", - contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto", }, // // 瓜农信息 // farmerInfo: { // component: SupplierInfoSection, // title: "瓜农信息", - // containerClass: "border-l-8 border-l-purple-500", - // contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto", // }, // // 采购成本 // purchaseCostInfo: { // component: PurchaseCostInfoSection, // title: "采购成本", - // containerClass: "border-l-8 border-l-red-500", - // contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto", // }, // 包装纸箱费 packageInfo: { component: PackageInfoSection, title: "包装纸箱费复核", - containerClass: "border-l-8 border-l-indigo-500", - contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto", }, // 空箱费用 emptyBoxInfo: { component: EmptyBoxInfoSection, title: "空箱费用复核", - containerClass: "border-l-8 border-l-pink-500", - contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto", }, - // 用工信息 - laborInfo: { - component: LaborInfoSection, - title: "人工费复核", - containerClass: "border-l-8 border-l-teal-500", - contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto", + // 工头垫付费用复核 + foremanAdvance: { + component: WorkerAdvanceSection, + title: "工头垫付费用复核", + }, + // 产地垫付费用复核 + originAdvance: { + component: ProductionAdvanceSection, + title: "产地垫付费用复核", }, // 辅料费用复核 materialCost: { component: MaterialCostSection, title: "辅料费复核", - containerClass: "border-l-8 border-l-orange-500", - contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto", }, // 其他费用复核 otherCost: { component: PackagingCostSection, title: "其他费用复核", - containerClass: "border-l-8 border-l-orange-500", - contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto", }, // 成本合计 costSummary: { component: CostSummarySection, title: "成本合计", - containerClass: "border-l-8 border-l-cyan-500", - contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto", }, // 个人返点复核 rebateCalc: { component: RebateCalcSection, title: "个人返点复核", - containerClass: "border-l-8 border-l-amber-500", - contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto", }, // 公司返点复核 taxSubsidy: { component: TaxSubsidySection, title: "公司返点复核", - containerClass: "border-l-8 border-l-amber-500", - contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto", }, // 计提税金复核 taxProvision: { component: TaxProvisionSection, title: "计提税金复核", - containerClass: "border-l-8 border-l-amber-500", - contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto", }, // 调诚信志远分红 costDifference: { component: CostDifferenceSection, title: "调诚信志远分红", - containerClass: "border-l-8 border-l-amber-500", - contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto", }, }; @@ -157,16 +124,75 @@ export default hocAuth(function Page(props: CommonComponent) { const [purchaseOrderVO, setPurchaseOrderVO] = useState(); - const [originPrincipal, setOriginPrincipal] = useState(""); + // 驳回原因弹窗相关状态 + const [rejectVisible, setRejectVisible] = useState(false); + const [rejectReason, setRejectReason] = useState(""); - const [collapsedSections, setCollapsedSections] = useState< - Record - >(Object.keys(sections).reduce((acc, key) => ({ ...acc, [key]: false }), {})); + // 驳回操作 + const handleReject = () => { + setRejectVisible(true); + }; + + // 确认驳回 + const confirmReject = async () => { + if (!purchaseOrderVO) { + Toast.show("toast", { + icon: "fail", + title: "提示", + content: "请稍后再试", + }); + return; + } + + if (!rejectReason.trim()) { + Toast.show("toast", { + icon: "fail", + title: "提示", + content: "请输入驳回原因", + }); + return; + } + + try { + // 调用驳回API + await business.purchaseOrder.rejectApprovePurchaseOrder({ + orderId: purchaseOrderVO?.orderId, + rejectReason: rejectReason, + }); + + Toast.show("toast", { + icon: "success", + title: "提示", + content: "驳回成功,正在跳转我的审核...", + }); + + // 关闭弹窗 + setRejectVisible(false); + setRejectReason(""); + + setTimeout(() => { + Taro.redirectTo({ url: buildUrl("/pages/purchase/reviewer/list") }); + }, 1000); + + // 返回采购单页面 + } catch (error) { + Toast.show("toast", { + icon: "fail", + title: "提示", + content: error.message || "驳回失败", + }); + } + }; + + const [originPrincipal, setOriginPrincipal] = useState(""); // 暂存和提交审核的Dialog状态 const [saveDialogVisible, setSaveDialogVisible] = useState(false); const [submitDialogVisible, setSubmitDialogVisible] = useState(false); + // 控制更多操作的ActionSheet显示状态 + const [moreActionVisible, setMoreActionVisible] = useState(false); + const [dealerVO, setDealerVO] = useState(); const initDealer = async (dealerId: BusinessAPI.DealerVO["dealerId"]) => { @@ -180,13 +206,6 @@ export default hocAuth(function Page(props: CommonComponent) { setDealerVO(data.data); }; - const toggleSection = (section: string) => { - setCollapsedSections((prev) => ({ - ...prev, - [section]: !prev[section], - })); - }; - // 暂存操作 const handleSave = () => { setSaveDialogVisible(true); @@ -335,124 +354,119 @@ export default hocAuth(function Page(props: CommonComponent) { const personalProfit = calculator.getPersonalProfit(); return ( - - {/* 顶部导航 */} - - - - {purchaseOrderVO?.orderDealer?.shortName || "-"} 第 - {purchaseOrderVO?.orderVehicle?.vehicleNo || "-"}车 - - - - - - {purchaseOrderVO?.orderVehicle.origin || "-"} 至{" "} - {purchaseOrderVO?.orderVehicle.destination || "-"} - - - {/* 展示产地负责人*/} - - - 产地负责人 - - - - setOriginPrincipal(value)} - onBlur={() => { - // 更新采购订单中的产地负责人 - setPurchaseOrderVO((prev) => ({ - ...prev!, - originPrincipal: originPrincipal, - })); - }} - className="w-full bg-transparent" - /> - - - - - {/* 循环渲染各部分内容 */} - {Object.keys(sections).map((sectionKey) => { - const section = sections[sectionKey]; - const isCollapsed = collapsedSections[sectionKey]; - - if (!dealerVO?.enableCompanyRebate && sectionKey === "taxSubsidy") { - return null; - } - - if (!dealerVO?.enableAccrualTax && sectionKey === "taxProvision") { - return null; - } - - if (!dealerVO?.enableShare && sectionKey === "costDifference") { - return null; - } - - return ( - - toggleSection(sectionKey)} - > - - {section.title} + <> + + + {/* 顶部导航 */} + + + + {purchaseOrderVO?.orderDealer?.shortName || "-"} 第 + {purchaseOrderVO?.orderVehicle?.vehicleNo || "-"}车 - - {isCollapsed ? "▲" : "▼"} + + + + + {purchaseOrderVO?.orderVehicle.origin || "-"} 至{" "} + {purchaseOrderVO?.orderVehicle.destination || "-"} + + + {/* 展示产地负责人*/} + + + 产地负责人 + + + + setOriginPrincipal(value)} + onBlur={() => { + // 更新采购订单中的产地负责人 + setPurchaseOrderVO((prev) => ({ + ...prev!, + originPrincipal: originPrincipal, + })); + }} + className="w-full bg-transparent" + /> - {!isCollapsed && ( - - + + + {/* 循环渲染各部分内容 */} + {Object.keys(sections).map((sectionKey) => { + const section = sections[sectionKey]; + + if (!dealerVO?.enableCompanyRebate && sectionKey === "taxSubsidy") { + return null; + } + + if (!dealerVO?.enableAccrualTax && sectionKey === "taxProvision") { + return null; + } + + if (!dealerVO?.enableShare && sectionKey === "costDifference") { + return null; + } + + return ( + <> + {section.title} + + + + + ); + })} + + {/* 个人利润 */} + + 个人利润 + {personalProfit > 0 ? ( + + ¥ {personalProfit || "-"} + + ) : ( + + ¥ {personalProfit || "-"} )} - ); - })} - - {/* 个人利润 */} - - 个人利润 - {personalProfit > 0 ? ( - - ¥ {personalProfit || "-"} - - ) : ( - - ¥ {personalProfit || "-"} - - )} + {/* 按钮操作 */} @@ -460,25 +474,15 @@ export default hocAuth(function Page(props: CommonComponent) { {purchaseOrderVO.state === "WAITING_AUDIT" && ( <> - - { - // 返回首页 - Taro.redirectTo({ url: "/pages/purchase/reviewer/list" }); - }} - /> - @@ -489,7 +493,7 @@ export default hocAuth(function Page(props: CommonComponent) { className="bg-primary text-white" onClick={handleSubmit} > - 提交老板审核 + 提交老板审批 @@ -531,6 +535,86 @@ export default hocAuth(function Page(props: CommonComponent) { onCancel={() => setSubmitDialogVisible(false)} onConfirm={confirmSubmit} /> - + + {/* 更多操作ActionSheet */} + setMoreActionVisible(false)} + onSelect={(item) => { + setMoreActionVisible(false); + if (item.name === "暂存") { + // 暂存操作 + handleSave(); + } else if (item.name === "驳回") { + // 驳回操作 + handleReject(); + } + }} + cancelText="取消" + /> + + {/* 驳回原因弹窗 */} + { + setRejectVisible(false); + setRejectReason(""); + }} + closeable + destroyOnClose + title={"驳回"} + description={"此采购单将退回到产地录入员"} + > + + 请输入驳回原因 +