ERPTurbo_Client/packages/app-client/src/pages/purchase/audit/audit.tsx
shenyifei d467e25a76 feat(purchase): 优化采购表单与成本编辑功能
- 新增 DeliveryFormSection 组件用于发货表单展示
- 移除多个弹窗组件,改用 PriceEditor 直接编辑金额
- 优化 PriceEditor 组件支持清空与格式化显示
- 调整采购成本相关字段命名与计算逻辑
- 增强返点计算 section 的交互与数据校验
- 更新表格样式并新增定金字段展示
- 修复 delivery 页面成本类型过滤条件
- 统一使用 formatCurrency 处理金额显示格式
- 重构多个 section 组件移除冗余状态管理
- 优化只读模式下表单元素的禁用与样式表现
2025-12-05 00:14:54 +08:00

712 lines
19 KiB
TypeScript

import hocAuth from "@/hocs/auth";
import { CommonComponent } from "@/types/typings";
import Taro from "@tarojs/taro";
import { business } from "@/services";
import { useEffect, useState } from "react";
import { View } from "@tarojs/components";
import purchaseOrder from "@/constant/purchaseOrder";
import {
ActionSheet,
Button,
Dialog,
Input,
Popup,
SafeArea,
TextArea,
Toast,
} from "@nutui/nutui-react-taro";
import {
BasicInfoSection,
CompanyInfoSection,
CostDifferenceSection,
CostSummarySection,
DealerInfoSection,
EmptyBoxInfoSection,
MarketPriceSection,
MaterialCostSection,
PackageInfoSection,
PackagingCostSection,
ProductionAdvanceSection,
PurchaseCostInfoSection,
RebateCalcSection,
State,
TaxProvisionSection,
TaxSubsidySection,
WorkerAdvanceSection,
} from "@/components";
import { buildUrl, formatCurrency, PurchaseOrderCalculator } from "@/utils";
import ProductionLossSection from "../../../components/purchase/section/ProductionLossSection";
import DeliveryFormSection from "../../../components/purchase/section/DeliveryFormSection";
import classNames from "classnames";
const sections = {
// 市场报价
marketPrice: {
component: MarketPriceSection,
title: "市场报价",
},
// 我方入账公司
supplierInfo: {
component: CompanyInfoSection,
title: "我方入账公司",
},
// 下游经销商信息
dealerInfo: {
component: DealerInfoSection,
title: "下游经销商",
},
// 基础信息
basicInfo: {
component: BasicInfoSection,
title: "基础信息",
},
// // 瓜农信息
// farmerInfo: {
// component: SupplierInfoSection,
// title: "瓜农信息",
// },
// 采购成本
purchaseCostInfo: {
component: PurchaseCostInfoSection,
title: "采购成本复核",
},
// 包装纸箱费
packageInfo: {
component: PackageInfoSection,
title: "包装纸箱费复核",
},
// 空箱费用
emptyBoxInfo: {
component: EmptyBoxInfoSection,
title: "空箱费用复核",
},
// 人工费用复核
foremanAdvance: {
component: WorkerAdvanceSection,
title: "人工费用复核",
},
// 辅料费用复核
materialCost: {
component: MaterialCostSection,
title: "辅料费复核",
},
// 产地费用复核
originAdvance: {
component: ProductionAdvanceSection,
title: "产地费用复核",
},
// 其他费用复核
otherCost: {
component: PackagingCostSection,
title: "其他费用复核",
},
// 产地损耗复核
productionLoss: {
component: ProductionLossSection,
title: "产地损耗复核",
},
// 公司返点复核
taxSubsidy: {
component: TaxSubsidySection,
title: "公司返点复核",
},
// 计提税金复核
taxProvision: {
component: TaxProvisionSection,
title: "计提税金复核",
},
// 调诚信志远分红
costDifference: {
component: CostDifferenceSection,
title: "调诚信志远分红",
},
// 成本合计
costSummary: {
component: CostSummarySection,
title: "成本合计",
},
// 个人返点复核
rebateCalc: {
component: RebateCalcSection,
title: "个人返点复核",
},
// 发货单复核
deliveryForm: {
component: DeliveryFormSection,
title: "发货单复核",
},
};
export default hocAuth(function Page(props: CommonComponent) {
const { router, isInitialized, setIsInitialized, userRoleVO, setLoading } =
props;
const orderId = router.params
.orderId as BusinessAPI.PurchaseOrderVO["orderId"];
// 费用项目列表
const [costList, setCostList] = useState<BusinessAPI.CostVO[]>([]);
// 获取费用项目列表
useEffect(() => {
const fetchCost = async () => {
try {
const { data } = await business.cost.listCost({
costListQry: {
status: true,
},
});
setCostList(data.data || []);
} catch (error) {
console.error("获取费用项目列表失败:", error);
}
};
fetchCost();
}, []);
const [purchaseOrderVO, setPurchaseOrderVO] =
useState<BusinessAPI.PurchaseOrderVO>();
console.log("purchaseOrderVO", purchaseOrderVO);
// 驳回原因弹窗相关状态
const [rejectVisible, setRejectVisible] = useState(false);
const [rejectReason, setRejectReason] = useState("");
// 驳回操作
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
const {
data: { success, errMessage },
} = await business.purchaseOrder.rejectApprovePurchaseOrder({
orderId: purchaseOrderVO?.orderId,
rejectReason: rejectReason,
});
if (!success) {
Toast.show("toast", {
icon: "fail",
title: "提示",
content: errMessage || "驳回失败",
});
return;
}
Toast.show("toast", {
icon: "success",
title: "提示",
content: "驳回成功,正在跳转我的审核...",
});
// 关闭弹窗
setRejectVisible(false);
setRejectReason("");
setTimeout(() => {
Taro.redirectTo({
url: buildUrl("/pages/purchase/audit/pending"),
});
}, 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 [dealerRebateCustomerVOList, setDealerRebateCustomerVOList] =
useState<BusinessAPI.DealerRebateCustomerVO[]>();
const initDealer = async (dealerId: BusinessAPI.DealerVO["dealerId"]) => {
const {
data: { data: dealerRebateCustomerVOList },
} = await business.dealerRebateCustomer.listDealerRebateCustomer({
dealerRebateCustomerListQry: {
dealerId: dealerId,
status: true,
},
});
setDealerRebateCustomerVOList(dealerRebateCustomerVOList);
};
// 暂存操作
const handleSave = () => {
setSaveDialogVisible(true);
};
// 确认暂存
const confirmSave = async () => {
// 关闭对话框
setSaveDialogVisible(false);
// 这里应该调用暂存API
console.log("暂存数据:", purchaseOrderVO);
const { data } = await business.purchaseOrder.approvePurchaseOrder({
...purchaseOrderVO!,
draft: true,
});
if (data.success) {
Toast.show("toast", {
icon: "success",
title: "提示",
content: "暂存成功",
});
// 返回采购单页面
Taro.redirectTo({ url: "/pages/purchase/audit/pending" });
}
};
// 提交老板审核操作
const handleSubmit = () => {
setSubmitDialogVisible(true);
};
// 确认提交老板审核
const confirmSubmit = async () => {
// 关闭对话框
setSubmitDialogVisible(false);
// 表单校验
const errorMsg = validateForm();
if (errorMsg) {
Toast.show("toast", {
icon: "fail",
title: "校验失败",
content: errorMsg,
});
return;
}
// 这里应该调用提交审核API
console.log("提交老板审核:", purchaseOrderVO);
const { data } = await business.purchaseOrder.approvePurchaseOrder({
...purchaseOrderVO!,
draft: false,
});
if (data.success) {
Toast.show("toast", {
icon: "success",
title: "提示",
content: "提交审核成功",
});
// 跳转到提交结果页面
Taro.redirectTo({
url: buildUrl("/pages/purchase/audit/result", {
orderId: purchaseOrderVO!.orderId,
}),
});
}
};
// 表单校验
const validateForm = () => {
// 校验销售方
if (!purchaseOrderVO?.orderCompany?.companyId) {
return "请选择销售方";
}
// 校验经销商
if (!purchaseOrderVO?.orderDealer?.dealerId) {
return "请选择经销商";
}
// 校验本车次号
if (!purchaseOrderVO?.orderVehicle?.vehicleNo) {
return "请输入本车次号";
}
// 校验运费类型
if (!purchaseOrderVO?.orderVehicle?.priceType) {
return "请选择运费类型";
}
// 校验市场报价的报价方式
if (!purchaseOrderVO?.pricingMethod) {
return "请选择市场报价的报价方式";
}
// 校验市场报价的销售单价
purchaseOrderVO.orderSupplierList.forEach(
(supplier: BusinessAPI.OrderSupplier) => {
if (!supplier.salePrice || supplier.salePrice <= 0) {
return "请填写市场报价的销售单价";
}
},
);
return null;
};
const init = async (orderId: BusinessAPI.PurchaseOrderVO["orderId"]) => {
const { data } = await business.purchaseOrder.showPurchaseOrder({
purchaseOrderShowQry: {
orderId,
},
});
if (data.success) {
setPurchaseOrderVO(data.data);
await initDealer(data.data?.orderDealer?.dealerId!);
}
};
useEffect(() => {
if (orderId && !isInitialized) {
setLoading(true);
init(orderId).then(() => {
setIsInitialized(true);
setLoading(false);
});
}
}, []);
if (!purchaseOrderVO || !dealerRebateCustomerVOList || !costList) {
return;
}
const calculator = new PurchaseOrderCalculator(purchaseOrderVO, true);
const personalProfit = calculator.getPersonalProfit();
return (
<>
<View
className={"overflow-x-hidden overflow-y-auto bg-[#D1D5DB]"}
id={"purchase-order-audit"}
>
<View className={"flex flex-col gap-2.5 p-2.5"}>
{/* 顶部导航 */}
<View className="flex flex-col gap-2.5 rounded-md bg-white p-2.5 shadow-sm">
<View className={"flex flex-row justify-between gap-2.5"}>
<View className="text-lg font-bold">
{purchaseOrderVO?.orderDealer?.shortName || "-"}
{purchaseOrderVO?.orderVehicle?.vehicleNo || "-"}
</View>
<State
state={purchaseOrderVO.state}
stateMap={purchaseOrder.stateMap}
/>
</View>
<View className="text-neutral-darker text-sm font-medium">
{purchaseOrderVO?.orderVehicle.origin || "-"} {" "}
{purchaseOrderVO?.orderVehicle.destination || "-"}
</View>
{/* 展示产地负责人*/}
<View className="flex flex-row items-center gap-2.5">
<View className="flex-shrink-0 text-base font-bold text-gray-900">
</View>
<View
className={`flex h-10 flex-1 items-center rounded-md border-4 border-gray-300`}
>
<Input
type="text"
disabled={
userRoleVO.slug === "boss" ||
(userRoleVO.slug === "reviewer" &&
purchaseOrderVO.state !== "WAITING_AUDIT")
}
placeholder="请输入产地负责人姓名"
value={originPrincipal || purchaseOrderVO.createdByName}
onChange={(value) => setOriginPrincipal(value)}
onBlur={() => {
// 更新采购订单中的产地负责人
setPurchaseOrderVO((prev) => ({
...prev!,
originPrincipal: originPrincipal,
}));
}}
className="w-full bg-transparent"
/>
</View>
</View>
</View>
{/* 循环渲染各部分内容 */}
{Object.keys(sections).map((sectionKey) => {
const section = sections[sectionKey];
const orderDealer = purchaseOrderVO.orderDealer;
if (
!orderDealer?.enableCompanyRebate &&
sectionKey === "taxSubsidy"
) {
return null;
}
if (
!orderDealer?.enableAccrualTax &&
sectionKey === "taxProvision"
) {
return null;
}
if (!orderDealer?.enableShare && sectionKey === "costDifference") {
return null;
}
// 如果没有返点人这个模块,则不渲染
if (
(!dealerRebateCustomerVOList ||
dealerRebateCustomerVOList.length === 0) &&
sectionKey === "rebateCalc"
) {
return null;
}
if (
(!purchaseOrderVO.orderPackageList ||
purchaseOrderVO?.orderPackageList.length === 0) &&
sectionKey === "emptyBoxInfo"
) {
return null;
}
return (
<>
<View className="text-sm font-bold">{section.title}</View>
<View
className={`overflow-x-auto rounded-md rounded-b-lg bg-white p-2.5 shadow-sm`}
>
<section.component
readOnly={purchaseOrderVO.state !== "WAITING_AUDIT"}
purchaseOrderVO={purchaseOrderVO}
onChange={setPurchaseOrderVO}
costList={costList}
calculator={calculator}
/>
</View>
</>
);
})}
{/* 个人利润 */}
<View
className={
"flex flex-row items-center rounded-md border border-[#FFBB96] p-4"
}
style={{
background: "linear-gradient(100deg, #FFF2E8 25%, #FFE7D9 75%)",
}}
>
<View className={"flex-1 text-center"}></View>
<View
className={classNames("flex-1 text-left text-2xl font-bold", {
"text-primary": personalProfit > 0,
"text-red-500": personalProfit < 0,
})}
>
{formatCurrency(personalProfit) || "-"}
</View>
</View>
</View>
</View>
{/* 按钮操作 */}
<View className={"sticky bottom-0 z-10 bg-white"} id={"bottomBar"}>
<View className="flex justify-between gap-2 border-t border-gray-200 p-2.5">
{purchaseOrderVO.state === "WAITING_AUDIT" &&
purchaseOrderVO.auditState === "PENDING_QUOTE_APPROVAL" && (
<>
<View className={"flex-1"}>
<Button
block
type={"default"}
size={"xlarge"}
className="bg-gray-200 text-gray-700"
onClick={() => setMoreActionVisible(true)}
>
</Button>
</View>
<View className={"flex-1"}>
<Button
block
type={"primary"}
size={"xlarge"}
className="bg-primary text-white"
onClick={handleSubmit}
>
</Button>
</View>
</>
)}
{purchaseOrderVO.state === "WAITING_AUDIT" &&
purchaseOrderVO.auditState === "PENDING_BOSS_APPROVAL" && (
<>
<View className={"flex-1"}>
<Button
block
type={"default"}
size={"xlarge"}
onClick={() => {
Taro.redirectTo({ url: "/pages/main/index/index" });
}}
>
</Button>
</View>
</>
)}
</View>
<SafeArea position={"bottom"} />
</View>
{/* 暂存确认对话框 */}
<Dialog
visible={saveDialogVisible}
title="确认暂存"
content="确定要暂存当前采购订单吗?"
onCancel={() => setSaveDialogVisible(false)}
onConfirm={confirmSave}
/>
{/* 提交审核确认对话框 */}
<Dialog
visible={submitDialogVisible}
title="提交审核"
content="确定要提交给老板审核吗?"
onCancel={() => setSubmitDialogVisible(false)}
onConfirm={confirmSubmit}
/>
{/* 更多操作ActionSheet */}
<ActionSheet
title="更多操作"
visible={moreActionVisible}
options={[
{
name: "暂存",
},
{
name: "驳回",
description: "此采购单将退回到产地录入员",
danger: true,
},
]}
onCancel={() => setMoreActionVisible(false)}
onSelect={(item) => {
setMoreActionVisible(false);
if (item.name === "暂存") {
// 暂存操作
handleSave();
} else if (item.name === "驳回") {
// 驳回操作
handleReject();
}
}}
cancelText="取消"
/>
{/* 驳回原因弹窗 */}
<Popup
duration={150}
style={{
minHeight: "auto",
}}
visible={rejectVisible}
position="bottom"
onClose={() => {
setRejectVisible(false);
setRejectReason("");
}}
closeable
destroyOnClose
title={"驳回"}
description={"此采购单将退回到产地录入员"}
>
<View className="p-2.5">
<View className="mb-2.5 text-sm font-bold"></View>
<View
className={`flex w-full items-center rounded-md border-4 border-gray-300`}
>
<TextArea
value={rejectReason}
onChange={(value) => setRejectReason(value)}
placeholder="请输入驳回原因"
rows={4}
maxLength={200}
showCount
/>
</View>
<View className="mt-4 flex justify-between gap-2">
<View className="flex-1">
<Button
size={"large"}
block
type="default"
onClick={() => {
setRejectVisible(false);
setRejectReason("");
}}
>
</Button>
</View>
<View className="flex-1">
<Button
size={"large"}
block
type="primary"
onClick={confirmReject}
>
</Button>
</View>
</View>
</View>
<SafeArea position="bottom" />
</Popup>
</>
);
});