fix(legacy-stage): purge 9-stage enum keys from rank tables and stale copy
L-001 hunt landed these:
- src/lib/services/clients.service.ts — stageRank used pre-refactor
9-stage names exclusively (`contract_signed`, `deposit_10pct`, …).
Every modern 7-stage interest fell to rank 0, making client-list
"most-progressed deal" sort effectively random. Modern values now
own the canonical ranks; legacy aliases map to their 7-stage
equivalents so historical audit data still sorts.
- src/lib/services/berth-recommender.service.ts — STAGE_ORDER had
the same 9-stage shape. LATE_STAGE_THRESHOLD pointed at the (now
nonexistent) `deposit_10pct` slot. Reworked to the 7-stage scale;
threshold now at `deposit_paid` (5).
- Stale comments referencing `deposit_10pct` in schema (clients,
financial) and client-archive services updated to current copy.
- Smart-archive dialog rendered `i.pipelineStage` as raw enum; now
routes through `stageLabelFor` (the new helper added with A2).
Test fixture updates: berth-recommender.test.ts numeric inputs
re-mapped to the new 7-stage scale (eoi_signed=5 → eoi=3, etc.).
1373/1373 vitest pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -184,20 +184,32 @@ export async function loadRecommenderSettings(portId: string): Promise<Recommend
|
||||
|
||||
// ─── Tier mapping ──────────────────────────────────────────────────────────
|
||||
|
||||
// L-001: modern 7-stage ranks own the canonical positions; legacy 9-stage
|
||||
// keys map to their post-refactor equivalents so historical interests in
|
||||
// the recommender stage-aware queries continue to rank correctly.
|
||||
const STAGE_ORDER: Record<string, number> = {
|
||||
// modern
|
||||
enquiry: 1,
|
||||
qualified: 2,
|
||||
nurturing: 2,
|
||||
eoi: 3,
|
||||
reservation: 4,
|
||||
deposit_paid: 5,
|
||||
contract: 6,
|
||||
// legacy aliases
|
||||
open: 1,
|
||||
details_sent: 2,
|
||||
in_communication: 3,
|
||||
eoi_sent: 4,
|
||||
eoi_signed: 5,
|
||||
deposit_10pct: 6,
|
||||
contract_sent: 7,
|
||||
contract_signed: 8,
|
||||
completed: 9,
|
||||
details_sent: 1,
|
||||
in_communication: 2,
|
||||
eoi_sent: 3,
|
||||
eoi_signed: 3,
|
||||
deposit_10pct: 5,
|
||||
contract_sent: 6,
|
||||
contract_signed: 6,
|
||||
completed: 6,
|
||||
};
|
||||
|
||||
/** Stage at which a berth is "in late stage" (Tier D when active). */
|
||||
const LATE_STAGE_THRESHOLD = STAGE_ORDER.deposit_10pct!; // 6
|
||||
const LATE_STAGE_THRESHOLD = STAGE_ORDER.deposit_paid!; // 5
|
||||
|
||||
export type Tier = 'A' | 'B' | 'C' | 'D';
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ export interface ClientArchiveDossier {
|
||||
* confirmation + reason required). */
|
||||
stakeLevel: ArchiveStakeLevel;
|
||||
/** The interest stage that earned the high-stakes classification (so
|
||||
* the UI can explain "this client is in deposit_10pct, please confirm").
|
||||
* the UI can explain "this client is in Deposit Paid, please confirm").
|
||||
* Null when low-stakes. */
|
||||
highStakesStage: PipelineStage | null;
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ export async function archiveClientWithDecisions(args: {
|
||||
|
||||
if (dossier.stakeLevel === 'high' && !decisions.reason.trim()) {
|
||||
throw new ValidationError(
|
||||
'A reason is required when archiving a client at deposit_10pct or later.',
|
||||
'A reason is required when archiving a client at Deposit Paid or later.',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -252,21 +252,36 @@ export async function listClients(portId: string, query: ListClientsInput) {
|
||||
// Aggregate berths per client, sorted so the most-action-worthy
|
||||
// interest floats to the top of the chip row. Priority:
|
||||
// 1. open outcome (active deal) before closed (won/lost/cancelled)
|
||||
// 2. within open: most progressed stage first (contract_signed > … > open)
|
||||
// 2. within open: most progressed stage first (contract > … > enquiry)
|
||||
// 3. tie-breaker: mooring number alphabetical for stable ordering
|
||||
// The list-view UI shows the top 2 with full labels; the rest fall
|
||||
// through into a "+N more" popover.
|
||||
//
|
||||
// L-001 fix: pre-refactor this map used the 9-stage legacy names
|
||||
// (contract_signed, deposit_10pct, …) and every modern 7-stage value
|
||||
// fell through to rank 0, making the sort effectively random for any
|
||||
// post-refactor interest. Modern values now own the canonical ranks
|
||||
// and legacy keys map to their 7-stage equivalents so historical data
|
||||
// continues to sort correctly.
|
||||
const stageRank: Record<string, number> = {
|
||||
// modern (post 9→7 refactor)
|
||||
contract: 1,
|
||||
deposit_paid: 2,
|
||||
reservation: 3,
|
||||
eoi: 4,
|
||||
nurturing: 5,
|
||||
qualified: 6,
|
||||
enquiry: 7,
|
||||
// legacy aliases — kept so audit-log + soft-archive data sorts the same
|
||||
contract_signed: 1,
|
||||
contract_sent: 1,
|
||||
completed: 1,
|
||||
deposit_10pct: 2,
|
||||
contract_sent: 3,
|
||||
eoi_signed: 4,
|
||||
eoi_sent: 5,
|
||||
eoi_sent: 4,
|
||||
in_communication: 6,
|
||||
details_sent: 7,
|
||||
qualified: 8,
|
||||
open: 9,
|
||||
completed: 10,
|
||||
open: 7,
|
||||
};
|
||||
type LinkedBerth = {
|
||||
id: string;
|
||||
|
||||
Reference in New Issue
Block a user