feat(documenso-phase-7): Project Director RBAC binding

Admin UI binding for the developer + approver user-id fields that
Phase 1 schema'd but left unwired. Surfaces four new fields in the
Documenso settings card so admins can:

  - Set per-port display labels for the developer/approver slots
    (documenso_developer_label / approver_label) — drives email
    subjects + signer-progress UI copy. Defaults to "Developer" /
    "Approver" when blank.
  - Link each slot to a CRM user (documenso_developer_user_id /
    approver_user_id) — UUID from /admin/users.

Webhook side-effect:
- handleRecipientSigned's cascade now fires an in-CRM notification
  for the next pending signer when their signerRole matches a
  configured developer_user_id / approver_user_id. The branded
  email is the primary channel; the notification is a defense-in-
  depth nudge for users who live in the CRM all day.
- New notification type `document_signing_your_turn` with dedupeKey
  `document:<id>:your-turn:<signerId>` so duplicate webhook
  deliveries don't re-notify.
- Falls back silently when the binding isn't set or the signer
  isn't a developer/approver — preserves the existing flow.

Out of scope (build plan flags as out-of-scope for v1):
- Auto-fill name/email when a user is selected: needs a typeahead
  field type the SettingsFormCard doesn't have yet. Admin reads the
  user's UUID from /admin/users and pastes; minor friction for a
  one-time per-port config.
- Webhook handler reading the linked user's email and matching
  against the inbound recipient: today the developer/approver email
  settings already drive the matching; the user-id is purely a
  notification target.

Tests: 1340/1340 ; tsc clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-13 14:08:52 +02:00
parent 7bf587de90
commit b1dfec09a0
2 changed files with 70 additions and 0 deletions

View File

@@ -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 ────────────────────────────────────────────────────