feat(sales): admin-configurable EOI signers + richer timeline events
1. Per-port EOI signer config
- New `eoi_signers` system_settings key (JSON: { developer, approver },
each `{ name, email }`). Settings UI exposes it under Admin → Settings.
- getPortEoiSigners(portId) reads the setting with a typed validator;
falls back to the legacy David Mizrahi / Abbie May defaults if the
row is missing or malformed (so older ports keep working until an
admin saves a value).
- Both EOI generation pathways now read from the helper instead of
hardcoded constants:
* documenso-template path (generateAndSignViaDocumensoTemplate)
* in-app PDF-fill path (generateAndSignViaInApp)
2. Timeline upgrades
The interest detail Activity tab now distinguishes the new automation
events that arrived with sessions 1+2:
- Stage auto-advances (userId='system') get a small "Auto" pill and
carry their reason into the description (e.g. "Stage advanced to
EOI Signed (auto-advanced — EOI signed via Documenso)").
- outcome_set events show "Marked as Won" / "Marked as Lost — went
to another marina" with optional reason; trophy/X icons.
- outcome_cleared events show "Reopened to {stage}" with a refresh
icon.
- Document events humanized: "Document 'X' fully signed" instead
of "Document X: completed".
- Stage labels run through stageLabel() so the timeline shows the
human label, not the enum key.
- Timestamps switched to relative-time with full-date tooltip.
- "by system" is rendered plainly (no longer the literal user-id).
tsc clean. vitest 832/832 pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,7 +23,7 @@ import {
|
||||
sendDocument as documensoSend,
|
||||
generateDocumentFromTemplate as documensoGenerateFromTemplate,
|
||||
} from '@/lib/services/documenso-client';
|
||||
import { buildDocumensoPayload } from '@/lib/services/documenso-payload';
|
||||
import { buildDocumensoPayload, getPortEoiSigners } from '@/lib/services/documenso-payload';
|
||||
import { generateEoiPdfFromTemplate } from '@/lib/pdf/fill-eoi-form';
|
||||
import { MERGE_FIELDS, type MergeFieldCatalog } from '@/lib/templates/merge-fields';
|
||||
import { buildEoiContext } from '@/lib/services/eoi-context';
|
||||
@@ -766,6 +766,7 @@ async function generateAndSignViaInApp(
|
||||
);
|
||||
}
|
||||
const eoiCtx = await buildEoiContext(context.interestId, portId);
|
||||
const signers = await getPortEoiSigners(portId);
|
||||
resolvedSigners = [
|
||||
{
|
||||
name: eoiCtx.client.fullName,
|
||||
@@ -773,8 +774,18 @@ async function generateAndSignViaInApp(
|
||||
role: 'signer',
|
||||
signingOrder: 1,
|
||||
},
|
||||
{ name: 'David Mizrahi', email: 'dm@portnimara.com', role: 'signer', signingOrder: 2 },
|
||||
{ name: 'Abbie May', email: 'sales@portnimara.com', role: 'approver', signingOrder: 3 },
|
||||
{
|
||||
name: signers.developer.name,
|
||||
email: signers.developer.email,
|
||||
role: 'signer',
|
||||
signingOrder: 2,
|
||||
},
|
||||
{
|
||||
name: signers.approver.name,
|
||||
email: signers.approver.email,
|
||||
role: 'approver',
|
||||
signingOrder: 3,
|
||||
},
|
||||
];
|
||||
}
|
||||
if (!resolvedSigners || resolvedSigners.length === 0) {
|
||||
@@ -859,12 +870,17 @@ async function generateAndSignViaDocumensoTemplate(
|
||||
}
|
||||
|
||||
const eoiContext = await buildEoiContext(context.interestId, portId);
|
||||
const signers = await getPortEoiSigners(portId);
|
||||
|
||||
const payload = buildDocumensoPayload(eoiContext, {
|
||||
interestId: context.interestId,
|
||||
clientRecipientId: env.DOCUMENSO_CLIENT_RECIPIENT_ID,
|
||||
developerRecipientId: env.DOCUMENSO_DEVELOPER_RECIPIENT_ID,
|
||||
approvalRecipientId: env.DOCUMENSO_APPROVAL_RECIPIENT_ID,
|
||||
developerName: signers.developer.name,
|
||||
developerEmail: signers.developer.email,
|
||||
approverName: signers.approver.name,
|
||||
approverEmail: signers.approver.email,
|
||||
redirectUrl: env.APP_URL,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user