refactor(clients): strip deprecated fields + extend getClientById with yachts/companies/reservations

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-04-24 14:31:14 +02:00
parent a6d6647bb2
commit 4c171848fc

View File

@@ -1,12 +1,10 @@
import { and, eq, ilike, inArray, or } from 'drizzle-orm'; import { and, eq, ilike, inArray, isNull, or } from 'drizzle-orm';
import { db } from '@/lib/db'; import { db } from '@/lib/db';
import { import { clients, clientContacts, clientRelationships, clientTags } from '@/lib/db/schema/clients';
clients, import { companies, companyMemberships } from '@/lib/db/schema/companies';
clientContacts, import { yachts } from '@/lib/db/schema/yachts';
clientRelationships, import { berthReservations } from '@/lib/db/schema/reservations';
clientTags,
} from '@/lib/db/schema/clients';
import { tags } from '@/lib/db/schema/system'; import { tags } from '@/lib/db/schema/system';
import { createAuditLog } from '@/lib/audit'; import { createAuditLog } from '@/lib/audit';
import { NotFoundError } from '@/lib/errors'; import { NotFoundError } from '@/lib/errors';
@@ -32,7 +30,7 @@ interface AuditMeta {
// ─── List ───────────────────────────────────────────────────────────────────── // ─── List ─────────────────────────────────────────────────────────────────────
export async function listClients(portId: string, query: ListClientsInput) { export async function listClients(portId: string, query: ListClientsInput) {
const { page, limit, sort, order, search, includeArchived, source, nationality, isProxy, tagIds } = query; const { page, limit, sort, order, search, includeArchived, source, nationality, tagIds } = query;
const filters = []; const filters = [];
@@ -42,9 +40,6 @@ export async function listClients(portId: string, query: ListClientsInput) {
if (nationality) { if (nationality) {
filters.push(ilike(clients.nationality, `%${nationality}%`)); filters.push(ilike(clients.nationality, `%${nationality}%`));
} }
if (isProxy !== undefined) {
filters.push(eq(clients.isProxy, isProxy));
}
if (tagIds && tagIds.length > 0) { if (tagIds && tagIds.length > 0) {
const clientsWithTags = await db const clientsWithTags = await db
.selectDistinct({ clientId: clientTags.clientId }) .selectDistinct({ clientId: clientTags.clientId })
@@ -105,20 +100,76 @@ export async function getClientById(id: string, portId: string) {
.innerJoin(tags, eq(clientTags.tagId, tags.id)) .innerJoin(tags, eq(clientTags.tagId, tags.id))
.where(eq(clientTags.clientId, id)); .where(eq(clientTags.clientId, id));
const yachtRows = await db.query.yachts.findMany({
where: and(
eq(yachts.portId, portId),
eq(yachts.currentOwnerType, 'client'),
eq(yachts.currentOwnerId, id),
isNull(yachts.archivedAt),
),
columns: {
id: true,
name: true,
hullNumber: true,
registration: true,
lengthFt: true,
widthFt: true,
status: true,
},
});
const membershipRows = await db
.select({
membershipId: companyMemberships.id,
role: companyMemberships.role,
isPrimary: companyMemberships.isPrimary,
startDate: companyMemberships.startDate,
company: {
id: companies.id,
name: companies.name,
legalName: companies.legalName,
status: companies.status,
},
})
.from(companyMemberships)
.innerJoin(companies, eq(companyMemberships.companyId, companies.id))
.where(
and(
eq(companyMemberships.clientId, id),
eq(companies.portId, portId),
isNull(companyMemberships.endDate),
),
);
const activeReservations = await db.query.berthReservations.findMany({
where: and(
eq(berthReservations.clientId, id),
eq(berthReservations.portId, portId),
eq(berthReservations.status, 'active'),
),
columns: {
id: true,
berthId: true,
yachtId: true,
startDate: true,
tenureType: true,
status: true,
},
});
return { return {
...client, ...client,
contacts, contacts,
tags: clientTagRows.map((r) => r.tag), tags: clientTagRows.map((r) => r.tag),
yachts: yachtRows,
companies: membershipRows,
activeReservations,
}; };
} }
// ─── Create ─────────────────────────────────────────────────────────────────── // ─── Create ───────────────────────────────────────────────────────────────────
export async function createClient( export async function createClient(portId: string, data: CreateClientInput, meta: AuditMeta) {
portId: string,
data: CreateClientInput,
meta: AuditMeta,
) {
const result = await withTransaction(async (tx) => { const result = await withTransaction(async (tx) => {
const { contacts: contactsInput, tagIds, ...clientData } = data; const { contacts: contactsInput, tagIds, ...clientData } = data;
@@ -128,15 +179,13 @@ export async function createClient(
.returning(); .returning();
if (contactsInput.length > 0) { if (contactsInput.length > 0) {
await tx.insert(clientContacts).values( await tx
contactsInput.map((c) => ({ clientId: client!.id, ...c })), .insert(clientContacts)
); .values(contactsInput.map((c) => ({ clientId: client!.id, ...c })));
} }
if (tagIds && tagIds.length > 0) { if (tagIds && tagIds.length > 0) {
await tx.insert(clientTags).values( await tx.insert(clientTags).values(tagIds.map((tagId) => ({ clientId: client!.id, tagId })));
tagIds.map((tagId) => ({ clientId: client!.id, tagId })),
);
} }
return client!; return client!;
@@ -153,7 +202,11 @@ export async function createClient(
userAgent: meta.userAgent, userAgent: meta.userAgent,
}); });
emitToRoom(`port:${portId}`, 'client:created', { clientId: result.id, clientName: result.fullName ?? '', source: result.source ?? '' }); emitToRoom(`port:${portId}`, 'client:created', {
clientId: result.id,
clientName: result.fullName ?? '',
source: result.source ?? '',
});
void import('@/lib/services/webhook-dispatch').then(({ dispatchWebhookEvent }) => void import('@/lib/services/webhook-dispatch').then(({ dispatchWebhookEvent }) =>
dispatchWebhookEvent(portId, 'client:created', { clientId: result.id }), dispatchWebhookEvent(portId, 'client:created', { clientId: result.id }),
@@ -198,7 +251,10 @@ export async function updateClient(
userAgent: meta.userAgent, userAgent: meta.userAgent,
}); });
emitToRoom(`port:${portId}`, 'client:updated', { clientId: id, changedFields: Object.keys(diff) }); emitToRoom(`port:${portId}`, 'client:updated', {
clientId: id,
changedFields: Object.keys(diff),
});
void import('@/lib/services/webhook-dispatch').then(({ dispatchWebhookEvent }) => void import('@/lib/services/webhook-dispatch').then(({ dispatchWebhookEvent }) =>
dispatchWebhookEvent(portId, 'client:updated', { clientId: id }), dispatchWebhookEvent(portId, 'client:updated', { clientId: id }),
@@ -311,7 +367,13 @@ export async function updateContact(
contactId: string, contactId: string,
clientId: string, clientId: string,
portId: string, portId: string,
data: Partial<{ channel: string; value: string; label: string; isPrimary: boolean; notes: string }>, data: Partial<{
channel: string;
value: string;
label: string;
isPrimary: boolean;
notes: string;
}>,
_meta: AuditMeta, _meta: AuditMeta,
) { ) {
const client = await db.query.clients.findFirst({ const client = await db.query.clients.findFirst({
@@ -398,8 +460,7 @@ export async function listRelationships(clientId: string, portId: string) {
if (!client || client.portId !== portId) throw new NotFoundError('Client'); if (!client || client.portId !== portId) throw new NotFoundError('Client');
return db.query.clientRelationships.findMany({ return db.query.clientRelationships.findMany({
where: (r, { or, eq }) => where: (r, { or, eq }) => or(eq(r.clientAId, clientId), eq(r.clientBId, clientId)),
or(eq(r.clientAId, clientId), eq(r.clientBId, clientId)),
}); });
} }
@@ -461,8 +522,7 @@ export async function deleteRelationship(
export async function findDuplicates(portId: string, fullName: string) { export async function findDuplicates(portId: string, fullName: string) {
return db.query.clients.findMany({ return db.query.clients.findMany({
where: (c, { and, eq }) => where: (c, { and, eq }) => and(eq(c.portId, portId), ilike(c.fullName, `%${fullName}%`)),
and(eq(c.portId, portId), ilike(c.fullName, `%${fullName}%`)),
limit: 5, limit: 5,
}); });
} }
@@ -473,10 +533,7 @@ export async function listClientOptions(portId: string, search?: string) {
const conditions = [eq(clients.portId, portId)]; const conditions = [eq(clients.portId, portId)];
if (search) { if (search) {
conditions.push( conditions.push(
or( or(ilike(clients.fullName, `%${search}%`), ilike(clients.companyName, `%${search}%`))!,
ilike(clients.fullName, `%${search}%`),
ilike(clients.companyName, `%${search}%`),
)!,
); );
} }