mirror of
https://github.com/documenso/documenso.git
synced 2026-08-24 07:12:23 +10:00
feat: use group-aware turn checks for sequential signing
This commit is contained in:
@@ -5,13 +5,14 @@ import EnvelopeSchema from '@documenso/prisma/generated/zod/modelSchema/Envelope
|
|||||||
import SignatureSchema from '@documenso/prisma/generated/zod/modelSchema/SignatureSchema';
|
import SignatureSchema from '@documenso/prisma/generated/zod/modelSchema/SignatureSchema';
|
||||||
import TeamSchema from '@documenso/prisma/generated/zod/modelSchema/TeamSchema';
|
import TeamSchema from '@documenso/prisma/generated/zod/modelSchema/TeamSchema';
|
||||||
import UserSchema from '@documenso/prisma/generated/zod/modelSchema/UserSchema';
|
import UserSchema from '@documenso/prisma/generated/zod/modelSchema/UserSchema';
|
||||||
import { DocumentSigningOrder, DocumentStatus, EnvelopeType, RecipientRole, SigningStatus } from '@prisma/client';
|
import { DocumentSigningOrder, DocumentStatus, EnvelopeType, SigningStatus } from '@prisma/client';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
import type { TDocumentAuthMethods } from '../../types/document-auth';
|
import type { TDocumentAuthMethods } from '../../types/document-auth';
|
||||||
import { ZEnvelopeFieldSchema, ZFieldSchema } from '../../types/field';
|
import { ZEnvelopeFieldSchema, ZFieldSchema } from '../../types/field';
|
||||||
import { ZRecipientLiteSchema } from '../../types/recipient';
|
import { ZRecipientLiteSchema } from '../../types/recipient';
|
||||||
|
import { isRecipientTurnBySigningOrder } from '../../utils/recipient-groups';
|
||||||
import { isRecipientExpired } from '../../utils/recipients';
|
import { isRecipientExpired } from '../../utils/recipients';
|
||||||
import { isRecipientAuthorized } from '../document/is-recipient-authorized';
|
import { isRecipientAuthorized } from '../document/is-recipient-authorized';
|
||||||
import { getTeamSettings } from '../team/get-team-settings';
|
import { getTeamSettings } from '../team/get-team-settings';
|
||||||
@@ -194,9 +195,6 @@ export const getEnvelopeForRecipientSigning = async ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
orderBy: {
|
|
||||||
signingOrder: 'asc',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
envelopeItems: true,
|
envelopeItems: true,
|
||||||
team: {
|
team: {
|
||||||
@@ -260,23 +258,9 @@ export const getEnvelopeForRecipientSigning = async ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let isRecipientsTurn = true;
|
const isRecipientsTurn =
|
||||||
|
envelope.documentMeta.signingOrder !== DocumentSigningOrder.SEQUENTIAL ||
|
||||||
const currentRecipientIndex = envelope.recipients.findIndex((r) => r.token === token);
|
isRecipientTurnBySigningOrder(envelope.recipients, recipient);
|
||||||
|
|
||||||
if (envelope.documentMeta.signingOrder === DocumentSigningOrder.SEQUENTIAL && currentRecipientIndex !== -1) {
|
|
||||||
for (let i = 0; i < currentRecipientIndex; i++) {
|
|
||||||
// CC recipients have no action to take, so they can never block the flow.
|
|
||||||
if (envelope.recipients[i].role === RecipientRole.CC) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (envelope.recipients[i].signingStatus !== SigningStatus.SIGNED) {
|
|
||||||
isRecipientsTurn = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sender = settings.includeSenderDetails
|
const sender = settings.includeSenderDetails
|
||||||
? {
|
? {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import { DocumentSigningOrder, EnvelopeType, RecipientRole, SigningStatus } from '@prisma/client';
|
import { DocumentSigningOrder, EnvelopeType } from '@prisma/client';
|
||||||
|
|
||||||
|
import { isRecipientTurnBySigningOrder } from '../../utils/recipient-groups';
|
||||||
|
|
||||||
export type GetIsRecipientTurnOptions = {
|
export type GetIsRecipientTurnOptions = {
|
||||||
token: string;
|
token: string;
|
||||||
@@ -17,11 +19,7 @@ export async function getIsRecipientsTurnToSign({ token }: GetIsRecipientTurnOpt
|
|||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
documentMeta: true,
|
documentMeta: true,
|
||||||
recipients: {
|
recipients: true,
|
||||||
orderBy: {
|
|
||||||
signingOrder: 'asc',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -29,24 +27,11 @@ export async function getIsRecipientsTurnToSign({ token }: GetIsRecipientTurnOpt
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { recipients } = envelope;
|
const currentRecipient = envelope.recipients.find((recipient) => recipient.token === token);
|
||||||
|
|
||||||
const currentRecipientIndex = recipients.findIndex((r) => r.token === token);
|
if (!currentRecipient) {
|
||||||
|
|
||||||
if (currentRecipientIndex === -1) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < currentRecipientIndex; i++) {
|
return isRecipientTurnBySigningOrder(envelope.recipients, currentRecipient);
|
||||||
// CC recipients have no action to take, so they can never block the flow.
|
|
||||||
if (recipients[i].role === RecipientRole.CC) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recipients[i].signingStatus !== SigningStatus.SIGNED) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ export const mergeSteps = <T extends EditorRecipient>(
|
|||||||
const targetStep = steps[targetStepIndex];
|
const targetStep = steps[targetStepIndex];
|
||||||
|
|
||||||
if (!sourceStep || !targetStep || sourceStepIndex === targetStepIndex) {
|
if (!sourceStep || !targetStep || sourceStepIndex === targetStepIndex) {
|
||||||
return recipients;
|
return normalizeGroupedSigningOrders(recipients, canUpdateRecipient);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sourceFormIds = new Set(sourceStep.members.map((member) => member.formId));
|
const sourceFormIds = new Set(sourceStep.members.map((member) => member.formId));
|
||||||
@@ -162,11 +162,11 @@ export const moveRecipientToStep = <T extends EditorRecipient>(
|
|||||||
const mover = recipients.find((recipient) => recipient.formId === formId);
|
const mover = recipients.find((recipient) => recipient.formId === formId);
|
||||||
|
|
||||||
if (!targetStep || !mover || isCcRecipient(mover)) {
|
if (!targetStep || !mover || isCcRecipient(mover)) {
|
||||||
return recipients;
|
return normalizeGroupedSigningOrders(recipients, canUpdateRecipient);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetStep.members.some((member) => member.formId === formId)) {
|
if (targetStep.members.some((member) => member.formId === formId)) {
|
||||||
return recipients;
|
return normalizeGroupedSigningOrders(recipients, canUpdateRecipient);
|
||||||
}
|
}
|
||||||
|
|
||||||
const remaining = recipients.filter((recipient) => recipient.formId !== formId);
|
const remaining = recipients.filter((recipient) => recipient.formId !== formId);
|
||||||
@@ -197,7 +197,7 @@ export const extractRecipientToNewStep = <T extends EditorRecipient>(
|
|||||||
const mover = recipients.find((recipient) => recipient.formId === formId);
|
const mover = recipients.find((recipient) => recipient.formId === formId);
|
||||||
|
|
||||||
if (!mover || isCcRecipient(mover)) {
|
if (!mover || isCcRecipient(mover)) {
|
||||||
return recipients;
|
return normalizeGroupedSigningOrders(recipients, canUpdateRecipient);
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentStepIndex = steps.findIndex((step) => step.members.some((member) => member.formId === formId));
|
const currentStepIndex = steps.findIndex((step) => step.members.some((member) => member.formId === formId));
|
||||||
@@ -205,7 +205,7 @@ export const extractRecipientToNewStep = <T extends EditorRecipient>(
|
|||||||
|
|
||||||
// Dropping a solo step into the gap directly above or below itself is a no-op.
|
// Dropping a solo step into the gap directly above or below itself is a no-op.
|
||||||
if (isSoloStep && (insertStepIndex === currentStepIndex || insertStepIndex === currentStepIndex + 1)) {
|
if (isSoloStep && (insertStepIndex === currentStepIndex || insertStepIndex === currentStepIndex + 1)) {
|
||||||
return recipients;
|
return normalizeGroupedSigningOrders(recipients, canUpdateRecipient);
|
||||||
}
|
}
|
||||||
|
|
||||||
const insertOrder =
|
const insertOrder =
|
||||||
@@ -234,7 +234,7 @@ export const reorderStep = <T extends EditorRecipient>(
|
|||||||
const { steps, ccRecipients } = groupRecipientsBySigningOrder(recipients);
|
const { steps, ccRecipients } = groupRecipientsBySigningOrder(recipients);
|
||||||
|
|
||||||
if (!steps[fromStepIndex] || fromStepIndex === toStepIndex) {
|
if (!steps[fromStepIndex] || fromStepIndex === toStepIndex) {
|
||||||
return recipients;
|
return normalizeGroupedSigningOrders(recipients, canUpdateRecipient);
|
||||||
}
|
}
|
||||||
|
|
||||||
const reorderedSteps = [...steps];
|
const reorderedSteps = [...steps];
|
||||||
@@ -365,7 +365,7 @@ export const ungroupStep = <T extends EditorRecipient>(
|
|||||||
const step = steps[stepIndex];
|
const step = steps[stepIndex];
|
||||||
|
|
||||||
if (!step || step.members.length < 2) {
|
if (!step || step.members.length < 2) {
|
||||||
return recipients;
|
return normalizeGroupedSigningOrders(recipients, canUpdateRecipient);
|
||||||
}
|
}
|
||||||
|
|
||||||
const offsetByFormId = new Map(step.members.map((member, index) => [member.formId, index]));
|
const offsetByFormId = new Map(step.members.map((member, index) => [member.formId, index]));
|
||||||
|
|||||||
Reference in New Issue
Block a user