- 移除 OWN 类型纸箱,统一使用 USED 类型处理 - 简化纸箱使用流程,去除瓜农是否包纸箱的选择 - 更新采购成本计算逻辑,支持按毛重或净重报价 - 优化界面布局,提升用户体验 - 调整数据结构定义,确保类型一致性 - 增加版本号至 v0.0.31 - 添加被驳回订单的编辑功能 - 根据经销商控制表单展示内容 - 修复部分计算逻辑中的过滤条件 - 清理冗余代码和无用字段
732 lines
20 KiB
TypeScript
732 lines
20 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,
|
|
DeliveryFormSection,
|
|
EmptyBoxInfoSection,
|
|
MarketPriceSection,
|
|
MaterialCostSection,
|
|
PackageInfoSection,
|
|
PackagingCostSection,
|
|
ProductionAdvanceSection,
|
|
ProductionLossSection,
|
|
PurchaseCostInfoSection,
|
|
PurchaseFormSection,
|
|
RebateCalcSection,
|
|
State,
|
|
TaxProvisionSection,
|
|
TaxSubsidySection,
|
|
WorkerAdvanceSection,
|
|
} from "@/components";
|
|
import { buildUrl, formatCurrency, PurchaseOrderCalculator } from "@/utils";
|
|
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: "采购成本复核",
|
|
},
|
|
// 分单发货复核
|
|
purchaseForm: {
|
|
component: PurchaseFormSection,
|
|
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;
|
|
}
|
|
|
|
if (
|
|
sectionKey === "purchaseForm" &&
|
|
purchaseOrderVO.orderDealer.shortName !== "信誉楼"
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
if (
|
|
sectionKey === "deliveryForm" &&
|
|
purchaseOrderVO.orderDealer.shortName === "信誉楼"
|
|
) {
|
|
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>
|
|
</>
|
|
);
|
|
});
|