feat(eoi): align prerequisites with EOI document structure
Match the gate to the actual EOI's structure (Section 2 vs Section 3) so
the rep can generate the document the moment they have what they need —
and not before.
Required (Section 2 — top paragraph):
- Client name
- Client primary email
- Client primary address
Optional (Section 3 — left blank when absent):
- Linked yacht (name, dimensions)
- Linked berth (mooring number)
Previously the dialog blocked generation unless yacht AND berth were both
linked, which was overzealous — early-stage EOIs are routinely sent before
a specific berth is pinned down.
- eoi-context.ts: yacht and berth are now nullable in the returned
context. The hard ValidationError is now driven by the EOI's Section
2 fields (name/email/address) rather than yacht/berth presence. The
owner block falls back to the interest's client when no yacht is
linked, so signing parties remain resolvable.
- documenso-payload.ts + fill-eoi-form.ts: Section 3 form values
render as empty strings when yacht or berth are absent, so the
rendered PDF leaves those template inputs blank.
- document-templates.ts: yacht.* and berth.* tokens fall back to
empty strings; the legacy-fallback catch handler also recognises
the new "missing required client details" error.
- interests.service.ts: getInterestById now also returns
`clientPrimaryEmail` and `clientHasAddress` so the Documents tab
can compute the EOI prerequisites checklist client-side without an
extra fetch.
- eoi-generate-dialog.tsx: prereqs split into two groups visually —
Required (with red ✗ when missing) and Optional (with grey – when
absent). The Generate button only requires the Required block to
pass. A small amber banner surfaces when Required is incomplete so
the rep knows where to add the missing data.
Tests: 835/835 pass. Replaces the obsolete "throws on missing yacht/
berth" tests with parity coverage for the new behaviour ("builds a
valid context when yacht/berth missing", "throws when client email/
address missing"). Adds a payload test for the empty-Section-3 case.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { and, eq, inArray, isNull, sql } from 'drizzle-orm';
|
||||
import { and, desc, eq, inArray, isNull, sql } from 'drizzle-orm';
|
||||
|
||||
import { db } from '@/lib/db';
|
||||
import { interests, interestTags } from '@/lib/db/schema/interests';
|
||||
import { clients } from '@/lib/db/schema/clients';
|
||||
import { clients, clientAddresses, clientContacts } from '@/lib/db/schema/clients';
|
||||
import { berths } from '@/lib/db/schema/berths';
|
||||
import { yachts } from '@/lib/db/schema/yachts';
|
||||
import { companyMemberships } from '@/lib/db/schema/companies';
|
||||
@@ -282,6 +282,24 @@ export async function getInterestById(id: string, portId: string) {
|
||||
.from(clients)
|
||||
.where(eq(clients.id, interest.clientId));
|
||||
|
||||
// EOI prerequisites: surface enough of the linked client's primary contact
|
||||
// and address so the Documents tab can show the readiness checklist
|
||||
// (Required: name, email, address — Section 2 of the EOI document).
|
||||
const [emailContact] = await db
|
||||
.select({ value: clientContacts.value })
|
||||
.from(clientContacts)
|
||||
.where(and(eq(clientContacts.clientId, interest.clientId), eq(clientContacts.channel, 'email')))
|
||||
.orderBy(desc(clientContacts.isPrimary), desc(clientContacts.updatedAt))
|
||||
.limit(1);
|
||||
|
||||
const [addressRow] = await db
|
||||
.select({ id: clientAddresses.id })
|
||||
.from(clientAddresses)
|
||||
.where(
|
||||
and(eq(clientAddresses.clientId, interest.clientId), eq(clientAddresses.isPrimary, true)),
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
let berthMooringNumber: string | null = null;
|
||||
if (interest.berthId) {
|
||||
const [berthRow] = await db
|
||||
@@ -300,6 +318,8 @@ export async function getInterestById(id: string, portId: string) {
|
||||
return {
|
||||
...interest,
|
||||
clientName: clientRow?.fullName ?? null,
|
||||
clientPrimaryEmail: emailContact?.value ?? null,
|
||||
clientHasAddress: !!addressRow,
|
||||
berthMooringNumber,
|
||||
tags: tagRows,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user