feat(eoi-generate): Include-yacht toggle to omit Section 3 when yacht is a placeholder
EoiGenerateDialog gains an inline "Include on EOI" checkbox in the Section 3 header (renders only when ctx.yacht is set; defaults ON so existing behaviour is unchanged). When OFF, the generate-and-sign POST flips includeYachtDetails=false on the body; service blanks eoiContext.yacht before either pathway runs: - Documenso template payload: buildDocumensoPayload reads no yacht so yacht.* and owner.* merge fields ship empty. Existing template tolerates blanks per the "left blank if absent" copy. - In-app PDF fill (pdf-lib): generateEoiPdfFromTemplate sees no yacht so AcroForm field writes for the yacht block are skipped. Persists the rep's choice in the document-create audit log (metadata.includeYachtDetails) so an audit trail records explicit opt-outs even though documents has no JSONB metadata column today. ft/m unit toggle in the Section 3 header now hides when Include is OFF (unit choice is meaningless without yacht details). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -490,7 +490,7 @@ async function generateEoiFromSourcePdf(
|
||||
portId: string,
|
||||
context: GenerateInput,
|
||||
meta: AuditMeta,
|
||||
options?: { dimensionUnit?: 'ft' | 'm' },
|
||||
options?: { dimensionUnit?: 'ft' | 'm'; includeYachtDetails?: boolean },
|
||||
applied: AppliedOverrides = { resolved: {}, documentOverrideColumns: {} },
|
||||
): Promise<{ document: DbDocument; file: DbFile }> {
|
||||
if (!context.interestId) {
|
||||
@@ -501,6 +501,12 @@ async function generateEoiFromSourcePdf(
|
||||
await buildEoiContext(context.interestId, portId),
|
||||
applied,
|
||||
);
|
||||
// Rep opted out of Section 3 — blank the yacht slot so the AcroForm fill
|
||||
// skips writing the yacht.* / owner.* fields (matching the Documenso
|
||||
// pathway).
|
||||
if (options?.includeYachtDetails === false) {
|
||||
eoiContext.yacht = null;
|
||||
}
|
||||
const pdfBytes = await generateEoiPdfFromTemplate(eoiContext, {
|
||||
dimensionUnit: options?.dimensionUnit ?? eoiContext.yacht?.lengthUnit ?? 'ft',
|
||||
});
|
||||
@@ -597,7 +603,14 @@ export async function generateAndSign(
|
||||
signers: GenerateAndSignInput['signers'],
|
||||
pathway: 'inapp' | 'documenso-template',
|
||||
meta: AuditMeta,
|
||||
options?: { dimensionUnit?: 'ft' | 'm'; overrides?: EoiOverridesInput },
|
||||
options?: {
|
||||
dimensionUnit?: 'ft' | 'm';
|
||||
overrides?: EoiOverridesInput;
|
||||
/** False = blank out Section 3 (yacht.* + owner.* merge fields) even
|
||||
* when the interest carries a linked yacht. True (or unset) keeps the
|
||||
* current behaviour (auto-fill from yacht record). */
|
||||
includeYachtDetails?: boolean;
|
||||
},
|
||||
) {
|
||||
// Phase 3b - apply per-field overrides BEFORE either pathway resolves the
|
||||
// EOI context, so any setAsDefault contact promotion is visible to the
|
||||
@@ -623,7 +636,7 @@ async function generateAndSignViaInApp(
|
||||
context: GenerateInput,
|
||||
signers: GenerateAndSignInput['signers'],
|
||||
meta: AuditMeta,
|
||||
options?: { dimensionUnit?: 'ft' | 'm' },
|
||||
options?: { dimensionUnit?: 'ft' | 'm'; includeYachtDetails?: boolean },
|
||||
applied: AppliedOverrides = { resolved: {}, documentOverrideColumns: {} },
|
||||
) {
|
||||
const template = await getTemplateById(templateId, portId);
|
||||
@@ -755,7 +768,7 @@ async function generateAndSignViaDocumensoTemplate(
|
||||
portId: string,
|
||||
context: GenerateInput,
|
||||
meta: AuditMeta,
|
||||
options?: { dimensionUnit?: 'ft' | 'm' },
|
||||
options?: { dimensionUnit?: 'ft' | 'm'; includeYachtDetails?: boolean },
|
||||
applied: AppliedOverrides = { resolved: {}, documentOverrideColumns: {} },
|
||||
) {
|
||||
if (!context.interestId) {
|
||||
@@ -766,6 +779,14 @@ async function generateAndSignViaDocumensoTemplate(
|
||||
await buildEoiContext(context.interestId, portId),
|
||||
applied,
|
||||
);
|
||||
// Rep opted out of Section 3 (yacht details) — blank the yacht slot so
|
||||
// buildDocumensoPayload + the EOI template see "no yacht linked" and
|
||||
// leave yacht.* / owner.* merge fields empty. Persisted in document
|
||||
// metadata below for audit (kind: 'eoi_include_yacht_details=false').
|
||||
const yachtDeclined = options?.includeYachtDetails === false;
|
||||
if (yachtDeclined) {
|
||||
eoiContext.yacht = null;
|
||||
}
|
||||
const signers = await getPortEoiSigners(portId);
|
||||
// Per-port Documenso template + recipient IDs (with env fallback). Each
|
||||
// tenant pointing at its own Documenso instance has different numeric
|
||||
@@ -908,6 +929,10 @@ async function generateAndSignViaDocumensoTemplate(
|
||||
pathway: 'documenso-template',
|
||||
templateId: env.DOCUMENSO_TEMPLATE_ID_EOI,
|
||||
interestId: context.interestId,
|
||||
// Rep's explicit Section-3 choice. Audit-only — Docs row has no
|
||||
// metadata jsonb; the blanked yacht.* / owner.* merge fields on the
|
||||
// generated PDF are the user-visible evidence.
|
||||
includeYachtDetails: !yachtDeclined,
|
||||
},
|
||||
ipAddress: meta.ipAddress,
|
||||
userAgent: meta.userAgent,
|
||||
|
||||
@@ -109,6 +109,14 @@ export const generateAndSignSchema = generateSchema.extend({
|
||||
* EOI's Length/Width/Draft formValues. The drawer's toggle drives this;
|
||||
* server defaults to the yacht's `lengthUnit` column when omitted. */
|
||||
dimensionUnit: z.enum(['ft', 'm']).optional(),
|
||||
/** Optional Section 3 (yacht details) inclusion. Defaults to true when a
|
||||
* yacht is linked. Rep can flip OFF in the dialog to blank out yacht.*
|
||||
* + owner.* merge fields even though a yacht is on file — used when the
|
||||
* yacht is a placeholder, multi-berth deals where yacht-specific dims
|
||||
* don't apply, or the client explicitly asked for the section to stay
|
||||
* off the document. Persisted in `documents.metadata.includeYachtDetails`
|
||||
* for audit. */
|
||||
includeYachtDetails: z.boolean().optional(),
|
||||
/** Phase 3b/3-follow-up - optional per-field overrides applied at generation. */
|
||||
overrides: z
|
||||
.object({
|
||||
|
||||
Reference in New Issue
Block a user