mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-08 11:37:25 +00:00
Only let surveyed outcomes override a Do Not Book status
Previously, any HubSpot outcome would override bookingStatus="Do Not Book" and keep the property in the normal pipeline. That was too permissive — outcomes like "Tenant Refusal" or "Not Viable" combined with Do Not Book should classify the property as Removed from Bookings, not lurk in Queries or the survey-issues bucket. Now only completed survey outcomes (Surveyed, Surveyed - Pending Upload, EPC Completed) override Do Not Book. Any other outcome + Do Not Book falls through to Removed from Bookings, surfaces in the Halted or Removed panel, and gets the matching stage badge in the Properties tab. The redundant "Removed from Bookings" chip in the drill-down table is gone since the stage classification now carries that signal cleanly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5ff99a636b
commit
56fdfa06e4
5 changed files with 77 additions and 13 deletions
|
|
@ -60,7 +60,6 @@ function RemovalFlagChip({ label, tooltip }: { label: string; tooltip: string })
|
|||
|
||||
function DealNameCell({ deal }: { deal: ClassifiedDeal }) {
|
||||
const name = deal.dealname;
|
||||
const isDoNotBook = deal.bookingStatus === "Do Not Book";
|
||||
const isRemovedFromProgram = deal.batch === "Removed from Program";
|
||||
|
||||
return (
|
||||
|
|
@ -68,12 +67,6 @@ function DealNameCell({ deal }: { deal: ClassifiedDeal }) {
|
|||
<span className="text-sm text-gray-800">
|
||||
{name ?? <span className="text-gray-300">—</span>}
|
||||
</span>
|
||||
{isDoNotBook && (
|
||||
<RemovalFlagChip
|
||||
label="Removed from Bookings"
|
||||
tooltip="bookingStatus is Do Not Book in HubSpot. If an outcome is set above, it is real history; no further action expected."
|
||||
/>
|
||||
)}
|
||||
{isRemovedFromProgram && (
|
||||
<RemovalFlagChip
|
||||
label="Removed from Program"
|
||||
|
|
|
|||
|
|
@ -4,8 +4,7 @@ import { motion } from "framer-motion";
|
|||
import { AlertCircle } from "lucide-react";
|
||||
import { Card, CardContent } from "@/app/shadcn_components/ui/card";
|
||||
import type { ClassifiedDeal } from "./types";
|
||||
|
||||
const SUCCESSFUL_OUTCOMES = new Set(["Surveyed", "Surveyed - Pending Upload", "EPC Completed"]);
|
||||
import { SUCCESSFUL_SURVEY_OUTCOMES } from "./types";
|
||||
|
||||
const COLUMNS: (keyof ClassifiedDeal)[] = [
|
||||
"dealname",
|
||||
|
|
@ -35,9 +34,14 @@ export default function SurveyIssuesPanel({
|
|||
deals,
|
||||
onOpenTable,
|
||||
}: SurveyIssuesPanelProps) {
|
||||
// Filter to deals with a populated outcome that is not a success
|
||||
// Deals with a non-successful outcome that are still in the active pipeline.
|
||||
// DNB-overridden rows are classified as "Removed from Bookings" and surfaced
|
||||
// in the Halted or Removed panel instead — exclude them here to avoid double-counting.
|
||||
const issueDeals = deals.filter(
|
||||
(d) => d.outcome && !SUCCESSFUL_OUTCOMES.has(d.outcome),
|
||||
(d) =>
|
||||
d.outcome &&
|
||||
!SUCCESSFUL_SURVEY_OUTCOMES.has(d.outcome) &&
|
||||
d.displayStage !== "Removed from Bookings",
|
||||
);
|
||||
|
||||
if (issueDeals.length === 0) return null;
|
||||
|
|
|
|||
|
|
@ -144,13 +144,68 @@ describe("resolveDisplayStage — Removed from Bookings", () => {
|
|||
).toBe("Removed from Bookings");
|
||||
});
|
||||
|
||||
it("falls back to normal stage resolution when bookingStatus is Do Not Book but an outcome is set", () => {
|
||||
it("classifies as Removed from Bookings when bookingStatus is Do Not Book and outcome is a non-successful survey outcome", () => {
|
||||
// Tenant Refusal is a non-successful outcome; with DNB the property is removed from bookings,
|
||||
// not lingering in the normal pipeline.
|
||||
expect(
|
||||
resolveDisplayStage(makeDeal({
|
||||
dealstage: "1617223910",
|
||||
bookingStatus: "Do Not Book",
|
||||
outcome: "Tenant Refusal",
|
||||
}))
|
||||
).toBe("Removed from Bookings");
|
||||
});
|
||||
|
||||
it("falls back to normal stage resolution when bookingStatus is Do Not Book but outcome is Surveyed", () => {
|
||||
// Real survey history overrides DNB — the property stays in the normal pipeline.
|
||||
expect(
|
||||
resolveDisplayStage(makeDeal({
|
||||
dealstage: "1617223910",
|
||||
bookingStatus: "Do Not Book",
|
||||
outcome: "Surveyed",
|
||||
}))
|
||||
).toBe("Scope & Planning");
|
||||
});
|
||||
|
||||
it("falls back to normal stage resolution when bookingStatus is Do Not Book but outcome is Surveyed - Pending Upload", () => {
|
||||
expect(
|
||||
resolveDisplayStage(makeDeal({
|
||||
dealstage: "1617223910",
|
||||
bookingStatus: "Do Not Book",
|
||||
outcome: "Surveyed - Pending Upload",
|
||||
}))
|
||||
).toBe("Scope & Planning");
|
||||
});
|
||||
|
||||
it("falls back to normal stage resolution when bookingStatus is Do Not Book but outcome is EPC Completed", () => {
|
||||
expect(
|
||||
resolveDisplayStage(makeDeal({
|
||||
dealstage: "1617223910",
|
||||
bookingStatus: "Do Not Book",
|
||||
outcome: "EPC Completed",
|
||||
}))
|
||||
).toBe("Scope & Planning");
|
||||
});
|
||||
|
||||
it("classifies as Removed from Bookings for Not Viable + Do Not Book even when the dealstage maps to Queries", () => {
|
||||
// "1887735998" is the Not Viable dealstage, which normally maps to "Queries".
|
||||
// With DNB + a non-successful outcome, Removed from Bookings takes precedence over the Queries mapping.
|
||||
expect(
|
||||
resolveDisplayStage(makeDeal({
|
||||
dealstage: "1887735998",
|
||||
bookingStatus: "Do Not Book",
|
||||
outcome: "Not Viable",
|
||||
}))
|
||||
).toBe("Removed from Bookings");
|
||||
});
|
||||
|
||||
it("does not classify as Removed from Bookings when an outcome is set but bookingStatus is null", () => {
|
||||
expect(
|
||||
resolveDisplayStage(makeDeal({
|
||||
dealstage: "1617223910",
|
||||
bookingStatus: null,
|
||||
outcome: "Surveyed",
|
||||
}))
|
||||
).toBe("Scope & Planning");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import type {
|
|||
import {
|
||||
STAGE_ORDER,
|
||||
MAJOR_CONDITION_STAGE_ID,
|
||||
SUCCESSFUL_SURVEY_OUTCOMES,
|
||||
} from "./types";
|
||||
|
||||
// Terminal stages that exit the pipeline by design — excluded from funnel
|
||||
|
|
@ -116,7 +117,10 @@ export function resolveDisplayStage(deal: HubspotDeal): DisplayStage {
|
|||
return "Removed from Program";
|
||||
}
|
||||
|
||||
if (deal.bookingStatus === "Do Not Book" && !deal.outcome) {
|
||||
if (
|
||||
deal.bookingStatus === "Do Not Book" &&
|
||||
!(deal.outcome && SUCCESSFUL_SURVEY_OUTCOMES.has(deal.outcome))
|
||||
) {
|
||||
return "Removed from Bookings";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -316,6 +316,14 @@ export const SURVEYOR_OUTCOMES = [
|
|||
|
||||
export type SurveyorOutcome = (typeof SURVEYOR_OUTCOMES)[number];
|
||||
|
||||
// Outcomes that represent real completed survey history. Take precedence over
|
||||
// a "Do Not Book" booking status — the property stays in the normal pipeline.
|
||||
export const SUCCESSFUL_SURVEY_OUTCOMES: ReadonlySet<string> = new Set([
|
||||
"Surveyed",
|
||||
"Surveyed - Pending Upload",
|
||||
"EPC Completed",
|
||||
]);
|
||||
|
||||
export const MAJOR_CONDITION_STAGE_ID = "3061261536" as const;
|
||||
|
||||
// Order of stages for grouping/display (queries excluded from this list)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue