docs(eoi): document Documenso template field name mapping

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-04-24 16:06:39 +02:00
parent d133d6d656
commit db74c9394b

View File

@@ -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. |