diff --git a/docs/eoi-documenso-field-mapping.md b/docs/eoi-documenso-field-mapping.md new file mode 100644 index 0000000..02c5ce7 --- /dev/null +++ b/docs/eoi-documenso-field-mapping.md @@ -0,0 +1,76 @@ +# Documenso EOI Template — Field Mapping + +**Purpose:** This doc is the canonical reference for mapping the Documenso EOI template's `formValues` keys to the new data model's `EoiContext` shape. It drives `buildDocumensoPayload()` (Task 11.2), the in-app Standard EOI HTML tokens (Task 11.3), and the Spec 2 importer's yacht/company hydration. + +## Source + +The legacy field list comes from `client-portal/server/api/eoi/generate-quick-eoi.ts`, specifically the POST body sent to `POST /api/v1/templates/{templateId}/generate-document` (Documenso template 8). The relevant lines in that file are around the `createDocumentPayload.formValues` object. + +## Documenso template `formValues` keys + +Documenso template IDs and recipient IDs are configured via env vars: + +- `NUXT_DOCUMENSO_TEMPLATE_ID` (default: `8`) +- `NUXT_DOCUMENSO_CLIENT_RECIPIENT_ID` (default: `192`) — signing order 1 +- `NUXT_DOCUMENSO_DEVELOPER_RECIPIENT_ID` (default: `193`) — signing order 2 +- `NUXT_DOCUMENSO_APPROVAL_RECIPIENT_ID` (default: `194`) — APPROVER, signing order 3 + +The template exposes eight text fields (`formValues` keys) and two boolean checkboxes. + +## Field mapping + +| Documenso key | Type | Legacy source | New `EoiContext` path | Notes | +| -------------- | ------- | --------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------- | +| `Name` | text | `interest['Full Name']` | `context.client.fullName` | The interest's point-of-contact client (billing signer). | +| `Email` | text | `interest['Email Address']` | `context.client.primaryEmail` | Primary email contact from `client_contacts`. | +| `Address` | text | `interest['Address']` | concat `context.client.address.{street,city,country}` | Concatenate street, city, country with `', '`. Empty if address is null. | +| `Yacht Name` | text | `interest['Yacht Name']` | `context.yacht.name` | Yacht is now a first-class row; pulled via `interest.yachtId`. | +| `Length` | text | `interest['Length']` | `context.yacht.lengthFt` | Send as string. Documenso doesn't enforce numeric format. | +| `Width` | text | `interest['Width']` | `context.yacht.widthFt` | Same. | +| `Draft` | text | `interest['Depth']` | `context.yacht.draftFt` | Legacy field was named "Depth" in NocoDB; Documenso key is "Draft". | +| `Berth Number` | text | `berthNumbers` (joined) | `context.berth.mooringNumber` | One berth per reservation. Multi-berth case was multi-interest in legacy. | +| `Lease_10` | boolean | hardcoded `false` | `false` | Hardcoded — legacy flow defaults to Purchase (not Lease). | +| `Purchase` | boolean | hardcoded `true` | `true` | Hardcoded — legacy flow defaults to Purchase. | + +## Document `meta` fields (non-`formValues`) + +| Documenso key | Type | Legacy source | New source | +| ------------------------- | ---- | ---------------------------------------- | ----------------------------------------------------------------- | +| `meta.message` | text | `Dear ${interest['Full Name']}...` | `Dear ${context.client.fullName}, ...port name interpolated` | +| `meta.subject` | text | `"Your LOI is ready to be signed"` | Same — constant. | +| `meta.redirectUrl` | text | `"https://portnimara.com"` | `context.port.redirectUrl` if per-port; otherwise global app URL. | +| `meta.distributionMethod` | text | `"NONE"` | Same — constant. We use manual send flow (Documenso webhook). | +| `title` | text | `` `${interest['Full Name']}-EOI-NDA` `` | `` `${context.client.fullName}-EOI-NDA` `` | +| `externalId` | text | `` `loi-${interestId}` `` | Same. | + +## Recipients (non-`formValues`) + +| Recipient | Role | Name | Email | Signing order | +| ------------------- | -------- | ------------------------- | ----------------------------- | ------------- | +| Client (signer) | SIGNER | `context.client.fullName` | `context.client.primaryEmail` | 1 | +| Developer (signer) | SIGNER | `"David Mizrahi"` | `"dm@portnimara.com"` | 2 | +| Approval (approver) | APPROVER | `"Abbie May"` | `"sales@portnimara.com"` | 3 | + +The Developer and Approval recipients are currently hardcoded in the legacy flow. In the new system these should eventually come from port-level settings (e.g., `ports.settings.eoi.developerName` + email). For Task 11.2, keep them hardcoded as the legacy system does — tracking as TODO: "Replace hardcoded Developer/Approval recipients with port-level configuration." + +## Company-owned yacht handling + +The legacy flow has no concept of company ownership — the signer is always the interest's client. In the new system: + +- If `context.yacht.ownerType === 'client'`: behavior unchanged. +- If `context.yacht.ownerType === 'company'`: the interest's point-of-contact client still signs (they're the representative of the yacht's owning company), but an extra block should appear in the message body: `"On behalf of ${context.company.legalName ?? context.company.name} (representing the yacht's owner)."`. This isn't a separate Documenso field — it's woven into `meta.message`. + +Tracking this in the mapping doc rather than as a hard TODO because company-owned EOIs were rare in the legacy system and need product input before committing to the final wording. + +## Deprecated fields (no longer sourced from `clients`) + +The legacy system read these fields from the client row. They are now sourced elsewhere: + +| Legacy source | New source | +| ------------------------- | --------------------------------------------------- | +| `client.yachtName` | `yachts.name` via `interest.yachtId` | +| `client.yachtLengthFt` | `yachts.lengthFt` via `interest.yachtId` | +| `client.yachtWidthFt` | `yachts.widthFt` via `interest.yachtId` | +| `client.yachtDraftFt` | `yachts.draftFt` via `interest.yachtId` | +| `client.companyName` | `companies.name` via polymorphic owner resolution | +| `client.berthSizeDesired` | Removed. Berth is picked via reservation, not text. |