diff --git a/src/app/(dashboard)/[portSlug]/admin/documenso/page.tsx b/src/app/(dashboard)/[portSlug]/admin/documenso/page.tsx index 7929134b..f50d0dca 100644 --- a/src/app/(dashboard)/[portSlug]/admin/documenso/page.tsx +++ b/src/app/(dashboard)/[portSlug]/admin/documenso/page.tsx @@ -57,6 +57,24 @@ const SIGNER_FIELDS: SettingFieldDef[] = [ placeholder: 'dm@portnimara.com', defaultValue: '', }, + { + key: 'documenso_developer_label', + label: 'Developer signer — display label', + description: + 'How the developer slot is referenced in email subjects + signer-progress UI copy. Defaults to "Developer" when blank.', + type: 'string', + placeholder: 'Developer', + defaultValue: '', + }, + { + key: 'documenso_developer_user_id', + label: 'Developer signer — linked CRM user (optional)', + description: + "Project Director RBAC binding. When set, the webhook handler fires an in-CRM notification for this user when it's their turn to sign — alongside the branded email. Leave blank if the developer slot doesn't map to a CRM user (e.g. external developer). Use the user's UUID from /admin/users.", + type: 'string', + placeholder: '00000000-0000-0000-0000-000000000000', + defaultValue: '', + }, { key: 'documenso_approver_name', label: 'Approver — name', @@ -74,6 +92,24 @@ const SIGNER_FIELDS: SettingFieldDef[] = [ placeholder: 'sales@portnimara.com', defaultValue: '', }, + { + key: 'documenso_approver_label', + label: 'Approver — display label', + description: + 'How the approver slot is referenced in email subjects + signer-progress UI copy. Defaults to "Approver" when blank.', + type: 'string', + placeholder: 'Approver', + defaultValue: '', + }, + { + key: 'documenso_approver_user_id', + label: 'Approver — linked CRM user (optional)', + description: + "Same as developer's linked user — when set, fires an in-CRM notification when it's the approver's turn. Use the user's UUID from /admin/users.", + type: 'string', + placeholder: '00000000-0000-0000-0000-000000000000', + defaultValue: '', + }, ]; const EOI_FIELDS: SettingFieldDef[] = [ diff --git a/src/lib/services/documents.service.ts b/src/lib/services/documents.service.ts index 9b02c6e5..c6b0fee8 100644 --- a/src/lib/services/documents.service.ts +++ b/src/lib/services/documents.service.ts @@ -1151,6 +1151,40 @@ async function sendCascadingInviteForNextSigner(doc: { .update(documentSigners) .set({ invitedAt: new Date() }) .where(eq(documentSigners.id, next.id)); + + // Phase 7 — Project Director RBAC binding: when the per-port settings + // map the developer / approver slot to a CRM user (developerUserId / + // approverUserId), fire an in-CRM notification so the user sees their + // pending signing turn alongside the branded email. The email is the + // primary channel; the notification is a defense-in-depth nudge for + // users who live in the CRM all day. Falls back silently when the + // settings aren't wired or the signer role doesn't match. + const linkedUserId = + next.signerRole === 'developer' + ? (docCfg.developerUserId ?? null) + : next.signerRole === 'approver' + ? (docCfg.approverUserId ?? null) + : null; + if (linkedUserId) { + void import('@/lib/services/notifications.service').then(({ createNotification }) => + createNotification({ + portId: doc.portId, + userId: linkedUserId, + type: 'document_signing_your_turn', + title: 'Your signature is needed', + description: `"${doc.title}" is waiting for you to sign.`, + link: `/documents/${doc.id}`, + entityType: 'document', + entityId: doc.id, + dedupeKey: `document:${doc.id}:your-turn:${next.id}`, + }).catch((err) => { + logger.warn( + { err, documentId: doc.id, signerId: next.id, linkedUserId }, + 'phase-7 in-CRM your-turn notification failed (email still sent)', + ); + }), + ); + } } // ─── Owner-wins resolution ────────────────────────────────────────────────────