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:
@@ -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 ────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user