refactor(clients): drop deprecated yacht/company/proxy columns
PR 13: now that all reads are migrated to the dedicated yacht / company
/ membership entities, drop the columns that mirrored them on `clients`:
companyName, isProxy, proxyType, actualOwnerName, relationshipNotes,
yachtName, yachtLength{Ft,M}, yachtWidth{Ft,M}, yachtDraft{Ft,M},
berthSizeDesired.
Migration `0008_loud_ikaris.sql` issues the destructive ALTER TABLE
DROP COLUMN statements. Run `pnpm db:push` (or the migration runner) to
apply.
Caller cleanup (zero behavioral change to remaining flows):
- Drops the legacy `generateEoi` flow entirely (route, service function,
pdfme template, validator schema). The dual-path generate-and-sign
service from PR 11 has fully replaced it; the route was no longer
wired to the UI.
- `clients.service`: company-name search column / WHERE / audit value
removed; search now ranks by full name only.
- `interests.service`: `resolveLeadCategory` reads dimensions from
`yachts` via `interest.yachtId` instead of the dropped
`client.yachtLength{Ft,M}`.
- `record-export`: client-summary now lists yachts via owner-side
lookup (direct + active company memberships); interest-summary fetches
yacht via `interest.yachtId`. Both PDF templates updated to read
yacht details from the new entity.
- `client-detail-header`, `client-picker`, `command-search`,
`search-result-item`, `use-search` hook, `types/domain.ts`,
`search.service` — drop the companyName badge / sub-label / typed
field everywhere it was rendered or fetched.
- `ai.ts` worker: drop the company / yacht context lines from the
prompt (will be re-added later sourced from the new entities).
- `validators/interests.ts`: remove the deprecated public-form flat
yacht/company fields. The route already ignores them.
- `factories.ts`: drop the `isProxy: false` default.
Tests: 652/652 green; type-check clean. The
`security-sensitive-data` tests use `companyName` / `isProxy` as
arbitrary record keys for a generic util — left unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import { and, desc, eq, inArray } from 'drizzle-orm';
|
||||
import { and, desc, eq, inArray, isNull, or } from 'drizzle-orm';
|
||||
|
||||
import { db } from '@/lib/db';
|
||||
import { clients, clientContacts } from '@/lib/db/schema/clients';
|
||||
import { interests } from '@/lib/db/schema/interests';
|
||||
import { berths, berthWaitingList, berthMaintenanceLog } from '@/lib/db/schema/berths';
|
||||
import { yachts } from '@/lib/db/schema/yachts';
|
||||
import { companyMemberships } from '@/lib/db/schema/companies';
|
||||
import { auditLogs } from '@/lib/db/schema/system';
|
||||
import { ports } from '@/lib/db/schema/ports';
|
||||
import { NotFoundError } from '@/lib/errors';
|
||||
@@ -12,10 +14,7 @@ import {
|
||||
clientSummaryTemplate,
|
||||
buildClientSummaryInputs,
|
||||
} from '@/lib/pdf/templates/client-summary-template';
|
||||
import {
|
||||
berthSpecTemplate,
|
||||
buildBerthSpecInputs,
|
||||
} from '@/lib/pdf/templates/berth-spec-template';
|
||||
import { berthSpecTemplate, buildBerthSpecInputs } from '@/lib/pdf/templates/berth-spec-template';
|
||||
import {
|
||||
interestSummaryTemplate,
|
||||
buildInterestSummaryInputs,
|
||||
@@ -63,9 +62,7 @@ export async function exportClientPdf(clientId: string, portId: string): Promise
|
||||
.limit(20);
|
||||
|
||||
// Enrich interests with berth mooring numbers
|
||||
const berthIds = interestList
|
||||
.map((i) => i.berthId)
|
||||
.filter(Boolean) as string[];
|
||||
const berthIds = interestList.map((i) => i.berthId).filter(Boolean) as string[];
|
||||
|
||||
let berthsMap: Record<string, string> = {};
|
||||
if (berthIds.length > 0) {
|
||||
@@ -81,7 +78,44 @@ export async function exportClientPdf(clientId: string, portId: string): Promise
|
||||
berthMooringNumber: i.berthId ? (berthsMap[i.berthId] ?? null) : null,
|
||||
}));
|
||||
|
||||
const inputs = buildClientSummaryInputs(client, contactList, enrichedInterests, activity, port ?? {});
|
||||
// Yachts owned by the client directly OR by a company they're an active
|
||||
// member of. Active membership = no end date.
|
||||
const memberCompanies = await db
|
||||
.select({ companyId: companyMemberships.companyId })
|
||||
.from(companyMemberships)
|
||||
.where(and(eq(companyMemberships.clientId, clientId), isNull(companyMemberships.endDate)));
|
||||
const companyIds = memberCompanies.map((m) => m.companyId);
|
||||
|
||||
const ownerConditions = [
|
||||
and(eq(yachts.currentOwnerType, 'client'), eq(yachts.currentOwnerId, clientId))!,
|
||||
];
|
||||
if (companyIds.length > 0) {
|
||||
ownerConditions.push(
|
||||
and(eq(yachts.currentOwnerType, 'company'), inArray(yachts.currentOwnerId, companyIds))!,
|
||||
);
|
||||
}
|
||||
|
||||
const ownedYachts = await db
|
||||
.select({
|
||||
name: yachts.name,
|
||||
lengthFt: yachts.lengthFt,
|
||||
widthFt: yachts.widthFt,
|
||||
draftFt: yachts.draftFt,
|
||||
lengthM: yachts.lengthM,
|
||||
widthM: yachts.widthM,
|
||||
draftM: yachts.draftM,
|
||||
})
|
||||
.from(yachts)
|
||||
.where(and(eq(yachts.portId, portId), isNull(yachts.archivedAt), or(...ownerConditions)));
|
||||
|
||||
const inputs = buildClientSummaryInputs(
|
||||
client,
|
||||
contactList,
|
||||
ownedYachts,
|
||||
enrichedInterests,
|
||||
activity,
|
||||
port ?? {},
|
||||
);
|
||||
|
||||
return generatePdf(clientSummaryTemplate, [inputs]);
|
||||
}
|
||||
@@ -143,7 +177,13 @@ export async function exportBerthPdf(berthId: string, portId: string): Promise<U
|
||||
.orderBy(desc(interests.updatedAt))
|
||||
.limit(20);
|
||||
|
||||
const inputs = buildBerthSpecInputs(berth, enrichedWaitingList, maintenance, linkedInterests, port ?? {});
|
||||
const inputs = buildBerthSpecInputs(
|
||||
berth,
|
||||
enrichedWaitingList,
|
||||
maintenance,
|
||||
linkedInterests,
|
||||
port ?? {},
|
||||
);
|
||||
|
||||
return generatePdf(berthSpecTemplate, [inputs]);
|
||||
}
|
||||
@@ -169,6 +209,11 @@ export async function exportInterestPdf(interestId: string, portId: string): Pro
|
||||
berth = await db.query.berths.findFirst({ where: eq(berths.id, interest.berthId) });
|
||||
}
|
||||
|
||||
let yacht = null;
|
||||
if (interest.yachtId) {
|
||||
yacht = await db.query.yachts.findFirst({ where: eq(yachts.id, interest.yachtId) });
|
||||
}
|
||||
|
||||
// Audit timeline (last 20 events for this interest)
|
||||
const timeline = await db
|
||||
.select()
|
||||
@@ -183,7 +228,14 @@ export async function exportInterestPdf(interestId: string, portId: string): Pro
|
||||
.orderBy(desc(auditLogs.createdAt))
|
||||
.limit(20);
|
||||
|
||||
const inputs = buildInterestSummaryInputs(interest, client ?? {}, berth ?? null, timeline, port ?? {});
|
||||
const inputs = buildInterestSummaryInputs(
|
||||
interest,
|
||||
client ?? {},
|
||||
yacht ?? null,
|
||||
berth ?? null,
|
||||
timeline,
|
||||
port ?? {},
|
||||
);
|
||||
|
||||
return generatePdf(interestSummaryTemplate, [inputs]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user