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:
@@ -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}%`),
|
|
||||||
)!,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user