feat(eoi): toggleable local-fill pathway — clean detail render + address wrapping
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:
44
tests/unit/services/eoi-signature-layout.test.ts
Normal file
44
tests/unit/services/eoi-signature-layout.test.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { computeEoiSignatureLayout } from '@/lib/services/documenso-client';
|
||||
|
||||
// The EOI moves from the Documenso *template* pathway (Documenso fills the
|
||||
// AcroForm detail fields and auto-sizes/clips them) to the in-app pathway:
|
||||
// we fill + flatten the PDF locally, upload it as a Documenso *document*, then
|
||||
// place ONLY the page-3 signature fields. This layout must match template 8's
|
||||
// six fields exactly (client: Signature/Name/Place-of-Signing/Date; developer:
|
||||
// Name/Signature) so the signed EOI looks identical. Coords are percent of page.
|
||||
describe('computeEoiSignatureLayout', () => {
|
||||
const CLIENT = 101;
|
||||
const DEV = 102;
|
||||
const fields = computeEoiSignatureLayout(CLIENT, DEV);
|
||||
|
||||
it('produces exactly the 6 page-3 EOI signature fields', () => {
|
||||
expect(fields).toHaveLength(6);
|
||||
expect(fields.every((f) => f.pageNumber === 3)).toBe(true);
|
||||
});
|
||||
|
||||
it('maps client recipient to Signature + Name + Place-of-Signing + Date', () => {
|
||||
const client = fields.filter((f) => f.recipientId === CLIENT);
|
||||
expect(client.map((f) => f.type).sort()).toEqual(['DATE', 'NAME', 'SIGNATURE', 'TEXT']);
|
||||
});
|
||||
|
||||
it('maps developer recipient to Name + Signature only', () => {
|
||||
const dev = fields.filter((f) => f.recipientId === DEV);
|
||||
expect(dev.map((f) => f.type).sort()).toEqual(['NAME', 'SIGNATURE']);
|
||||
});
|
||||
|
||||
it('carries the Place-of-Signing label + required so the signer is prompted', () => {
|
||||
const place = fields.find((f) => f.recipientId === CLIENT && f.type === 'TEXT');
|
||||
expect(place?.fieldMeta?.label).toBe('Place of Signing');
|
||||
expect(place?.fieldMeta?.required).toBe(true);
|
||||
});
|
||||
|
||||
it('positions fields at template-8 coordinates (page-3 signature block)', () => {
|
||||
const sig = fields.find((f) => f.recipientId === CLIENT && f.type === 'SIGNATURE');
|
||||
expect(sig?.pageX).toBeCloseTo(39.645, 2);
|
||||
expect(sig?.pageY).toBeCloseTo(64.82, 1);
|
||||
const devSig = fields.find((f) => f.recipientId === DEV && f.type === 'SIGNATURE');
|
||||
expect(devSig?.pageY).toBeCloseTo(72.57, 1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user