feat(audit): 添加审批和审核查看功能并优化发货单据生成

- 在审批列表中添加老板查看审批按钮,仅在审批成功时显示
- 在审核列表中添加审核员查看审核按钮,仅在审核成功时显示
- 修复发货单据组件中重量计算使用净重而非毛重的问题
- 移除采购交付表单中的调试日志输出
- 将审批页面标题从"采购单审核"改为"采购单审批"
- 在审批页面中添加供应商采购单价列表展示功能,支持展开收起
- 优化审批页面成本明细显示,添加到货日期信息
- 修复审批页面中返点金额的条件判断逻辑
- 优化审批页面利润显示颜色,正利润显示为蓝色,负利润显示为红色
- 添加返回首页按钮到审批页面底部操作栏
- 重构发货单据页面数据加载逻辑,优化模板配置更新流程
- 移除发货单据页面中未使用的经销商返点客户列表状态管理
- 优化发货单据页面的预览和生成方法参数传递
This commit is contained in:
shenyifei 2025-12-28 08:43:04 +08:00
parent d019257566
commit 24d53d6c83
9 changed files with 214 additions and 85 deletions

View File

@ -342,6 +342,28 @@ export default function ApprovalList(props: IApprovalListProps) {
</Button> </Button>
</> </>
)} )}
{auditVO.state === "AUDIT_SUCCESS" &&
auditVO.type === "BOSS_AUDIT" &&
userRoleVO.slug === "boss" && (
<>
<Button
type={"primary"}
size={"small"}
block
onClick={(e) => {
Taro.navigateTo({
url: buildUrl("/pages/approval/audit", {
orderId: orderVO.orderId,
auditId: auditVO.auditId,
}),
});
e.stopPropagation();
}}
>
</Button>
</>
)}
</View> </View>
</View> </View>
</View> </View>

View File

@ -357,6 +357,28 @@ export default function AuditList(props: IAuditListProps) {
</Button> </Button>
)} )}
{auditVO.state === "AUDIT_SUCCESS" &&
userRoleVO.slug === "reviewer" && (
<>
<Button
type={"primary"}
size={"small"}
block
onClick={(e) => {
Taro.navigateTo({
url: buildUrl("/pages/audit/audit", {
orderId: orderVO.orderId,
auditId: auditVO.auditId,
}),
});
e.stopPropagation();
}}
>
</Button>
</>
)}
</View> </View>
</View> </View>
</View> </View>

View File

