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 {
|
||||
clients,
|
||||
clientContacts,
|
||||
clientRelationships,
|
||||
clientTags,
|
||||
} from '@/lib/db/schema/clients';
|
||||
import { clients, clientContacts, clientRelationships, clientTags } from '@/lib/db/schema/clients';
|
||||
import { companies, companyMemberships } from '@/lib/db/schema/companies';
|
||||
import { yachts } from '@/lib/db/schema/yachts';
|
||||
import { berthReservations } from '@/lib/db/schema/reservations';
|
||||
import { tags } from '@/lib/db/schema/system';
|
||||
import { createAuditLog } from '@/lib/audit';
|
||||
import { NotFoundError } from '@/lib/errors';
|
||||
@@ -32,7 +30,7 @@ interface AuditMeta {
|
||||
// ─── List ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
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 = [];
|
||||
|
||||
@@ -42,9 +40,6 @@ export async function listClients(portId: string, query: ListClientsInput) {
|
||||
if (nationality) {
|
||||
filters.push(ilike(clients.nationality, `%${nationality}%`));
|
||||
}
|
||||
if (isProxy !== undefined) {
|
||||
filters.push(eq(clients.isProxy, isProxy));
|
||||
}
|
||||
if (tagIds && tagIds.length > 0) {
|
||||
const clientsWithTags = await db
|
||||
.selectDistinct({ clientId: clientTags.clientId })
|
||||
@@ -105,20 +100,76 @@ export async function getClientById(id: string, portId: string) {
|
||||
.innerJoin(tags, eq(clientTags.tagId, tags.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 {
|
||||
...client,
|
||||
contacts,
|
||||
tags: clientTagRows.map((r) => r.tag),
|
||||
yachts: yachtRows,
|
||||
companies: membershipRows,
|
||||
activeReservations,
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Create ───────────────────────────────────────────────────────────────────
|
||||
|
||||
export async function createClient(
|
||||
portId: string,
|
||||
data: CreateClientInput,
|
||||
meta: AuditMeta,
|
||||
) {
|
||||
export async function createClient(portId: string, data: CreateClientInput, meta: AuditMeta) {
|
||||
const result = await withTransaction(async (tx) => {
|
||||
const { contacts: contactsInput, tagIds, ...clientData } = data;
|
||||
|
||||
@@ -128,15 +179,13 @@ export async function createClient(
|
||||
.returning();
|
||||
|
||||
if (contactsInput.length > 0) {
|
||||
await tx.insert(clientContacts).values(
|
||||
contactsInput.map((c) => ({ clientId: client!.id, ...c })),
|
||||
);
|
||||
await tx
|
||||
.insert(clientContacts)
|
||||
.values(contactsInput.map((c) => ({ clientId: client!.id, ...c })));
|
||||
}
|
||||
|
||||
if (tagIds && tagIds.length > 0) {
|
||||
await tx.insert(clientTags).values(
|
||||
tagIds.map((tagId) => ({ clientId: client!.id, tagId })),
|
||||
);
|
||||
await tx.insert(clientTags).values(tagIds.map((tagId) => ({ clientId: client!.id, tagId })));
|
||||
}
|
||||
|
||||
return client!;
|
||||
@@ -153,7 +202,11 @@ export async function createClient(
|
||||
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 }) =>
|
||||
dispatchWebhookEvent(portId, 'client:created', { clientId: result.id }),
|
||||
@@ -198,7 +251,10 @@ export async function updateClient(
|
||||
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 }) =>
|
||||
dispatchWebhookEvent(portId, 'client:updated', { clientId: id }),
|
||||
@@ -311,7 +367,13 @@ export async function updateContact(
|
||||
contactId: string,
|
||||
clientId: 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,
|
||||
) {
|
||||
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');
|
||||
|
||||
return db.query.clientRelationships.findMany({
|
||||
where: (r, { or, eq }) =>
|
||||
or(eq(r.clientAId, clientId), eq(r.clientBId, clientId)),
|
||||
where: (r, { or, eq }) => 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) {
|
||||
return db.query.clients.findMany({
|
||||
where: (c, { and, eq }) =>
|
||||
and(eq(c.portId, portId), ilike(c.fullName, `%${fullName}%`)),
|
||||
where: (c, { and, eq }) => and(eq(c.portId, portId), ilike(c.fullName, `%${fullName}%`)),
|
||||
limit: 5,
|
||||
});
|
||||
}
|
||||
@@ -473,10 +533,7 @@ export async function listClientOptions(portId: string, search?: string) {
|
||||
const conditions = [eq(clients.portId, portId)];
|
||||
if (search) {
|
||||
conditions.push(
|
||||
or(
|
||||
ilike(clients.fullName, `%${search}%`),
|
||||
ilike(clients.companyName, `%${search}%`),
|
||||
)!,
|
||||
or(ilike(clients.fullName, `%${search}%`), ilike(clients.companyName, `%${search}%`))!,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user