feat: activate whole signing group on sequential advance

This commit is contained in:
David Nguyen
2026-08-04 17:30:46 +10:00
parent bbe03bb83d
commit e8484c4405
@@ -25,6 +25,7 @@ import { mapEnvelopeToWebhookDocumentPayload, ZWebhookDocumentSchema } from '../
import { extractDocumentAuthMethods } from '../../utils/document-auth'; import { extractDocumentAuthMethods } from '../../utils/document-auth';
import type { EnvelopeIdOptions } from '../../utils/envelope'; import type { EnvelopeIdOptions } from '../../utils/envelope';
import { mapSecondaryIdToDocumentId, unsafeBuildEnvelopeIdQuery } from '../../utils/envelope'; import { mapSecondaryIdToDocumentId, unsafeBuildEnvelopeIdQuery } from '../../utils/envelope';
import { filterRecipientsInFirstSigningGroup } from '../../utils/recipient-groups';
import { assertRecipientNotExpired } from '../../utils/recipients'; import { assertRecipientNotExpired } from '../../utils/recipients';
import { getIsRecipientsTurnToSign } from '../recipient/get-is-recipient-turn'; import { getIsRecipientsTurnToSign } from '../recipient/get-is-recipient-turn';
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook'; import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
@@ -372,6 +373,7 @@ export const completeDocumentWithToken = async ({
name: true, name: true,
email: true, email: true,
role: true, role: true,
sendStatus: true,
}, },
where: { where: {
envelopeId: envelope.id, envelopeId: envelope.id,
@@ -397,10 +399,25 @@ export const completeDocumentWithToken = async ({
}); });
if (envelope.documentMeta?.signingOrder === DocumentSigningOrder.SEQUENTIAL) { if (envelope.documentMeta?.signingOrder === DocumentSigningOrder.SEQUENTIAL) {
const [nextRecipient] = pendingRecipients; // The active group: every pending recipient sharing the lowest pending
// signing order. Members already activated (sendStatus SENT) are group
// peers who were notified earlier — activating only fresh members means
// a mid-group completion is a no-op and a step transition activates the
// whole next group at once.
const nextGroup = filterRecipientsInFirstSigningGroup(pendingRecipients);
const recipientsToActivate = nextGroup.filter((r) => r.sendStatus !== SendStatus.SENT);
// Dictation only applies when advancing to a single-recipient step.
const canDictateNextSigner =
Boolean(nextSigner) &&
Boolean(envelope.documentMeta?.allowDictateNextSigner) &&
nextGroup.length === 1 &&
recipientsToActivate.length === 1;
await prisma.$transaction(async (tx) => { await prisma.$transaction(async (tx) => {
if (nextSigner && envelope.documentMeta?.allowDictateNextSigner) { if (canDictateNextSigner && nextSigner) {
const [nextRecipient] = recipientsToActivate;
await tx.documentAuditLog.create({ await tx.documentAuditLog.create({
data: createDocumentAuditLogData({ data: createDocumentAuditLogData({
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_UPDATED, type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_UPDATED,
@@ -432,30 +449,34 @@ export const completeDocumentWithToken = async ({
}); });
} }
await tx.recipient.update({ for (const nextRecipient of recipientsToActivate) {
where: { id: nextRecipient.id }, await tx.recipient.update({
data: { where: { id: nextRecipient.id },
sendStatus: SendStatus.SENT, data: {
sentAt: new Date(), sendStatus: SendStatus.SENT,
...(nextSigner && envelope.documentMeta?.allowDictateNextSigner sentAt: new Date(),
? { ...(canDictateNextSigner && nextSigner
name: nextSigner.name, ? {
email: nextSigner.email, name: nextSigner.name,
} email: nextSigner.email,
: {}), }
}, : {}),
}); },
});
}
}); });
await jobs.triggerJob({ for (const nextRecipient of recipientsToActivate) {
name: 'send.signing.requested.email', await jobs.triggerJob({
payload: { name: 'send.signing.requested.email',
userId: envelope.userId, payload: {
documentId: legacyDocumentId, userId: envelope.userId,
recipientId: nextRecipient.id, documentId: legacyDocumentId,
requestMetadata, recipientId: nextRecipient.id,
}, requestMetadata,
}); },
});
}
} }
} }