mirror of
https://github.com/documenso/documenso.git
synced 2026-08-27 00:32:29 +10:00
fix: wip
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import { isSignatureFieldType } from '@documenso/prisma/guards/is-signature-field';
|
||||
import type { Envelope, Field, Recipient } from '@prisma/client';
|
||||
import { RecipientRole, SigningStatus } from '@prisma/client';
|
||||
import type { Envelope, Field, Prisma, Recipient } from '@prisma/client';
|
||||
import { EnvelopeType, RecipientRole, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../constants/app';
|
||||
import { AppError, AppErrorCode } from '../errors/app-error';
|
||||
import type { TEditorEnvelope } from '../types/envelope-editor';
|
||||
import type { TRecipientLite } from '../types/recipient';
|
||||
import { extractLegacyIds } from '../universal/id';
|
||||
import { zEmail } from './zod';
|
||||
@@ -22,11 +23,100 @@ export const isCcRecipient = (recipient: Pick<Recipient, 'role'>) => {
|
||||
return recipient.role === RecipientRole.CC;
|
||||
};
|
||||
|
||||
export const isAssistantLastSigner = (recipients: Pick<Recipient, 'role'>[]) => {
|
||||
/**
|
||||
* Whether an assistant sits in the last signing step (nobody after them to assist).
|
||||
*
|
||||
* Falls back to a positional check when no recipient carries a signing order.
|
||||
*/
|
||||
export const isAssistantLastSigner = (
|
||||
recipients: Array<Pick<Recipient, 'role'> & { signingOrder?: number | null }>,
|
||||
) => {
|
||||
const nonCcRecipients = recipients.filter((recipient) => !isCcRecipient(recipient));
|
||||
const lastNonCcRecipient = nonCcRecipients[nonCcRecipients.length - 1];
|
||||
|
||||
return lastNonCcRecipient?.role === RecipientRole.ASSISTANT;
|
||||
if (nonCcRecipients.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasAnySigningOrder = nonCcRecipients.some((recipient) => typeof recipient.signingOrder === 'number');
|
||||
|
||||
if (!hasAnySigningOrder) {
|
||||
return nonCcRecipients[nonCcRecipients.length - 1]?.role === RecipientRole.ASSISTANT;
|
||||
}
|
||||
|
||||
const maxOrder = Math.max(...nonCcRecipients.map((recipient) => recipient.signingOrder ?? Number.MAX_SAFE_INTEGER));
|
||||
|
||||
return nonCcRecipients.some(
|
||||
(recipient) =>
|
||||
(recipient.signingOrder ?? Number.MAX_SAFE_INTEGER) === maxOrder && recipient.role === RecipientRole.ASSISTANT,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Prisma `where` input matching recipients in strictly LATER signing steps
|
||||
* than the assistant, under the null-as-last convention shared with
|
||||
* `effectiveOrder`: a missing signing order means the LAST step.
|
||||
*
|
||||
* - An assistant with a numeric order reaches recipients with a greater
|
||||
* numeric order, plus null-order recipients (the tail step).
|
||||
* - A null-order assistant sits in the last step themselves: nobody comes
|
||||
* after them, so this matches nothing.
|
||||
*
|
||||
* The historical inline filters used `signingOrder ?? 0`, which encoded
|
||||
* "null = first" — hiding tail-step recipients from ordered assistants and
|
||||
* letting a null-order assistant reach every ordered (earlier) recipient.
|
||||
*
|
||||
* Deliberately applied regardless of the document's signing order mode:
|
||||
* restricting assistants by step on PARALLEL documents mirrors the
|
||||
* historical behavior and fails closed. Whether parallel documents should
|
||||
* lift the restriction entirely is an open product decision.
|
||||
*/
|
||||
export const getLaterSigningStepRecipientsWhereInput = (
|
||||
assistant: Pick<Recipient, 'signingOrder'>,
|
||||
): Prisma.RecipientWhereInput => {
|
||||
if (assistant.signingOrder === null) {
|
||||
// Matches nothing.
|
||||
return { id: { in: [] } };
|
||||
}
|
||||
|
||||
return {
|
||||
OR: [{ signingOrder: { gt: assistant.signingOrder } }, { signingOrder: null }],
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Prisma `where` input matching every recipient an assistant may act for:
|
||||
* themself, plus recipients in strictly later steps — never their own group
|
||||
* peers.
|
||||
*/
|
||||
export const getAssistableRecipientsWhereInput = (
|
||||
assistant: Pick<Recipient, 'id' | 'signingOrder'>,
|
||||
): Prisma.RecipientWhereInput => ({
|
||||
OR: [{ id: assistant.id }, getLaterSigningStepRecipientsWhereInput(assistant)],
|
||||
});
|
||||
|
||||
/**
|
||||
* Prisma `where` input matching the recipients whose fields the token holder
|
||||
* may act on: non-assistants may only act on their own fields, while
|
||||
* assistants may also act on fields of unsigned recipients in strictly later
|
||||
* steps.
|
||||
*
|
||||
* Shared by every field-level endpoint (sign / uninsert, V1 and V2) so the
|
||||
* scoping rule cannot drift between them.
|
||||
*/
|
||||
export const getFieldOwnerWhereInput = (
|
||||
recipient: Pick<Recipient, 'id' | 'role' | 'signingOrder' | 'envelopeId'>,
|
||||
): Prisma.RecipientWhereInput => {
|
||||
if (recipient.role !== RecipientRole.ASSISTANT) {
|
||||
return { id: recipient.id };
|
||||
}
|
||||
|
||||
return {
|
||||
signingStatus: {
|
||||
not: SigningStatus.SIGNED,
|
||||
},
|
||||
envelopeId: recipient.envelopeId,
|
||||
AND: [getAssistableRecipientsWhereInput(recipient)],
|
||||
};
|
||||
};
|
||||
|
||||
export const sortRecipientsForSigningOrder = <T extends RecipientWithSigningOrder>(recipients: T[]): T[] => {
|
||||
@@ -120,6 +210,36 @@ export const canRecipientBeModified = (
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Editor-level wrapper around `canRecipientBeModified`.
|
||||
*
|
||||
* Template recipients and unsaved (id-less) recipients can always be modified.
|
||||
*/
|
||||
export const canEditorRecipientBeModified = (
|
||||
envelope: Pick<TEditorEnvelope, 'type' | 'recipients' | 'fields'>,
|
||||
recipientId?: number,
|
||||
) => {
|
||||
if (envelope.type === EnvelopeType.TEMPLATE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (recipientId === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const recipient = envelope.recipients.find((r) => r.id === recipientId);
|
||||
|
||||
// The envelope lags behind the form: a recipient the editor has just created
|
||||
// is not in it yet. Such a recipient cannot have acted on the document, so
|
||||
// treat an unknown id as modifiable — reporting it as locked would freeze
|
||||
// reordering for a document nobody has signed.
|
||||
if (!recipient) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return canRecipientBeModified(recipient, envelope.fields);
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether a recipient can have their fields modified by the document owner.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user