mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-08 11:37:25 +00:00
Remove unused WorkPhaseStats and consolidate deal mapping into shared module
Delete WorkPhaseStats type and its four computation blocks (coordination, design, install, lodgement) from transforms.ts and types.ts — the computed values were never read by any component. Extract mapDbRowToHubspotDeal, DealRow, and the coordinator/designer aliases into a new dealQuery.ts module, eliminating the verbatim duplication between the live tracker page and the deal detail page. Replace the inline doc status computation in [dealId]/page.tsx with calls to the existing fetchDocsByDealId and computeDocStatusMap from docStatus.ts, so both paths now share a single implementation.
This commit is contained in:
parent
281f15c11e
commit
e52ab73e9f
5 changed files with 90 additions and 374 deletions
|
|
@ -1,104 +1,23 @@
|
|||
import { getServerSession } from "next-auth";
|
||||
import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
|
||||
import { redirect, notFound } from "next/navigation";
|
||||
import { eq, inArray, and, desc } from "drizzle-orm";
|
||||
import { eq, inArray, and, desc, sql } from "drizzle-orm";
|
||||
import { db } from "@/app/db/db";
|
||||
import { hubspotDealData } from "@/app/db/schema/crm/hubspot_deal_table";
|
||||
import { alias } from "drizzle-orm/pg-core";
|
||||
import { hubspotUsers } from "@/app/db/schema/crm/hubspot_user_table";
|
||||
import { uploadedFiles } from "@/app/db/schema/uploaded_files";
|
||||
import { portfolioOrganisation } from "@/app/db/schema/portfolio_organisation";
|
||||
import { organisation } from "@/app/db/schema/organisation";
|
||||
import { portfolioCapabilities, portfolioUsers } from "@/app/db/schema/portfolio";
|
||||
import { dealMeasureApprovals } from "@/app/db/schema/approvals";
|
||||
import { propertyRemovalRequests } from "@/app/db/schema/removal_requests";
|
||||
import { user as userTable } from "@/app/db/schema/users";
|
||||
import { sql } from "drizzle-orm";
|
||||
import type {
|
||||
HubspotDeal,
|
||||
DocStatus,
|
||||
MeasureDocProgress,
|
||||
PortfolioCapabilityType,
|
||||
EffectiveRemovalState,
|
||||
} from "../types";
|
||||
import {
|
||||
EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES,
|
||||
SURVEY_ALL_DOC_TYPES,
|
||||
} from "../types";
|
||||
import { getRequiredDocs } from "@/app/lib/measureDocumentRequirements";
|
||||
import type { DocStatus, PortfolioCapabilityType, EffectiveRemovalState } from "../types";
|
||||
import { classifyDeals } from "../transforms";
|
||||
import type { InferSelectModel } from "drizzle-orm";
|
||||
import { fetchDocsByDealId, computeDocStatusMap } from "../docStatus";
|
||||
import { coordinatorUser, designerUser, mapDbRowToHubspotDeal } from "../dealQuery";
|
||||
import type { DealRow } from "../dealQuery";
|
||||
import DealPage from "./DealPage";
|
||||
import Link from "next/link";
|
||||
|
||||
const coordinatorUser = alias(hubspotUsers, "coordinator_user");
|
||||
const designerUser = alias(hubspotUsers, "designer_user");
|
||||
|
||||
type DealRow = {
|
||||
deal: InferSelectModel<typeof hubspotDealData>;
|
||||
coordinator: string | null;
|
||||
designer: string | null;
|
||||
};
|
||||
|
||||
function mapDbRowToHubspotDeal(row: DealRow): HubspotDeal {
|
||||
const d = row.deal;
|
||||
return {
|
||||
id: d.id,
|
||||
dealId: d.dealId,
|
||||
dealname: d.dealname,
|
||||
dealstage: d.dealstage,
|
||||
companyId: d.companyId,
|
||||
projectCode: d.projectCode,
|
||||
landlordPropertyId: d.landlordPropertyId,
|
||||
uprn: d.uprn,
|
||||
outcome: d.outcome,
|
||||
outcomeNotes: d.outcomeNotes,
|
||||
majorConditionIssueDescription: d.majorConditionIssueDescription,
|
||||
majorConditionIssuePhotos: d.majorConditionIssuePhotos,
|
||||
majorConditionIssuePhotosS3: d.majorConditionIssuePhotosS3,
|
||||
coordinationStatus: d.coordinationStatus,
|
||||
designStatus: d.designStatus,
|
||||
pashubLink: d.pashubLink,
|
||||
sharepointLink: d.sharepointLink,
|
||||
dampMouldFlag: d.dampmouldGrowth,
|
||||
dampMouldAndRepairComments: d.damnpMouldAndRepairComments,
|
||||
preSapScore: d.preSap,
|
||||
coordinator: row.coordinator,
|
||||
ioeV1Date: d.mtpCompletionDate,
|
||||
ioeV2Date: d.mtpReModelCompletionDate,
|
||||
ioeV3Date: d.ioeV3CompletionDate,
|
||||
proposedMeasures: d.proposedMeasures,
|
||||
approvedPackage: d.approvedPackage,
|
||||
designer: row.designer,
|
||||
designDate: d.designCompletionDate,
|
||||
actualMeasuresInstalled: d.actualMeasuresInstalled,
|
||||
installer: d.installer,
|
||||
installerHandover: d.installerHandover,
|
||||
lodgementStatus: d.lodgementStatus,
|
||||
measuresLodgementDate: d.measuresLodgementDate,
|
||||
fullLodgementDate: d.lodgementDate,
|
||||
confirmedSurveyDate: d.confirmedSurveyDate,
|
||||
confirmedSurveyTime: d.confirmedSurveyTime,
|
||||
surveyedDate: d.surveyedDate,
|
||||
designType: d.dealType,
|
||||
eiScore: d.eiScore,
|
||||
eiScorePotential: d.eiScorePotential,
|
||||
epcSapScore: d.epcSapScore,
|
||||
epcSapScorePotential: d.epcSapScorePotential,
|
||||
surveyType: d.surveyType,
|
||||
measuresForPibiOrdered: d.measuresForPibiOrdered,
|
||||
pibiOrderDate: d.pibiOrderDate,
|
||||
pibiCompletedDate: d.pibiCompletedDate,
|
||||
propertyHaltedDate: d.propertyHaltedDate,
|
||||
propertyHaltedReason: d.propertyHaltedReason,
|
||||
technicalApprovedMeasuresForInstall: d.technicalApprovedMeasuresForInstall,
|
||||
domnaSurveyType: d.domnaSurveyType,
|
||||
domnaSurveyDate: d.domnaSurveyDate,
|
||||
createdAt: d.createdAt,
|
||||
updatedAt: d.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
export default async function DealDetailPage(props: {
|
||||
params: Promise<{ slug: string; dealId: string }>;
|
||||
}) {
|
||||
|
|
@ -240,99 +159,15 @@ export default async function DealDetailPage(props: {
|
|||
}
|
||||
}
|
||||
|
||||
// Doc status — same two-phase strategy as live tracker
|
||||
const docFiles: Array<{ fileType: string; measureName: string | null }> = [];
|
||||
|
||||
const phase1Rows = await db
|
||||
.select({
|
||||
hubsotDealId: uploadedFiles.hubsotDealId,
|
||||
fileType: uploadedFiles.fileType,
|
||||
measureName: uploadedFiles.measureName,
|
||||
})
|
||||
.from(uploadedFiles)
|
||||
.where(eq(uploadedFiles.hubsotDealId, dealId));
|
||||
|
||||
for (const row of phase1Rows) {
|
||||
if (row.fileType !== null) {
|
||||
docFiles.push({ fileType: row.fileType, measureName: row.measureName });
|
||||
}
|
||||
}
|
||||
|
||||
if (docFiles.length === 0 && deal.uprn) {
|
||||
try {
|
||||
const uprnBig = BigInt(deal.uprn);
|
||||
const phase2Rows = await db
|
||||
.select({
|
||||
fileType: uploadedFiles.fileType,
|
||||
measureName: uploadedFiles.measureName,
|
||||
})
|
||||
.from(uploadedFiles)
|
||||
.where(eq(uploadedFiles.uprn, uprnBig));
|
||||
|
||||
for (const row of phase2Rows) {
|
||||
if (row.fileType !== null) {
|
||||
docFiles.push({
|
||||
fileType: row.fileType,
|
||||
measureName: row.measureName,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Invalid UPRN — skip phase 2
|
||||
}
|
||||
}
|
||||
|
||||
const measures =
|
||||
approvedMeasures.length > 0
|
||||
? approvedMeasures
|
||||
: (deal.proposedMeasures ?? "")
|
||||
.split(",")
|
||||
.map((m: string) => m.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
const surveyDocs = docFiles.filter((d) => SURVEY_ALL_DOC_TYPES.has(d.fileType));
|
||||
const installDocs = docFiles.filter((d) => !SURVEY_ALL_DOC_TYPES.has(d.fileType));
|
||||
const surveyTypeSet = new Set(surveyDocs.map((d) => d.fileType));
|
||||
|
||||
const measureProgress: MeasureDocProgress[] = measures.map((measureName) => {
|
||||
const required = getRequiredDocs(measureName);
|
||||
const docsForMeasure = installDocs.filter(
|
||||
(d) => d.measureName === measureName,
|
||||
);
|
||||
const uploadedTypeSet = new Set(docsForMeasure.map((d) => d.fileType));
|
||||
const uploaded = required.filter((r) => uploadedTypeSet.has(r));
|
||||
return {
|
||||
measureName,
|
||||
required,
|
||||
uploaded,
|
||||
isComplete: uploaded.length === required.length,
|
||||
uploadedCount: uploaded.length,
|
||||
requiredCount: required.length,
|
||||
};
|
||||
});
|
||||
|
||||
let installStatus: DocStatus["installStatus"] = "none";
|
||||
if (installDocs.length > 0) {
|
||||
if (measures.length === 0) {
|
||||
installStatus = "hasDocs";
|
||||
} else {
|
||||
installStatus = measureProgress.every((m) => m.isComplete)
|
||||
? "all"
|
||||
: measureProgress.some((m) => m.uploadedCount > 0)
|
||||
? "partial"
|
||||
: "none";
|
||||
}
|
||||
}
|
||||
|
||||
const docStatus: DocStatus = {
|
||||
presentSurveyTypes: Array.from(surveyTypeSet),
|
||||
hasSurveyDocs: surveyDocs.length > 0,
|
||||
isSurveyComplete: EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES.every((t) =>
|
||||
surveyTypeSet.has(t),
|
||||
),
|
||||
hasInstallDocs: installDocs.length > 0,
|
||||
installStatus,
|
||||
measureProgress,
|
||||
const docsByDealId = await fetchDocsByDealId([hubspotDeal], [dealId]);
|
||||
const docStatusMap = computeDocStatusMap([hubspotDeal], docsByDealId, { [dealId]: approvedMeasures });
|
||||
const docStatus: DocStatus = docStatusMap[dealId] ?? {
|
||||
presentSurveyTypes: [],
|
||||
hasSurveyDocs: false,
|
||||
isSurveyComplete: false,
|
||||
hasInstallDocs: false,
|
||||
installStatus: "none",
|
||||
measureProgress: [],
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
import { alias } from "drizzle-orm/pg-core";
|
||||
import { hubspotUsers } from "@/app/db/schema/crm/hubspot_user_table";
|
||||
import { hubspotDealData } from "@/app/db/schema/crm/hubspot_deal_table";
|
||||
import type { HubspotDeal } from "./types";
|
||||
import type { InferSelectModel } from "drizzle-orm";
|
||||
|
||||
export const coordinatorUser = alias(hubspotUsers, "coordinator_user");
|
||||
export const designerUser = alias(hubspotUsers, "designer_user");
|
||||
|
||||
export type DealRow = {
|
||||
deal: InferSelectModel<typeof hubspotDealData>;
|
||||
coordinator: string | null;
|
||||
designer: string | null;
|
||||
};
|
||||
|
||||
export function mapDbRowToHubspotDeal(row: DealRow): HubspotDeal {
|
||||
const d = row.deal;
|
||||
return {
|
||||
id: d.id,
|
||||
dealId: d.dealId,
|
||||
dealname: d.dealname,
|
||||
dealstage: d.dealstage,
|
||||
companyId: d.companyId,
|
||||
projectCode: d.projectCode,
|
||||
landlordPropertyId: d.landlordPropertyId,
|
||||
uprn: d.uprn,
|
||||
outcome: d.outcome,
|
||||
outcomeNotes: d.outcomeNotes,
|
||||
majorConditionIssueDescription: d.majorConditionIssueDescription,
|
||||
majorConditionIssuePhotos: d.majorConditionIssuePhotos,
|
||||
majorConditionIssuePhotosS3: d.majorConditionIssuePhotosS3,
|
||||
coordinationStatus: d.coordinationStatus,
|
||||
designStatus: d.designStatus,
|
||||
pashubLink: d.pashubLink,
|
||||
sharepointLink: d.sharepointLink,
|
||||
dampMouldFlag: d.dampmouldGrowth,
|
||||
dampMouldAndRepairComments: d.damnpMouldAndRepairComments,
|
||||
preSapScore: d.preSap,
|
||||
coordinator: row.coordinator,
|
||||
ioeV1Date: d.mtpCompletionDate,
|
||||
ioeV2Date: d.mtpReModelCompletionDate,
|
||||
ioeV3Date: d.ioeV3CompletionDate,
|
||||
proposedMeasures: d.proposedMeasures,
|
||||
approvedPackage: d.approvedPackage,
|
||||
designer: row.designer,
|
||||
designDate: d.designCompletionDate,
|
||||
actualMeasuresInstalled: d.actualMeasuresInstalled,
|
||||
installer: d.installer,
|
||||
installerHandover: d.installerHandover,
|
||||
lodgementStatus: d.lodgementStatus,
|
||||
measuresLodgementDate: d.measuresLodgementDate,
|
||||
fullLodgementDate: d.lodgementDate,
|
||||
confirmedSurveyDate: d.confirmedSurveyDate,
|
||||
confirmedSurveyTime: d.confirmedSurveyTime,
|
||||
surveyedDate: d.surveyedDate,
|
||||
designType: d.dealType,
|
||||
eiScore: d.eiScore,
|
||||
eiScorePotential: d.eiScorePotential,
|
||||
epcSapScore: d.epcSapScore,
|
||||
epcSapScorePotential: d.epcSapScorePotential,
|
||||
surveyType: d.surveyType,
|
||||
measuresForPibiOrdered: d.measuresForPibiOrdered,
|
||||
pibiOrderDate: d.pibiOrderDate,
|
||||
pibiCompletedDate: d.pibiCompletedDate,
|
||||
propertyHaltedDate: d.propertyHaltedDate,
|
||||
propertyHaltedReason: d.propertyHaltedReason,
|
||||
technicalApprovedMeasuresForInstall: d.technicalApprovedMeasuresForInstall,
|
||||
domnaSurveyType: d.domnaSurveyType,
|
||||
domnaSurveyDate: d.domnaSurveyDate,
|
||||
createdAt: d.createdAt,
|
||||
updatedAt: d.updatedAt,
|
||||
};
|
||||
}
|
||||
|
|
@ -7,9 +7,9 @@ import { computeLiveTrackerData } from "./transforms";
|
|||
import { fetchDocsByDealId, computeDocStatusMap } from "./docStatus";
|
||||
import { db } from "@/app/db/db";
|
||||
import { hubspotDealData } from "@/app/db/schema/crm/hubspot_deal_table";
|
||||
import { alias } from "drizzle-orm/pg-core";
|
||||
import { hubspotUsers } from "@/app/db/schema/crm/hubspot_user_table";
|
||||
import { portfolioOrganisation } from "@/app/db/schema/portfolio_organisation";
|
||||
import { coordinatorUser, designerUser, mapDbRowToHubspotDeal } from "./dealQuery";
|
||||
import type { DealRow } from "./dealQuery";
|
||||
import { organisation } from "@/app/db/schema/organisation";
|
||||
import {
|
||||
portfolioCapabilities,
|
||||
|
|
@ -20,86 +20,15 @@ import { userDefinedDealMeasures } from "@/app/db/schema/user_defined_deal_measu
|
|||
import { propertyRemovalRequests } from "@/app/db/schema/removal_requests";
|
||||
import { user as userTable } from "@/app/db/schema/users";
|
||||
import type {
|
||||
HubspotDeal,
|
||||
PortfolioCapabilityType,
|
||||
ApprovalsByDeal,
|
||||
InstructedMeasuresByDeal,
|
||||
RemovalStatusByDeal,
|
||||
EffectiveRemovalState,
|
||||
} from "./types";
|
||||
import type { InferSelectModel } from "drizzle-orm";
|
||||
import { Card, CardContent } from "@/app/shadcn_components/ui/card";
|
||||
import { Building2 } from "lucide-react";
|
||||
|
||||
const coordinatorUser = alias(hubspotUsers, "coordinator_user");
|
||||
const designerUser = alias(hubspotUsers, "designer_user");
|
||||
|
||||
type DealRow = {
|
||||
deal: InferSelectModel<typeof hubspotDealData>;
|
||||
coordinator: string | null;
|
||||
designer: string | null;
|
||||
};
|
||||
|
||||
function mapDbRowToHubspotDeal(row: DealRow): HubspotDeal {
|
||||
const d = row.deal;
|
||||
return {
|
||||
id: d.id,
|
||||
dealId: d.dealId,
|
||||
dealname: d.dealname,
|
||||
dealstage: d.dealstage,
|
||||
companyId: d.companyId,
|
||||
projectCode: d.projectCode,
|
||||
landlordPropertyId: d.landlordPropertyId,
|
||||
uprn: d.uprn,
|
||||
outcome: d.outcome,
|
||||
outcomeNotes: d.outcomeNotes,
|
||||
majorConditionIssueDescription: d.majorConditionIssueDescription,
|
||||
majorConditionIssuePhotos: d.majorConditionIssuePhotos,
|
||||
majorConditionIssuePhotosS3: d.majorConditionIssuePhotosS3,
|
||||
coordinationStatus: d.coordinationStatus,
|
||||
designStatus: d.designStatus,
|
||||
pashubLink: d.pashubLink,
|
||||
sharepointLink: d.sharepointLink,
|
||||
dampMouldFlag: d.dampmouldGrowth,
|
||||
dampMouldAndRepairComments: d.damnpMouldAndRepairComments,
|
||||
preSapScore: d.preSap,
|
||||
coordinator: row.coordinator,
|
||||
ioeV1Date: d.mtpCompletionDate,
|
||||
ioeV2Date: d.mtpReModelCompletionDate,
|
||||
ioeV3Date: d.ioeV3CompletionDate,
|
||||
proposedMeasures: d.proposedMeasures,
|
||||
approvedPackage: d.approvedPackage,
|
||||
designer: row.designer,
|
||||
designDate: d.designCompletionDate,
|
||||
actualMeasuresInstalled: d.actualMeasuresInstalled,
|
||||
installer: d.installer,
|
||||
installerHandover: d.installerHandover,
|
||||
lodgementStatus: d.lodgementStatus,
|
||||
measuresLodgementDate: d.measuresLodgementDate,
|
||||
fullLodgementDate: d.lodgementDate,
|
||||
confirmedSurveyDate: d.confirmedSurveyDate,
|
||||
confirmedSurveyTime: d.confirmedSurveyTime,
|
||||
surveyedDate: d.surveyedDate,
|
||||
designType: d.dealType,
|
||||
eiScore: d.eiScore,
|
||||
eiScorePotential: d.eiScorePotential,
|
||||
epcSapScore: d.epcSapScore,
|
||||
epcSapScorePotential: d.epcSapScorePotential,
|
||||
// New per-deal workflow fields
|
||||
surveyType: d.surveyType,
|
||||
measuresForPibiOrdered: d.measuresForPibiOrdered,
|
||||
pibiOrderDate: d.pibiOrderDate,
|
||||
pibiCompletedDate: d.pibiCompletedDate,
|
||||
propertyHaltedDate: d.propertyHaltedDate,
|
||||
propertyHaltedReason: d.propertyHaltedReason,
|
||||
technicalApprovedMeasuresForInstall: d.technicalApprovedMeasuresForInstall,
|
||||
domnaSurveyType: d.domnaSurveyType,
|
||||
domnaSurveyDate: d.domnaSurveyDate,
|
||||
createdAt: d.createdAt,
|
||||
updatedAt: d.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
export default async function LiveReportingPage(props: {
|
||||
params: Promise<{ slug: string }>;
|
||||
}) {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import type {
|
|||
ProjectData,
|
||||
OutcomeSlice,
|
||||
LiveTrackerProps,
|
||||
WorkPhaseStats,
|
||||
|
||||
DampMouldRiskData,
|
||||
FunnelStage,
|
||||
} from "./types";
|
||||
|
|
@ -224,106 +224,6 @@ export function computeProjectProgress(
|
|||
|
||||
const totalDeals = deals.length;
|
||||
|
||||
// Coordination phase:
|
||||
// completed = Design in Progress + Installation in Progress + Installation Complete + At Lodgement + At Post Survey + Project Complete
|
||||
// in progress = Coordination in Progress
|
||||
const coordCompletedDeals = deals.filter((d) =>
|
||||
[
|
||||
"Design in Progress",
|
||||
"Installation in Progress",
|
||||
"Installation Complete",
|
||||
"At Lodgement",
|
||||
"At Post Survey",
|
||||
"Project Complete",
|
||||
].includes(d.displayStage)
|
||||
);
|
||||
const coordInProgressDeals = deals.filter(
|
||||
(d) => d.displayStage === "Coordination in Progress"
|
||||
);
|
||||
|
||||
const coordination: WorkPhaseStats = {
|
||||
completedDeals: coordCompletedDeals,
|
||||
inProgressDeals: coordInProgressDeals,
|
||||
completedCount: coordCompletedDeals.length,
|
||||
inProgressCount: coordInProgressDeals.length,
|
||||
completedPercentage:
|
||||
totalDeals > 0 ? (coordCompletedDeals.length / totalDeals) * 100 : 0,
|
||||
inProgressPercentage:
|
||||
totalDeals > 0 ? (coordInProgressDeals.length / totalDeals) * 100 : 0,
|
||||
total: totalDeals,
|
||||
};
|
||||
|
||||
// Design phase:
|
||||
// completed = Installation in Progress + Installation Complete + At Lodgement + At Post Survey + Project Complete
|
||||
// in progress = Design in Progress
|
||||
const designCompletedDeals = deals.filter((d) =>
|
||||
[
|
||||
"Installation in Progress",
|
||||
"Installation Complete",
|
||||
"At Lodgement",
|
||||
"At Post Survey",
|
||||
"Project Complete",
|
||||
].includes(d.displayStage)
|
||||
);
|
||||
const designInProgressDeals = deals.filter(
|
||||
(d) => d.displayStage === "Design in Progress"
|
||||
);
|
||||
|
||||
const design: WorkPhaseStats = {
|
||||
completedDeals: designCompletedDeals,
|
||||
inProgressDeals: designInProgressDeals,
|
||||
completedCount: designCompletedDeals.length,
|
||||
inProgressCount: designInProgressDeals.length,
|
||||
completedPercentage:
|
||||
totalDeals > 0 ? (designCompletedDeals.length / totalDeals) * 100 : 0,
|
||||
inProgressPercentage:
|
||||
totalDeals > 0 ? (designInProgressDeals.length / totalDeals) * 100 : 0,
|
||||
total: totalDeals,
|
||||
};
|
||||
|
||||
// Install phase:
|
||||
// completed = At Lodgement + At Post Survey + Project Complete
|
||||
// in progress = Installation Complete
|
||||
const installCompletedDeals = deals.filter((d) =>
|
||||
["At Lodgement", "At Post Survey", "Project Complete"].includes(d.displayStage)
|
||||
);
|
||||
const installInProgressDeals = deals.filter(
|
||||
(d) => d.displayStage === "Installation Complete"
|
||||
);
|
||||
|
||||
const install: WorkPhaseStats = {
|
||||
completedDeals: installCompletedDeals,
|
||||
inProgressDeals: installInProgressDeals,
|
||||
completedCount: installCompletedDeals.length,
|
||||
inProgressCount: installInProgressDeals.length,
|
||||
completedPercentage:
|
||||
totalDeals > 0 ? (installCompletedDeals.length / totalDeals) * 100 : 0,
|
||||
inProgressPercentage:
|
||||
totalDeals > 0 ? (installInProgressDeals.length / totalDeals) * 100 : 0,
|
||||
total: totalDeals,
|
||||
};
|
||||
|
||||
// Lodgement phase:
|
||||
// completed = At Post Survey + Project Complete
|
||||
// in progress = At Lodgement
|
||||
const lodgementInProgressDeals = deals.filter(
|
||||
(d) => d.displayStage === "At Lodgement"
|
||||
);
|
||||
|
||||
const lodgement: WorkPhaseStats = {
|
||||
completedDeals,
|
||||
inProgressDeals: lodgementInProgressDeals,
|
||||
completedCount,
|
||||
inProgressCount: lodgementInProgressDeals.length,
|
||||
completedPercentage:
|
||||
totalDeals > 0 ? (completedCount / totalDeals) * 100 : 0,
|
||||
inProgressPercentage:
|
||||
totalDeals > 0
|
||||
? (lodgementInProgressDeals.length / totalDeals) * 100
|
||||
: 0,
|
||||
total: totalDeals,
|
||||
};
|
||||
|
||||
return {
|
||||
stageProgress,
|
||||
queriesDeals,
|
||||
|
|
@ -332,10 +232,6 @@ export function computeProjectProgress(
|
|||
completedPercentage,
|
||||
nonQueryTotal,
|
||||
totalDeals,
|
||||
coordination,
|
||||
design,
|
||||
install,
|
||||
lodgement,
|
||||
dampMouldRisk: computeDampMouldRisk(deals),
|
||||
funnelStages: computeFunnelStages(deals),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -104,19 +104,6 @@ export type StageProgressItem = {
|
|||
deals: ClassifiedDeal[];
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Coordination/Design/Install/Lodgement summary card data
|
||||
// -----------------------------------------------------------------------
|
||||
export type WorkPhaseStats = {
|
||||
completedDeals: ClassifiedDeal[];
|
||||
inProgressDeals: ClassifiedDeal[];
|
||||
completedCount: number;
|
||||
inProgressCount: number;
|
||||
completedPercentage: number; // out of ALL deals in project
|
||||
inProgressPercentage: number;
|
||||
total: number;
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Damp & mould risk comparison (survey-stage vs coordination-stage flags)
|
||||
// -----------------------------------------------------------------------
|
||||
|
|
@ -151,10 +138,6 @@ export type ProjectProgressData = {
|
|||
completedPercentage: number; // out of non-query total
|
||||
nonQueryTotal: number;
|
||||
totalDeals: number;
|
||||
coordination: WorkPhaseStats;
|
||||
design: WorkPhaseStats;
|
||||
install: WorkPhaseStats;
|
||||
lodgement: WorkPhaseStats;
|
||||
dampMouldRisk: DampMouldRiskData;
|
||||
funnelStages: FunnelStage[];
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue