From e52b3a6d382fc3f4d6176e6fd257cee673be6f7b Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 21 May 2026 19:07:00 +0200 Subject: [PATCH] feat(notifications): include berth-range suffix in stage-change titles Stage-change notification titles previously read "Acme Corp moved to Reservation" with no context on which berths the deal covers. For multi-berth deals the rep had to drill into the interest to see what moved. With multiple deals in flight per client the bell tray became ambiguous. Switch the title-build path from `getPrimaryBerth` (single-row) to `listBerthsForInterest` (full set) and append a compact suffix via `formatBerthRange()`: Acme Corp moved to Reservation [A1-A3, B5] Falls back to plain " moved to " when the interest has no linked berths. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lib/services/interests.service.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/lib/services/interests.service.ts b/src/lib/services/interests.service.ts index 77c0bba9..9d33e78b 100644 --- a/src/lib/services/interests.service.ts +++ b/src/lib/services/interests.service.ts @@ -24,10 +24,12 @@ import { logger } from '@/lib/logger'; import { getPrimaryBerth, getPrimaryBerthsForInterests, + listBerthsForInterest, removeInterestBerth, upsertInterestBerth, upsertInterestBerthTx, } from '@/lib/services/interest-berths.service'; +import { formatBerthRange } from '@/lib/templates/berth-range'; import { buildListQuery } from '@/lib/db/query-builder'; import { diffEntity } from '@/lib/entity-diff'; import { softDelete, restore, withTransaction } from '@/lib/db/utils'; @@ -1041,17 +1043,22 @@ export async function changeInterestStage( // canonical STAGE_LABELS dictionary so "deposit_10pct" reads as // "10% Deposit" everywhere. void (async () => { - const [{ createNotification }, clientRow, primaryBerth] = await Promise.all([ + const [{ createNotification }, clientRow, allBerths] = await Promise.all([ import('@/lib/services/notifications.service'), db.query.clients.findFirst({ where: eq(clients.id, existing.clientId), columns: { fullName: true }, }), - getPrimaryBerth(id).catch(() => null), + listBerthsForInterest(id).catch( + () => [] as Awaited>, + ), ]); + const primaryBerth = allBerths[0] ?? null; + const moorings = allBerths.map((b) => b.mooringNumber).filter((m): m is string => Boolean(m)); + const berthSuffix = moorings.length > 0 ? ` [${formatBerthRange(moorings)}]` : ''; const subject = clientRow?.fullName ?? - (primaryBerth ? `Berth ${primaryBerth.mooringNumber}` : 'this interest'); + (primaryBerth?.mooringNumber ? `Berth ${primaryBerth.mooringNumber}` : 'this interest'); const fromLabel = oldStage ? (STAGE_LABELS[oldStage as PipelineStage] ?? oldStage.replace(/_/g, ' ')) : 'unknown'; @@ -1061,7 +1068,7 @@ export async function changeInterestStage( portId, userId: meta.userId, type: 'interest_stage_changed', - title: `${subject} moved to ${toLabel}`, + title: `${subject} moved to ${toLabel}${berthSuffix}`, description: `Stage changed from ${fromLabel} to ${toLabel}.`, link: `/interests/${id}`, entityType: 'interest',