# 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

The legacy template (Documenso template `8`, configured in production) auto-fills exactly the fields below. All eight text fields + two booleans are populated by `buildDocumensoPayload()` from the resolved `EoiContext`. Anything else on the form (signature, date, terms acknowledgment) is filled in by the client inside Documenso.

| 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`. Empty string when no yacht is linked yet.                                                                                                                                                                                                                                                                                                                                                       |
| `Length`       | text    | `interest['Length']`        | `context.yacht.lengthFt`                              | Boat dimension. Send as string. Documenso doesn't enforce numeric format. Empty string when not applicable.                                                                                                                                                                                                                                                                                                                                                    |
| `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`                         | The interest's PRIMARY berth (resolved via `interest_berths.is_primary=true`). Empty string when no primary set.                                                                                                                                                                                                                                                                                                                                               |
| `Berth Range`  | text    | (new)                       | `context.eoiBerthRange`                               | **NEW IN PHASE 5** — compact range string for multi-berth EOIs (e.g. `"A1-A3, B5-B7"`) covering every junction row marked `is_in_eoi_bundle=true`. Empty string when the bundle is empty. **The live Documenso template (id `8`) does NOT yet have this field. Add a `Berth Range` text field to the template before multi-berth EOIs render the range; until then Documenso silently drops the value and only `Berth Number` (the primary mooring) renders.** |
| `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.                                                                                                                                                                                                                                                                                                                                                                                                                  |

**Backwards-compatibility guarantee**: every legacy `formValues` key is still emitted with the same name and type. The only addition is `Berth Range` (Phase 5). Documenso silently ignores unknown formValues keys, so old templates that don't have `Berth Range` will simply not render it — single-berth EOIs continue to work identically. No template changes are required for legacy use.

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