feat(eoi): toggleable local-fill pathway — clean detail render + address wrapping
All checks were successful
Build & Push Docker Images / lint (push) Successful in 2m48s
Build & Push Docker Images / build-and-push (push) Successful in 8m34s

EOI detail fields (address, name, yacht, berth) rendered oversized and
top-clipped because Documenso auto-sizes AcroForm text when *it* fills the
template (ignores the PDF's 12pt font; a taller box → bigger font → more clip,
and a 2-line address box renders huge). Proven: filling the same source PDF
locally at 12pt renders cleanly and wraps long addresses to a 2nd line.

Add a per-port `eoi_fill_method` setting (default `local`), toggleable in
admin → Documenso → Templates & signing pathway:
- local:     CRM fills + flattens the source PDF (pdf-lib, fixed 12pt +
             multiline address wrap), uploads the flattened PDF to Documenso,
             and places ONLY the 6 page-3 signature fields. Documenso never
             re-renders the body text → no clipping.
- documenso: legacy template AcroForm fill (auto-sizes/clips) — fallback only.

Both still flow through Documenso for signing, so branded invites, embedded
signing, webhooks, signer rows, and the EOI milestone are unchanged.

- computeEoiSignatureLayout(): 6 page-3 fields at template-8 coords (unit-tested)
- createDocument (v1): PUT bytes to Documenso's presigned uploadUrl (2.x v1-compat
  ignores the base64 field) so the uploaded document actually has content
- placeFields (v1): pass fieldMeta through so the Place-of-Signing TEXT field
  keeps its label/required

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-25 02:48:11 +02:00
parent af05bb18dc
commit 0ca9b2c3b5
6 changed files with 431 additions and 40 deletions

View File

@@ -49,6 +49,11 @@ export const SETTING_KEYS = {
// timing-safe comparison.
documensoWebhookSecret: 'documenso_webhook_secret',
eoiDefaultPathway: 'eoi_default_pathway',
// EOI body-text fill method: 'local' (CRM fills + flattens the PDF, clean
// 12pt + multiline address wrap, Documenso signs only) vs 'documenso'
// (legacy: Documenso fills the template AcroForm fields and auto-sizes /
// clips them). Toggleable per-port in admin → Documenso.
eoiFillMethod: 'eoi_fill_method',
// Identity of the developer + approver that the template's static
// recipient slots get filled with. Old system hardcoded these
// (David Mizrahi, Abbie May @ portnimara.com) but multi-port deploys
@@ -316,6 +321,16 @@ export interface PortDocumensoConfig {
apiUrlSource: 'port' | 'global' | 'env' | 'default' | 'none';
eoiTemplateId: number;
defaultPathway: EoiPathway;
/**
* EOI body-text fill method:
* - 'local' : CRM fills + flattens the source PDF (pdf-lib, fixed 12pt +
* multiline address wrapping), then uploads the flattened PDF
* to Documenso for signature placement only. Renders cleanly.
* - 'documenso': legacy — Documenso fills the template's AcroForm fields via
* the template-generate API (auto-sizes the text → clips it).
* Toggleable per-port in admin → Documenso. Defaults to 'local'.
*/
eoiFillMethod: 'local' | 'documenso';
/** Documenso template recipient slot IDs (per-instance numeric). */
clientRecipientId: number;
developerRecipientId: number;
@@ -387,6 +402,7 @@ export async function getPortDocumensoConfig(portId: string): Promise<PortDocume
developerRecipientId,
approvalRecipientId,
defaultPathway,
eoiFillMethod,
developerName,
developerEmail,
approverName,
@@ -411,6 +427,7 @@ export async function getPortDocumensoConfig(portId: string): Promise<PortDocume
readSetting<string | number>(SETTING_KEYS.documensoDeveloperRecipientId, portId),
readSetting<string | number>(SETTING_KEYS.documensoApprovalRecipientId, portId),
readSetting<EoiPathway>(SETTING_KEYS.eoiDefaultPathway, portId),
readSetting<'local' | 'documenso'>(SETTING_KEYS.eoiFillMethod, portId),
readSetting<string>(SETTING_KEYS.documensoDeveloperName, portId),
readSetting<string>(SETTING_KEYS.documensoDeveloperEmail, portId),
readSetting<string>(SETTING_KEYS.documensoApproverName, portId),
@@ -464,6 +481,9 @@ export async function getPortDocumensoConfig(portId: string): Promise<PortDocume
approvalRecipientId:
toIntOrNull(approvalRecipientId) ?? env.DOCUMENSO_APPROVAL_RECIPIENT_ID ?? 0,
defaultPathway: defaultPathway ?? 'documenso-template',
// Default to the local-fill method (clean render + address wrapping). Set
// to 'documenso' per-port to fall back to Documenso's template AcroForm fill.
eoiFillMethod: eoiFillMethod === 'documenso' ? 'documenso' : 'local',
developerName: developerName ?? '',
developerEmail: developerEmail ?? '',
approverName: approverName ?? '',