fix: advance signing groups by step completion instead of send status

This commit is contained in:
David Nguyen
2026-08-04 18:43:49 +10:00
parent 83eb53fffb
commit 635332da2c
@@ -373,7 +373,6 @@ 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,
@@ -399,83 +398,86 @@ export const completeDocumentWithToken = async ({
}); });
if (envelope.documentMeta?.signingOrder === DocumentSigningOrder.SEQUENTIAL) { if (envelope.documentMeta?.signingOrder === DocumentSigningOrder.SEQUENTIAL) {
// The active group: every pending recipient sharing the lowest pending // The next group: every pending recipient sharing the lowest pending
// signing order. Members already activated (sendStatus SENT) are group // signing order. If the completing recipient's own step is still
// peers who were notified earlier — activating only fresh members means // pending (a group peer has not signed yet), the flow does not advance —
// a mid-group completion is a no-op and a step transition activates the // the remaining peers were already activated when their step unlocked.
// whole next group at once.
const nextGroup = filterRecipientsInFirstSigningGroup(pendingRecipients); const nextGroup = filterRecipientsInFirstSigningGroup(pendingRecipients);
const recipientsToActivate = nextGroup.filter((r) => r.sendStatus !== SendStatus.SENT);
// Dictation only applies when advancing to a single-recipient step. const currentRecipientOrder = recipient.signingOrder ?? Number.MAX_SAFE_INTEGER;
const canDictateNextSigner =
Boolean(nextSigner) &&
Boolean(envelope.documentMeta?.allowDictateNextSigner) &&
nextGroup.length === 1 &&
recipientsToActivate.length === 1;
await prisma.$transaction(async (tx) => { const hasCompletedCurrentStep = nextGroup.every(
if (canDictateNextSigner && nextSigner) { (pendingRecipient) => (pendingRecipient.signingOrder ?? Number.MAX_SAFE_INTEGER) > currentRecipientOrder,
const [nextRecipient] = recipientsToActivate; );
await tx.documentAuditLog.create({ if (nextGroup.length > 0 && hasCompletedCurrentStep) {
data: createDocumentAuditLogData({ // Dictation only applies when advancing to a single-recipient step.
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_UPDATED, const canDictateNextSigner =
envelopeId: envelope.id, Boolean(nextSigner) && Boolean(envelope.documentMeta?.allowDictateNextSigner) && nextGroup.length === 1;
user: {
name: recipientName, await prisma.$transaction(async (tx) => {
email: recipientEmail, if (canDictateNextSigner && nextSigner) {
}, const [nextRecipient] = nextGroup;
requestMetadata,
await tx.documentAuditLog.create({
data: createDocumentAuditLogData({
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_UPDATED,
envelopeId: envelope.id,
user: {
name: recipientName,
email: recipientEmail,
},
requestMetadata,
data: {
recipientEmail: nextRecipient.email,
recipientName: nextRecipient.name,
recipientId: nextRecipient.id,
recipientRole: nextRecipient.role,
changes: [
{
type: RECIPIENT_DIFF_TYPE.NAME,
from: nextRecipient.name,
to: nextSigner.name,
},
{
type: RECIPIENT_DIFF_TYPE.EMAIL,
from: nextRecipient.email,
to: nextSigner.email,
},
],
},
}),
});
}
for (const nextRecipient of nextGroup) {
await tx.recipient.update({
where: { id: nextRecipient.id },
data: { data: {
recipientEmail: nextRecipient.email, sendStatus: SendStatus.SENT,
recipientName: nextRecipient.name, sentAt: new Date(),
recipientId: nextRecipient.id, ...(canDictateNextSigner && nextSigner
recipientRole: nextRecipient.role, ? {
changes: [ name: nextSigner.name,
{ email: nextSigner.email,
type: RECIPIENT_DIFF_TYPE.NAME, }
from: nextRecipient.name, : {}),
to: nextSigner.name,
},
{
type: RECIPIENT_DIFF_TYPE.EMAIL,
from: nextRecipient.email,
to: nextSigner.email,
},
],
}, },
}), });
}); }
} });
for (const nextRecipient of recipientsToActivate) { for (const nextRecipient of nextGroup) {
await tx.recipient.update({ await jobs.triggerJob({
where: { id: nextRecipient.id }, name: 'send.signing.requested.email',
data: { payload: {
sendStatus: SendStatus.SENT, userId: envelope.userId,
sentAt: new Date(), documentId: legacyDocumentId,
...(canDictateNextSigner && nextSigner recipientId: nextRecipient.id,
? { requestMetadata,
name: nextSigner.name,
email: nextSigner.email,
}
: {}),
}, },
}); });
} }
});
for (const nextRecipient of recipientsToActivate) {
await jobs.triggerJob({
name: 'send.signing.requested.email',
payload: {
userId: envelope.userId,
documentId: legacyDocumentId,
recipientId: nextRecipient.id,
requestMetadata,
},
});
} }
} }
} }