BIZ8-11 增强采购项目关联

这个提交包含在:
2026-05-13 21:42:34 +08:00
父节点 83be13df27
当前提交 32823edc1b
共修改 11 个文件,包含 737 行新增和 6 行删除
+25 -1
查看文件
@@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
import { Link, useParams } from "react-router-dom";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { fetchEmployees } from "../api/employees";
import { archiveProject, createConfidentialDocumentRef, createProjectCommunicationLog, createProjectDelivery, createProjectMember, createProjectMilestone, createProjectRisk, createProjectTask, fetchProject, fetchProjectClosureChecklist, fetchProjectCommunicationLogs, fetchProjectDeliveries, fetchProjectDocumentRefs, fetchProjectExpensesSummary, fetchProjectMembers, fetchProjectMilestones, fetchProjectRisks, fetchProjectTasks, fetchProjectTravelSummary, fetchProjectWeeklyReports, patchProject, patchProjectClosureChecklistItem, patchProjectCommunicationLog, patchProjectMember, patchProjectMilestone, patchProjectRisk, patchProjectTask, type ConfidentialityLevel, type DocumentStorageType, type ProjectCommunicationChannel, type ProjectClosureChecklistStatus, type ProjectDeliveryRecordType, type ProjectMilestoneStatus, type ProjectRiskLevel, type ProjectStatus, type ProjectTaskPriority, type ProjectTaskStatus } from "../api/projects";
import { archiveProject, createConfidentialDocumentRef, createProjectCommunicationLog, createProjectDelivery, createProjectMember, createProjectMilestone, createProjectRisk, createProjectTask, fetchProject, fetchProjectClosureChecklist, fetchProjectCommunicationLogs, fetchProjectDeliveries, fetchProjectDocumentRefs, fetchProjectExpensesSummary, fetchProjectMembers, fetchProjectMilestones, fetchProjectPurchaseSummary, fetchProjectRisks, fetchProjectTasks, fetchProjectTravelSummary, fetchProjectWeeklyReports, patchProject, patchProjectClosureChecklistItem, patchProjectCommunicationLog, patchProjectMember, patchProjectMilestone, patchProjectRisk, patchProjectTask, type ConfidentialityLevel, type DocumentStorageType, type ProjectCommunicationChannel, type ProjectClosureChecklistStatus, type ProjectDeliveryRecordType, type ProjectMilestoneStatus, type ProjectRiskLevel, type ProjectStatus, type ProjectTaskPriority, type ProjectTaskStatus } from "../api/projects";
import { fetchContractsRegistry } from "../api/contractsRegistry";
import { mapApiErrorMessage } from "../api/errorMessages";
import { DetailSideMeta } from "../components/patterns/DetailSideMeta";
@@ -98,6 +98,7 @@ export function ProjectDetailPage() {
});
const expensesQuery = useQuery({ enabled: Boolean(projectId), queryKey: ["projects", projectId, "expenses"], queryFn: () => fetchProjectExpensesSummary(projectId) });
const travelQuery = useQuery({ enabled: Boolean(projectId), queryKey: ["projects", projectId, "travel"], queryFn: () => fetchProjectTravelSummary(projectId) });
const purchaseQuery = useQuery({ enabled: Boolean(projectId), queryKey: ["projects", projectId, "purchase-requests"], queryFn: () => fetchProjectPurchaseSummary(projectId) });
const weeklyQuery = useQuery({ enabled: Boolean(projectId), queryKey: ["projects", projectId, "weekly"], queryFn: () => fetchProjectWeeklyReports(projectId) });
const deliveriesQuery = useQuery({ enabled: Boolean(projectId), queryKey: ["projects", projectId, "deliveries"], queryFn: () => fetchProjectDeliveries(projectId) });
const docsQuery = useQuery({ enabled: Boolean(projectId), queryKey: ["projects", projectId, "document-refs"], queryFn: () => fetchProjectDocumentRefs(projectId) });
@@ -524,6 +525,7 @@ export function ProjectDetailPage() {
<MetricCard title="任务" value={counts?.tasks ?? 0} note="按优先级与状态推进" />
<MetricCard title="风险" value={counts?.risks ?? 0} note="高风险建议指定责任人" tone={(counts?.risks ?? 0) > 0 ? "warning" : "neutral"} />
<MetricCard title="报销合计" value={expensesQuery.data?.totalAmount ?? "0.00"} note="当前项目已关联报销" />
<MetricCard title="采购合计" value={purchaseQuery.data?.totalAmount ?? "0.00"} note="当前项目已关联采购" />
<MetricCard title="差旅合计" value={travelQuery.data?.totalAmount ?? "0.00"} note="当前项目已关联差旅" />
</section>
@@ -1010,6 +1012,28 @@ export function ProjectDetailPage() {
))}
</div>
</div>
<div className="rounded-lg border border-zinc-200 p-4">
<div className="text-sm text-zinc-500">采购</div>
<div className="mt-1 text-xl font-semibold text-zinc-900">{purchaseQuery.data?.totalAmount ?? "0.00"}</div>
<div className="mt-2 space-y-2 text-sm">
{purchaseQuery.isLoading ? <div className="text-zinc-500">采购加载中...</div> : null}
{purchaseQuery.isError ? <div className="text-red-600">{resolveError(purchaseQuery.error, "加载采购摘要失败")}</div> : null}
{!purchaseQuery.isLoading && !purchaseQuery.isError && !(purchaseQuery.data?.items.length ?? 0) ? (
<div className="text-zinc-500">暂无关联采购</div>
) : null}
{(purchaseQuery.data?.items ?? []).slice(0, 5).map((item) => (
<Link key={item.id} to={`/purchase-requests?id=${item.id}`} className="block rounded-lg border border-zinc-100 px-3 py-2 hover:bg-zinc-50">
<div className="flex items-center justify-between gap-3">
<span className="truncate">{item.requestNo || item.title}</span>
<StatusBadge status={item.status} />
</div>
<div className="mt-1 text-xs text-zinc-500">
{item.totalAmount} · {item.updatedAt ? formatDateTime(item.updatedAt) : "-"}
</div>
</Link>
))}
</div>
</div>
<div className="rounded-lg border border-zinc-200 p-4">
<div className="text-sm text-zinc-500">项目周报</div>
<div className="mt-1 text-xl font-semibold text-zinc-900">{weeklyQuery.data?.items.length ?? 0}</div>