@ -1,4 +1,5 @@
import { View } from "@tarojs/components"; import { View } from "@tarojs/components";
import { Fragment } from "react";
export default function OtherInfo(props: { module: any }) { export default function OtherInfo(props: { module: any }) {
const { module } = props; const { module } = props;
@ -40,9 +41,9 @@ export default function OtherInfo(props: { module: any }) {
</View> </View>
</View> </View>
)} )}
{config.data?.map((item: any) => { {config.data?.map((item: any, number: number) => {
return ( return (
<> <Fragment key={number}>
{config.showGrade && ( {config.showGrade && (
<View className="grid w-full grid-cols-3 gap-0 text-base"> <View className="grid w-full grid-cols-3 gap-0 text-base">
<View className="p-2 text-left font-bold">:</View> <View className="p-2 text-left font-bold">:</View>
@ -64,17 +65,14 @@ export default function OtherInfo(props: { module: any }) {
{config.unitPriceUnit === "1" ? "元/斤" : "元/公斤"} {config.unitPriceUnit === "1" ? "元/斤" : "元/公斤"}
</View> </View>
</View> </View>
</> </Fragment>
); );
})} })}
<View className="grid w-full grid-cols-3 gap-0 text-base"> <View className="grid w-full grid-cols-3 gap-0 text-base">
<View className="p-2 text-left font-bold"></View> <View className="p-2 text-left font-bold"></View>
<View className="p-2 text-left"> <View className="p-2 text-left">
{config.data?.reduce( {config.data?.reduce((acc: any, cur: any) => acc + cur.netWeight, 0)}
(acc: any, cur: any) => acc + cur.grossWeight,
0,
)}
</View> </View>
<View className="p-2 text-left"></View> <View className="p-2 text-left"></View>
</View> </View>

View File

@ -96,9 +96,6 @@ export default function DeliveryFormSection(props: {
const moduleList = updateTemplateConfig(template, convertedData); const moduleList = updateTemplateConfig(template, convertedData);
console.log("moduleList", moduleList);
console.log("orderShip", orderShip);
return ( return (
<> <>
<DeliveryStep1Form <DeliveryStep1Form

View File

@ -1,4 +1,4 @@
export default definePageConfig({ export default definePageConfig({
navigationBarTitleText: "采购单审", navigationBarTitleText: "采购单审",
navigationBarBackgroundColor: "#fff", navigationBarBackgroundColor: "#fff",
}); });

View File

@ -4,10 +4,12 @@ import Taro from "@tarojs/taro";
import { business } from "@/services"; import { business } from "@/services";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { View } from "@tarojs/components"; import { View } from "@tarojs/components";
import { SafeArea } from "@nutui/nutui-react-taro"; import { Button, SafeArea } from "@nutui/nutui-react-taro";
import { Icon, OrderFinalApprove, OrderRejectFinal } from "@/components"; import { Icon, OrderFinalApprove, OrderRejectFinal } from "@/components";
import { buildUrl, formatUnitPrice, OrderCalculator } from "@/utils"; import { buildUrl, formatUnitPrice, OrderCalculator } from "@/utils";
import { DecimalUtils } from "@/utils/classes/calculators/core/DecimalUtils"; import { DecimalUtils } from "@/utils/classes/calculators/core/DecimalUtils";
import dayjs from "dayjs";
import classNames from "classnames";
export default hocAuth(function Page(props: CommonComponent) { export default hocAuth(function Page(props: CommonComponent) {
const { router, isInitialized, setIsInitialized, setLoading } = props; const { router, isInitialized, setIsInitialized, setLoading } = props;
@ -18,6 +20,9 @@ export default hocAuth(function Page(props: CommonComponent) {
const [auditVO, setAuditVO] = useState<BusinessAPI.AuditVO>(); const [auditVO, setAuditVO] = useState<BusinessAPI.AuditVO>();
const [orderVO, setOrderVO] = useState<BusinessAPI.OrderVO>(); const [orderVO, setOrderVO] = useState<BusinessAPI.OrderVO>();
// 控制供应商采购单价列表展开状态
const [showAllSuppliers, setShowAllSuppliers] = useState(false);
const init = async ( const init = async (
orderId: BusinessAPI.OrderVO["orderId"], orderId: BusinessAPI.OrderVO["orderId"],
auditId: BusinessAPI.AuditVO["auditId"], auditId: BusinessAPI.AuditVO["auditId"],
@ -83,6 +88,9 @@ export default hocAuth(function Page(props: CommonComponent) {
) )
: 0; : 0;
const estimatedArrivalDate = orderVO.orderShipList[0].estimatedArrivalDate;
const personalProfit = calculator.getPersonalProfit();
return ( return (
<> <>
<View <View
@ -107,26 +115,67 @@ export default hocAuth(function Page(props: CommonComponent) {
<View className="mb-1 text-center text-sm text-gray-500"> <View className="mb-1 text-center text-sm text-gray-500">
</View> </View>
<View className="flex flex-row items-center justify-center gap-2.5 text-center"> {/*<View className="flex flex-row items-center justify-center gap-2.5 text-center">*/}
<View className="text-primary text-3xl"> {/* <View className="text-primary text-3xl">*/}
{formatUnitPrice(calculator.getAveragePurchasePrice())} {/* {formatUnitPrice(calculator.getAveragePurchasePrice())}*/}
</View> {/* </View>*/}
<View className="text-sm text-gray-500">/</View> {/* <View className="text-sm text-gray-500">元/斤</View>*/}
</View> {/*</View>*/}
{/* 供应商采购单价列表 */}
{orderVO.orderSupplierList &&
orderVO.orderSupplierList.length > 0 && (
<View className="flex flex-col">
{orderVO.orderSupplierList
.slice(0, showAllSuppliers ? undefined : 3)
.map((supplier) => (
<View
key={supplier.orderSupplierId}
className="flex items-center justify-between py-1"
>
<View className="text-sm text-gray-600">
{supplier.name}
</View>
<View className="flex flex-row items-center gap-0.5">
<View className="text-primary text-xl font-bold">
{formatUnitPrice(supplier.purchasePrice || 0)}
</View>
<View className="text-sm text-gray-500">/</View>
</View>
</View>
))}
{/* 查看更多按钮 */}
{orderVO.orderSupplierList.length > 3 && (
<View
className="bg-primary/5 mt-2 flex flex-row items-center justify-center gap-0.5 rounded-md p-1 text-center"
onClick={() => setShowAllSuppliers(!showAllSuppliers)}
>
<View className="text-primary cursor-pointer text-sm">
{showAllSuppliers ? "收起" : "查看更多"}
</View>
<Icon
name={showAllSuppliers ? "chevron-up" : "chevron-down"}
size={14}
/>
</View>
)}
</View>
)}
</View> </View>
<View className="price-card flex-1 rounded-lg bg-white p-3"> <View className="price-card flex-1 rounded-lg bg-white p-3">
<View className="mb-1 text-center text-sm text-gray-500"> <View className="mb-1 text-center text-sm text-gray-500">
</View> </View>
<View className="flex flex-row items-center justify-center gap-2.5 text-center"> <View className="flex flex-row items-center justify-center gap-2.5 text-center">
<View className="text-primary text-2xl"> <View className="text-primary text-2xl font-bold">
{formatUnitPrice(calculator.getAverageSalesPrice())} {formatUnitPrice(calculator.getAverageSalesPrice())}
</View> </View>
<View className="text-sm text-gray-500">/</View> <View className="text-sm text-gray-500">/</View>
</View> </View>
</View> </View>
</View> </View>
<View className="overflow-hidden rounded-lg bg-white shadow-sm"> <View className="hidden overflow-hidden rounded-lg bg-white shadow-sm">
<View className="border-b border-gray-100 px-4 py-3"> <View className="border-b border-gray-100 px-4 py-3">
<View className="text-sm font-bold"></View> <View className="text-sm font-bold"></View>
</View> </View>
@ -153,7 +202,15 @@ export default hocAuth(function Page(props: CommonComponent) {
</View> </View>
<View className="overflow-hidden rounded-lg bg-white shadow-sm"> <View className="overflow-hidden rounded-lg bg-white shadow-sm">
<View className="border-b border-gray-100 px-4 py-3"> <View className="border-b border-gray-100 px-4 py-3">
<View className="text-sm font-bold"></View> <View className={"flex flex-row justify-between"}>
<View className="text-sm font-bold"></View>
<View className="text-sm text-gray-500">
{estimatedArrivalDate
? dayjs(estimatedArrivalDate).format("YYYY-MM-DD")
: "暂无"}
</View>
</View>
</View> </View>
<View className="grid grid-cols-2 divide-x divide-y divide-gray-100"> <View className="grid grid-cols-2 divide-x divide-y divide-gray-100">
<View className="cost-item flex flex-col px-3 py-2"> <View className="cost-item flex flex-col px-3 py-2">
@ -221,7 +278,7 @@ export default hocAuth(function Page(props: CommonComponent) {
</View> </View>
)} )}
{orderVO.orderRebate && ( {orderVO.orderRebate?.amount && (
<View className="cost-item flex flex-col px-3 py-2"> <View className="cost-item flex flex-col px-3 py-2">
<View className="text-sm text-gray-500"></View> <View className="text-sm text-gray-500"></View>
<View className="font-medium"> <View className="font-medium">
@ -257,8 +314,13 @@ export default hocAuth(function Page(props: CommonComponent) {
<View className="rounded-lg bg-white p-2.5 shadow-sm"> <View className="rounded-lg bg-white p-2.5 shadow-sm">
<View className="flex items-center justify-between"> <View className="flex items-center justify-between">
<View className="text-gray-500"></View> <View className="text-gray-500"></View>
<View className="profit-highlight text-primary text-3xl"> <View
{calculator.getShareProfit()} className={classNames("text-2xl font-bold", {
"text-primary": personalProfit > 0,
"text-red-500": personalProfit < 0,
})}
>
{personalProfit}
</View> </View>
</View> </View>
</View> </View>
@ -267,6 +329,20 @@ export default hocAuth(function Page(props: CommonComponent) {
{/* 按钮操作 */} {/* 按钮操作 */}
<View className={"sticky bottom-0 z-10 bg-white"} id={"bottomBar"}> <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"> <View className="flex justify-between gap-2 border-t border-gray-200 p-2.5">
<View className="flex-1">
<Button
type="default"
size={"large"}
block
onClick={() =>
Taro.switchTab({
url: buildUrl("/pages/main/index/index"),
})
}
>
</Button>
</View>
{auditVO?.type === "BOSS_AUDIT" && {auditVO?.type === "BOSS_AUDIT" &&
auditVO.state === "WAITING_AUDIT" && ( auditVO.state === "WAITING_AUDIT" && (
<> <>

View File

@ -256,9 +256,6 @@ export default hocAuth(function Page(props: CommonComponent) {
// 控制快速导航的显示状态 // 控制快速导航的显示状态
const [quickNavExpanded, setQuickNavExpanded] = useState(false); const [quickNavExpanded, setQuickNavExpanded] = useState(false);
const [dealerRebateCustomerVOList, setDealerRebateCustomerVOList] =
useState<BusinessAPI.DealerRebateCustomerVO[]>();
// 暂存操作 // 暂存操作
const handleSave = () => { const handleSave = () => {
setSaveDialogVisible(true); setSaveDialogVisible(true);
@ -504,19 +501,23 @@ export default hocAuth(function Page(props: CommonComponent) {
orderVO.orderShipList = [convertOrderToOrderShip(orderVO)]; orderVO.orderShipList = [convertOrderToOrderShip(orderVO)];
} }
const { if (!orderVO.orderRebate) {
data: { data: dealerRebateCustomerVOList }, const {
} = await business.dealerRebateCustomer.listDealerRebateCustomer({ data: { data: dealerRebateCustomerVOList },
dealerRebateCustomerListQry: { } = await business.dealerRebateCustomer.listDealerRebateCustomer({
dealerId: orderVO?.orderDealer?.dealerId!, dealerRebateCustomerListQry: {
status: true, dealerId: orderVO?.orderDealer?.dealerId!,
}, status: true,
}); },
});
if (dealerRebateCustomerVOList && dealerRebateCustomerVOList.length > 0) { if (
setDealerRebateCustomerVOList(dealerRebateCustomerVOList || []); dealerRebateCustomerVOList &&
orderVO.orderRebate = dealerRebateCustomerVOList.length > 0
dealerRebateCustomerVOList[0] as BusinessAPI.OrderRebate; ) {
orderVO.orderRebate =
dealerRebateCustomerVOList[0] as BusinessAPI.OrderRebate;
}
} }
orderVO.orderCostList.map((item) => { orderVO.orderCostList.map((item) => {
@ -656,7 +657,7 @@ export default hocAuth(function Page(props: CommonComponent) {
} }
}, []); }, []);
if (!orderVO || !dealerRebateCustomerVOList || !costList) { if (!orderVO || !costList) {
return; return;
} }

View File

@ -76,7 +76,6 @@ export default hocAuth(function Page(props: CommonComponent) {
const [orderVO, setOrderVO] = useState<BusinessAPI.OrderVO>(); const [orderVO, setOrderVO] = useState<BusinessAPI.OrderVO>();
const [orderShipVO, setOrderShipVO] = useState<BusinessAPI.OrderShip>(); const [orderShipVO, setOrderShipVO] = useState<BusinessAPI.OrderShip>();
const [deliveryTemplate, setDeliveryTemplate] = useState<string>();
const [pdfUrl, setPdfUrl] = useState<string>(); const [pdfUrl, setPdfUrl] = useState<string>();
const [picUrl, setPicUrl] = useState<string>(); const [picUrl, setPicUrl] = useState<string>();
@ -87,31 +86,46 @@ export default hocAuth(function Page(props: CommonComponent) {
orderShipId: BusinessAPI.OrderShipVO["orderShipId"], orderShipId: BusinessAPI.OrderShipVO["orderShipId"],
) => { ) => {
setLoading(true); setLoading(true);
const { data } = await business.order.showOrder({ const {
data: { data: orderVO },
} = await business.order.showOrder({
orderShowQry: { orderShowQry: {
orderId, orderId,
orderShipId, orderShipId,
}, },
}); });
const orderVO = data.data;
if (orderVO) { if (orderVO) {
setOrderVO(orderVO); setOrderVO(orderVO);
const {
data: { data: dealerVO },
} = await business.dealer.showDealer({
dealerShowQry: {
dealerId: orderVO?.orderVehicle.dealerId,
},
});
const template = JSON.parse(dealerVO?.deliveryTemplate!);
// 将 orderShipVO 转换为 examples 的数据格式,然后再替换 moduleList 里面的 config 数据
const convertedData = convertOrderShipVOToExamplesFormat(orderVO);
const moduleList = await updateTemplateConfig(
template,
convertedData,
orderVO,
);
setModuleList(moduleList);
const orderShip = orderVO.orderShipList[0]; const orderShip = orderVO.orderShipList[0];
setOrderShipVO(orderShip); setOrderShipVO(orderShip);
if (orderShip.pdfUrl || orderShip.picUrl) { if (orderShip.pdfUrl || orderShip.picUrl) {
setStep(3); setStep(3);
setPdfUrl(orderShip.pdfUrl); setPdfUrl(orderShip.pdfUrl);
setPicUrl(orderShip.picUrl); setPicUrl(orderShip.picUrl);
} else {
const [pdfUrl, picUrl] = await capturePreview(moduleList);
await updateFile(moduleList, orderVO, pdfUrl, picUrl);
setStep(3);
} }
const { data } = await business.dealer.showDealer({
dealerShowQry: {
dealerId: orderShip?.dealerId,
},
});
const deliveryTemplate = data.data?.deliveryTemplate!;
setDeliveryTemplate(deliveryTemplate);
} }
setLoading(false); setLoading(false);
}; };
@ -122,27 +136,6 @@ export default hocAuth(function Page(props: CommonComponent) {
} }
}, [orderId, orderShipId]); }, [orderId, orderShipId]);
const refresh = async (
deliveryTemplate: string,
orderVO: BusinessAPI.OrderVO,
) => {
const template = JSON.parse(deliveryTemplate);
// 将 orderShipVO 转换为 examples 的数据格式,然后再替换 moduleList 里面的 config 数据
const convertedData = convertOrderShipVOToExamplesFormat(orderVO);
const updatedTemplate = await updateTemplateConfig(
template,
convertedData,
orderVO,
);
setModuleList(updatedTemplate);
};
useEffect(() => {
if (deliveryTemplate && orderVO) {
refresh(deliveryTemplate, orderVO).then();
}
}, [orderVO, deliveryTemplate]);
// 更新模板配置 // 更新模板配置
const updateTemplateConfig = async ( const updateTemplateConfig = async (
template: any[], template: any[],
@ -166,7 +159,10 @@ export default hocAuth(function Page(props: CommonComponent) {
}; };
// 预览确认 // 预览确认
const previewAndConfirm = () => { const previewAndConfirm = (
moduleList: any[],
orderVO: BusinessAPI.OrderVO,
) => {
if (step === 2) { if (step === 2) {
// 显示确认对话框 // 显示确认对话框
Dialog.open("dialog", { Dialog.open("dialog", {
@ -176,7 +172,8 @@ export default hocAuth(function Page(props: CommonComponent) {
cancelText: "取消", cancelText: "取消",
onConfirm: async () => { onConfirm: async () => {
// 截图预览内容 // 截图预览内容
await capturePreview(); const [pdfUrl, picUrl] = await capturePreview(moduleList);
await updateFile(moduleList, orderVO, pdfUrl, picUrl);
Dialog.close("dialog"); Dialog.close("dialog");
// 进入第三步 // 进入第三步
setStep(3); setStep(3);
@ -202,7 +199,10 @@ export default hocAuth(function Page(props: CommonComponent) {
}; };
// 生成发货单据 // 生成发货单据
const generateShippingDocument = async () => { const generateShippingDocument = async (
moduleList: any[],
orderVO: BusinessAPI.OrderVO,
) => {
// 显示确认对话框 // 显示确认对话框
Dialog.open("dialog", { Dialog.open("dialog", {
title: "生成发货单据", title: "生成发货单据",
@ -211,7 +211,8 @@ export default hocAuth(function Page(props: CommonComponent) {
cancelText: "取消", cancelText: "取消",
onConfirm: async () => { onConfirm: async () => {
// 截图预览内容 // 截图预览内容
await capturePreview(); const [pdfUrl, picUrl] = await capturePreview(moduleList);
await updateFile(moduleList, orderVO, pdfUrl, picUrl);
Dialog.close("dialog"); Dialog.close("dialog");
// 进入第三步 // 进入第三步
setStep(3); setStep(3);
@ -223,7 +224,7 @@ export default hocAuth(function Page(props: CommonComponent) {
}; };
// 截图预览内容 // 截图预览内容
const capturePreview = async () => { const capturePreview = async (moduleList: any[]) => {
const template = new PdfTemplate(moduleList); const template = new PdfTemplate(moduleList);
const { const {
data: { data: pdfData }, data: { data: pdfData },
@ -242,13 +243,22 @@ export default hocAuth(function Page(props: CommonComponent) {
setPdfUrl(pdfData?.path); setPdfUrl(pdfData?.path);
setPicUrl(picData?.path); setPicUrl(picData?.path);
return [pdfData?.path!, picData?.path!];
};
const updateFile = async (
moduleList: any[],
orderVO: BusinessAPI.OrderVO,
pdfUrl: string,
picUrl: string,
) => {
const orderShip = orderVO?.orderShipList[0]; const orderShip = orderVO?.orderShipList[0];
// 存储 至 orderShip previewUrl // 存储 至 orderShip previewUrl
if (orderShip) { if (orderShip) {
let formData: BusinessAPI.OrderShipGenerateDocumentCmd = { let formData: BusinessAPI.OrderShipGenerateDocumentCmd = {
orderShipId: orderShip?.orderShipId, orderShipId: orderShip?.orderShipId,
pdfUrl: pdfData?.path as string, pdfUrl: pdfUrl as string,
picUrl: picData?.path as string, picUrl: picUrl as string,
}; };
// 检查各模块中的必填字段是否已填写 // 检查各模块中的必填字段是否已填写
for (const module of moduleList) { for (const module of moduleList) {
@ -314,6 +324,10 @@ export default hocAuth(function Page(props: CommonComponent) {
setStep(1); setStep(1);
}; };
if (!orderVO) {
return;
}
return ( return (
<> <>
<View <View
@ -370,7 +384,7 @@ export default hocAuth(function Page(props: CommonComponent) {
type="primary" type="primary"
block block
size={"large"} size={"large"}
onClick={previewAndConfirm} onClick={() => previewAndConfirm(moduleList, orderVO)}
> >
</Button> </Button>
@ -394,7 +408,7 @@ export default hocAuth(function Page(props: CommonComponent) {
type="primary" type="primary"
block block
size={"large"} size={"large"}
onClick={generateShippingDocument} onClick={() => generateShippingDocument(moduleList, orderVO)}
> >
</Button> </Button>

View File

@ -2,7 +2,6 @@ export class PdfTemplate {
private moduleList: any[]; private moduleList: any[];
constructor(moduleList: any[]) { constructor(moduleList: any[]) {
console.log("moduleList",moduleList)
this.moduleList = moduleList; this.moduleList = moduleList;
} }
@ -577,7 +576,7 @@ export class PdfTemplate {
</div> </div>
<div class="p-2 text-left"> <div class="p-2 text-left">
${config.data?.reduce( ${config.data?.reduce(
(acc: any, cur: any) => acc + cur.grossWeight, (acc: any, cur: any) => acc + cur.netWeight,
0, 0,
)} )}
</div> </div>