mirror of
https://github.com/documenso/documenso.git
synced 2026-07-26 09:54:51 +10:00
chore: merge main, resolve biome formatting conflicts
Merge origin/main into feat/external-2fa-codes. Resolve formatting conflicts caused by biome rollout; preserve both feature streams: PR's external 2FA token + signing-session 2FA proof additions plus main's RateLimit/RecipientExpired/signingReminders/date-auto-insert. In complete-document-with-token.ts, drop the duplicate early field-fetching block introduced when main moved that logic later with date auto-insert support; keep the EXTERNAL_TWO_FACTOR_AUTH check using derivedRecipientActionAuth.
This commit is contained in:
@@ -1,14 +1,11 @@
|
||||
import { EnvelopeType, RecipientRole } from '@prisma/client';
|
||||
import { SendStatus, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import type { TRecipientAccessAuthTypes } from '@documenso/lib/types/document-auth';
|
||||
import { type TRecipientActionAuthTypes } from '@documenso/lib/types/document-auth';
|
||||
import type { TRecipientAccessAuthTypes, TRecipientActionAuthTypes } from '@documenso/lib/types/document-auth';
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { createRecipientAuthOptions } from '@documenso/lib/utils/document-auth';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType, RecipientRole, SendStatus, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { EnvelopeIdOptions } from '../../utils/envelope';
|
||||
@@ -105,8 +102,7 @@ export const createEnvelopeRecipients = async ({
|
||||
signingOrder: recipient.signingOrder,
|
||||
token: nanoid(),
|
||||
sendStatus: recipient.role === RecipientRole.CC ? SendStatus.SENT : SendStatus.NOT_SENT,
|
||||
signingStatus:
|
||||
recipient.role === RecipientRole.CC ? SigningStatus.SIGNED : SigningStatus.NOT_SIGNED,
|
||||
signingStatus: recipient.role === RecipientRole.CC ? SigningStatus.SIGNED : SigningStatus.NOT_SIGNED,
|
||||
authOptions,
|
||||
},
|
||||
});
|
||||
@@ -136,8 +132,6 @@ export const createEnvelopeRecipients = async ({
|
||||
});
|
||||
|
||||
return {
|
||||
recipients: createdRecipients.map((recipient) =>
|
||||
mapRecipientToLegacyRecipient(recipient, envelope),
|
||||
),
|
||||
recipients: createdRecipients.map((recipient) => mapRecipientToLegacyRecipient(recipient, envelope)),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { EnvelopeType, SendStatus } from '@prisma/client';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import RecipientRemovedFromDocumentTemplate from '@documenso/email/templates/recipient-removed-from-document';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { EnvelopeType, SendStatus } from '@prisma/client';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
@@ -134,9 +132,7 @@ export const deleteEnvelopeRecipient = async ({
|
||||
});
|
||||
});
|
||||
|
||||
const isRecipientRemovedEmailEnabled = extractDerivedDocumentEmailSettings(
|
||||
envelope.documentMeta,
|
||||
).recipientRemoved;
|
||||
const isRecipientRemovedEmailEnabled = extractDerivedDocumentEmailSettings(envelope.documentMeta).recipientRemoved;
|
||||
|
||||
// Send email to deleted recipient.
|
||||
if (
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { DocumentSigningOrder, EnvelopeType, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentSigningOrder, EnvelopeType, SigningStatus } from '@prisma/client';
|
||||
|
||||
export type GetIsRecipientTurnOptions = {
|
||||
token: string;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { mapDocumentIdToSecondaryId } from '../../utils/envelope';
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { mapSecondaryIdToDocumentId, mapSecondaryIdToTemplateId } from '../../utils/envelope';
|
||||
@@ -17,12 +16,7 @@ export type GetRecipientByIdOptions = {
|
||||
* Get a recipient by ID. This will also return the recipient signing token so
|
||||
* be careful when using this.
|
||||
*/
|
||||
export const getRecipientById = async ({
|
||||
recipientId,
|
||||
userId,
|
||||
teamId,
|
||||
type,
|
||||
}: GetRecipientByIdOptions) => {
|
||||
export const getRecipientById = async ({ recipientId, userId, teamId, type }: GetRecipientByIdOptions) => {
|
||||
const recipient = await prisma.recipient.findFirst({
|
||||
where: {
|
||||
id: recipientId,
|
||||
@@ -48,14 +42,8 @@ export const getRecipientById = async ({
|
||||
}
|
||||
|
||||
const legacyId = {
|
||||
documentId:
|
||||
type === EnvelopeType.DOCUMENT
|
||||
? mapSecondaryIdToDocumentId(recipient.envelope.secondaryId)
|
||||
: null,
|
||||
templateId:
|
||||
type === EnvelopeType.TEMPLATE
|
||||
? mapSecondaryIdToTemplateId(recipient.envelope.secondaryId)
|
||||
: null,
|
||||
documentId: type === EnvelopeType.DOCUMENT ? mapSecondaryIdToDocumentId(recipient.envelope.secondaryId) : null,
|
||||
templateId: type === EnvelopeType.TEMPLATE ? mapSecondaryIdToTemplateId(recipient.envelope.secondaryId) : null,
|
||||
};
|
||||
|
||||
// Backwards compatibility mapping.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { EnvelopeType, Prisma } from '@prisma/client';
|
||||
|
||||
import { buildTeamWhereQuery } from '@documenso/lib/utils/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType, Prisma } from '@prisma/client';
|
||||
|
||||
export type GetRecipientSuggestionsOptions = {
|
||||
userId: number;
|
||||
@@ -9,11 +8,7 @@ export type GetRecipientSuggestionsOptions = {
|
||||
query: string;
|
||||
};
|
||||
|
||||
export const getRecipientSuggestions = async ({
|
||||
userId,
|
||||
teamId,
|
||||
query,
|
||||
}: GetRecipientSuggestionsOptions) => {
|
||||
export const getRecipientSuggestions = async ({ userId, teamId, query }: GetRecipientSuggestionsOptions) => {
|
||||
const trimmedQuery = query.trim();
|
||||
|
||||
const nameEmailFilter = trimmedQuery
|
||||
@@ -89,9 +84,7 @@ export const getRecipientSuggestions = async ({
|
||||
take: 5,
|
||||
});
|
||||
|
||||
const uniqueTeamMember = teamMembers.find(
|
||||
(member) => !recipients.some((r) => r.email === member.user.email),
|
||||
);
|
||||
const uniqueTeamMember = teamMembers.find((member) => !recipients.some((r) => r.email === member.user.email));
|
||||
|
||||
if (uniqueTeamMember) {
|
||||
const teamMemberSuggestion = {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { FieldType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { FieldType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
|
||||
|
||||
@@ -10,11 +9,7 @@ export interface GetRecipientsForDocumentOptions {
|
||||
teamId: number;
|
||||
}
|
||||
|
||||
export const getRecipientsForDocument = async ({
|
||||
documentId,
|
||||
userId,
|
||||
teamId,
|
||||
}: GetRecipientsForDocumentOptions) => {
|
||||
export const getRecipientsForDocument = async ({ documentId, userId, teamId }: GetRecipientsForDocumentOptions) => {
|
||||
const { envelopeWhereInput } = await getEnvelopeWhereInput({
|
||||
id: {
|
||||
type: 'documentId',
|
||||
|
||||
@@ -1,27 +1,18 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import type { Recipient } from '@prisma/client';
|
||||
import { EnvelopeType, RecipientRole } from '@prisma/client';
|
||||
import { SendStatus, SigningStatus } from '@prisma/client';
|
||||
import { isDeepEqual } from 'remeda';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import RecipientRemovedFromDocumentTemplate from '@documenso/email/templates/recipient-removed-from-document';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import type { TRecipientAccessAuthTypes } from '@documenso/lib/types/document-auth';
|
||||
import {
|
||||
type TRecipientActionAuthTypes,
|
||||
ZRecipientAuthOptionsSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { type TRecipientActionAuthTypes, ZRecipientAuthOptionsSchema } from '@documenso/lib/types/document-auth';
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import {
|
||||
createDocumentAuditLogData,
|
||||
diffRecipientChanges,
|
||||
} from '@documenso/lib/utils/document-audit-logs';
|
||||
import { createDocumentAuditLogData, diffRecipientChanges } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { createRecipientAuthOptions } from '@documenso/lib/utils/document-auth';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import type { Recipient } from '@prisma/client';
|
||||
import { EnvelopeType, RecipientRole, SendStatus, SigningStatus } from '@prisma/client';
|
||||
import { createElement } from 'react';
|
||||
import { isDeepEqual } from 'remeda';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
@@ -120,17 +111,13 @@ export const setDocumentRecipients = async ({
|
||||
const existingRecipients = envelope.recipients;
|
||||
|
||||
const removedRecipients = existingRecipients.filter(
|
||||
(existingRecipient) =>
|
||||
!normalizedRecipients.find((recipient) => recipient.id === existingRecipient.id),
|
||||
(existingRecipient) => !normalizedRecipients.find((recipient) => recipient.id === existingRecipient.id),
|
||||
);
|
||||
|
||||
const linkedRecipients = normalizedRecipients.map((recipient) => {
|
||||
const existing = existingRecipients.find(
|
||||
(existingRecipient) => existingRecipient.id === recipient.id,
|
||||
);
|
||||
const existing = existingRecipients.find((existingRecipient) => existingRecipient.id === recipient.id);
|
||||
|
||||
const canPersistedRecipientBeModified =
|
||||
existing && canRecipientBeModified(existing, envelope.fields);
|
||||
const canPersistedRecipientBeModified = existing && canRecipientBeModified(existing, envelope.fields);
|
||||
|
||||
if (
|
||||
existing &&
|
||||
@@ -180,8 +167,7 @@ export const setDocumentRecipients = async ({
|
||||
signingOrder: recipient.signingOrder,
|
||||
envelopeId: envelope.id,
|
||||
sendStatus: recipient.role === RecipientRole.CC ? SendStatus.SENT : SendStatus.NOT_SENT,
|
||||
signingStatus:
|
||||
recipient.role === RecipientRole.CC ? SigningStatus.SIGNED : SigningStatus.NOT_SIGNED,
|
||||
signingStatus: recipient.role === RecipientRole.CC ? SigningStatus.SIGNED : SigningStatus.NOT_SIGNED,
|
||||
authOptions,
|
||||
},
|
||||
create: {
|
||||
@@ -192,8 +178,7 @@ export const setDocumentRecipients = async ({
|
||||
token: nanoid(),
|
||||
envelopeId: envelope.id,
|
||||
sendStatus: recipient.role === RecipientRole.CC ? SendStatus.SENT : SendStatus.NOT_SENT,
|
||||
signingStatus:
|
||||
recipient.role === RecipientRole.CC ? SigningStatus.SIGNED : SigningStatus.NOT_SIGNED,
|
||||
signingStatus: recipient.role === RecipientRole.CC ? SigningStatus.SIGNED : SigningStatus.NOT_SIGNED,
|
||||
authOptions,
|
||||
},
|
||||
});
|
||||
@@ -220,9 +205,7 @@ export const setDocumentRecipients = async ({
|
||||
recipientRole: upsertedRecipient.role,
|
||||
};
|
||||
|
||||
const changes = recipient._persisted
|
||||
? diffRecipientChanges(recipient._persisted, upsertedRecipient)
|
||||
: [];
|
||||
const changes = recipient._persisted ? diffRecipientChanges(recipient._persisted, upsertedRecipient) : [];
|
||||
|
||||
// Handle recipient updated audit log.
|
||||
if (recipient._persisted && changes.length > 0) {
|
||||
@@ -290,9 +273,7 @@ export const setDocumentRecipients = async ({
|
||||
});
|
||||
});
|
||||
|
||||
const isRecipientRemovedEmailEnabled = extractDerivedDocumentEmailSettings(
|
||||
envelope.documentMeta,
|
||||
).recipientRemoved;
|
||||
const isRecipientRemovedEmailEnabled = extractDerivedDocumentEmailSettings(envelope.documentMeta).recipientRemoved;
|
||||
|
||||
// Send emails to deleted recipients who have emails.
|
||||
await Promise.all(
|
||||
@@ -338,12 +319,8 @@ export const setDocumentRecipients = async ({
|
||||
|
||||
// Filter out recipients that have been removed or have been updated.
|
||||
const filteredRecipients: RecipientDataWithClientId[] = existingRecipients.filter((recipient) => {
|
||||
const isRemoved = removedRecipients.find(
|
||||
(removedRecipient) => removedRecipient.id === recipient.id,
|
||||
);
|
||||
const isUpdated = persistedRecipients.find(
|
||||
(persistedRecipient) => persistedRecipient.id === recipient.id,
|
||||
);
|
||||
const isRemoved = removedRecipients.find((removedRecipient) => removedRecipient.id === recipient.id);
|
||||
const isUpdated = persistedRecipients.find((persistedRecipient) => persistedRecipient.id === recipient.id);
|
||||
|
||||
return !isRemoved && !isUpdated;
|
||||
});
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import type { Recipient } from '@prisma/client';
|
||||
import { EnvelopeType, RecipientRole } from '@prisma/client';
|
||||
|
||||
import {
|
||||
DIRECT_TEMPLATE_RECIPIENT_EMAIL,
|
||||
DIRECT_TEMPLATE_RECIPIENT_NAME,
|
||||
} from '@documenso/lib/constants/direct-templates';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Recipient } from '@prisma/client';
|
||||
import { EnvelopeType, RecipientRole } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import {
|
||||
type TRecipientActionAuthTypes,
|
||||
ZRecipientAuthOptionsSchema,
|
||||
} from '../../types/document-auth';
|
||||
import { type TRecipientActionAuthTypes, ZRecipientAuthOptionsSchema } from '../../types/document-auth';
|
||||
import { nanoid } from '../../universal/id';
|
||||
import { createRecipientAuthOptions } from '../../utils/document-auth';
|
||||
import { type EnvelopeIdOptions, mapSecondaryIdToTemplateId } from '../../utils/envelope';
|
||||
@@ -21,22 +17,10 @@ export type SetTemplateRecipientsOptions = {
|
||||
userId: number;
|
||||
teamId: number;
|
||||
id: EnvelopeIdOptions;
|
||||
recipients: {
|
||||
id?: number;
|
||||
email: string;
|
||||
name: string;
|
||||
role: RecipientRole;
|
||||
signingOrder?: number | null;
|
||||
actionAuth?: TRecipientActionAuthTypes[];
|
||||
}[];
|
||||
recipients: RecipientData[];
|
||||
};
|
||||
|
||||
export const setTemplateRecipients = async ({
|
||||
userId,
|
||||
teamId,
|
||||
id,
|
||||
recipients,
|
||||
}: SetTemplateRecipientsOptions) => {
|
||||
export const setTemplateRecipients = async ({ userId, teamId, id, recipients }: SetTemplateRecipientsOptions) => {
|
||||
const { envelopeWhereInput } = await getEnvelopeWhereInput({
|
||||
id,
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
@@ -95,8 +79,7 @@ export const setTemplateRecipients = async ({
|
||||
const existingRecipients = envelope.recipients;
|
||||
|
||||
const removedRecipients = existingRecipients.filter(
|
||||
(existingRecipient) =>
|
||||
!normalizedRecipients.find((recipient) => recipient.id === existingRecipient.id),
|
||||
(existingRecipient) => !normalizedRecipients.find((recipient) => recipient.id === existingRecipient.id),
|
||||
);
|
||||
|
||||
if (envelope.directLink !== null) {
|
||||
@@ -122,9 +105,7 @@ export const setTemplateRecipients = async ({
|
||||
}
|
||||
|
||||
const linkedRecipients = normalizedRecipients.map((recipient) => {
|
||||
const existing = existingRecipients.find(
|
||||
(existingRecipient) => existingRecipient.id === recipient.id,
|
||||
);
|
||||
const existing = existingRecipients.find((existingRecipient) => existingRecipient.id === recipient.id);
|
||||
|
||||
return {
|
||||
...recipient,
|
||||
@@ -183,7 +164,10 @@ export const setTemplateRecipients = async ({
|
||||
});
|
||||
}
|
||||
|
||||
return upsertedRecipient;
|
||||
return {
|
||||
...upsertedRecipient,
|
||||
clientId: recipient.clientId,
|
||||
};
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -199,13 +183,9 @@ export const setTemplateRecipients = async ({
|
||||
}
|
||||
|
||||
// Filter out recipients that have been removed or have been updated.
|
||||
const filteredRecipients: Recipient[] = existingRecipients.filter((recipient) => {
|
||||
const isRemoved = removedRecipients.find(
|
||||
(removedRecipient) => removedRecipient.id === recipient.id,
|
||||
);
|
||||
const isUpdated = persistedRecipients.find(
|
||||
(persistedRecipient) => persistedRecipient.id === recipient.id,
|
||||
);
|
||||
const filteredRecipients: RecipientDataWithClientId[] = existingRecipients.filter((recipient) => {
|
||||
const isRemoved = removedRecipients.find((removedRecipient) => removedRecipient.id === recipient.id);
|
||||
const isUpdated = persistedRecipients.find((persistedRecipient) => persistedRecipient.id === recipient.id);
|
||||
|
||||
return !isRemoved && !isUpdated;
|
||||
});
|
||||
@@ -218,3 +198,17 @@ export const setTemplateRecipients = async ({
|
||||
})),
|
||||
};
|
||||
};
|
||||
|
||||
type RecipientData = {
|
||||
id?: number;
|
||||
clientId?: string | null;
|
||||
email: string;
|
||||
name: string;
|
||||
role: RecipientRole;
|
||||
signingOrder?: number | null;
|
||||
actionAuth?: TRecipientActionAuthTypes[];
|
||||
};
|
||||
|
||||
type RecipientDataWithClientId = Recipient & {
|
||||
clientId?: string | null;
|
||||
};
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
import { EnvelopeType, RecipientRole, SendStatus, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import type { TRecipientAccessAuthTypes } from '@documenso/lib/types/document-auth';
|
||||
import {
|
||||
type TRecipientActionAuthTypes,
|
||||
ZRecipientAuthOptionsSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { type TRecipientActionAuthTypes, ZRecipientAuthOptionsSchema } from '@documenso/lib/types/document-auth';
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import {
|
||||
createDocumentAuditLogData,
|
||||
diffRecipientChanges,
|
||||
} from '@documenso/lib/utils/document-audit-logs';
|
||||
import { createDocumentAuditLogData, diffRecipientChanges } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { createRecipientAuthOptions } from '@documenso/lib/utils/document-auth';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType, RecipientRole, SendStatus, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { extractLegacyIds } from '../../universal/id';
|
||||
import { type EnvelopeIdOptions } from '../../utils/envelope';
|
||||
import type { EnvelopeIdOptions } from '../../utils/envelope';
|
||||
import { mapFieldToLegacyField } from '../../utils/fields';
|
||||
import { canRecipientBeModified } from '../../utils/recipients';
|
||||
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
|
||||
@@ -92,9 +85,7 @@ export const updateEnvelopeRecipients = async ({
|
||||
}
|
||||
|
||||
const recipientsToUpdate = recipients.map((recipient) => {
|
||||
const originalRecipient = envelope.recipients.find(
|
||||
(existingRecipient) => existingRecipient.id === recipient.id,
|
||||
);
|
||||
const originalRecipient = envelope.recipients.find((existingRecipient) => existingRecipient.id === recipient.id);
|
||||
|
||||
if (!originalRecipient) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
@@ -142,12 +133,8 @@ export const updateEnvelopeRecipients = async ({
|
||||
role: mergedRecipient.role,
|
||||
signingOrder: mergedRecipient.signingOrder,
|
||||
envelopeId: envelope.id,
|
||||
sendStatus:
|
||||
mergedRecipient.role === RecipientRole.CC ? SendStatus.SENT : SendStatus.NOT_SENT,
|
||||
signingStatus:
|
||||
mergedRecipient.role === RecipientRole.CC
|
||||
? SigningStatus.SIGNED
|
||||
: SigningStatus.NOT_SIGNED,
|
||||
sendStatus: mergedRecipient.role === RecipientRole.CC ? SendStatus.SENT : SendStatus.NOT_SENT,
|
||||
signingStatus: mergedRecipient.role === RecipientRole.CC ? SigningStatus.SIGNED : SigningStatus.NOT_SIGNED,
|
||||
authOptions,
|
||||
},
|
||||
include: {
|
||||
@@ -158,8 +145,7 @@ export const updateEnvelopeRecipients = async ({
|
||||
// Clear all fields if the recipient role is changed to a type that cannot have fields.
|
||||
if (
|
||||
originalRecipient.role !== updatedRecipient.role &&
|
||||
(updatedRecipient.role === RecipientRole.CC ||
|
||||
updatedRecipient.role === RecipientRole.VIEWER)
|
||||
(updatedRecipient.role === RecipientRole.CC || updatedRecipient.role === RecipientRole.VIEWER)
|
||||
) {
|
||||
await tx.field.deleteMany({
|
||||
where: {
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentDistributionMethod, RecipientRole, SendStatus, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { resolveNextReminderAt, ZEnvelopeReminderSettings } from '../../constants/envelope-reminder';
|
||||
|
||||
/**
|
||||
* Compute and store `nextReminderAt` for a single recipient.
|
||||
*
|
||||
* Call this after:
|
||||
* - Sending the signing email (sentAt is set)
|
||||
* - Sending a reminder (lastReminderSentAt is updated)
|
||||
*
|
||||
* If `reminderSettings` is provided it's used directly, avoiding a query.
|
||||
* Otherwise it's read from the envelope's documentMeta (already resolved
|
||||
* from the org/team cascade at envelope creation time).
|
||||
*/
|
||||
export const updateRecipientNextReminder = async (options: {
|
||||
recipientId: number;
|
||||
envelopeId: string;
|
||||
sentAt: Date;
|
||||
lastReminderSentAt: Date | null;
|
||||
reminderSettings?: ReturnType<typeof ZEnvelopeReminderSettings.parse> | null;
|
||||
}) => {
|
||||
const { recipientId, envelopeId, sentAt, lastReminderSentAt } = options;
|
||||
|
||||
let settings = options.reminderSettings;
|
||||
|
||||
if (settings === undefined) {
|
||||
const envelope = await prisma.envelope.findFirst({
|
||||
where: { id: envelopeId },
|
||||
select: { documentMeta: { select: { reminderSettings: true } } },
|
||||
});
|
||||
|
||||
settings = envelope?.documentMeta?.reminderSettings
|
||||
? ZEnvelopeReminderSettings.parse(envelope.documentMeta.reminderSettings)
|
||||
: null;
|
||||
}
|
||||
|
||||
const nextReminderAt = resolveNextReminderAt({
|
||||
config: settings,
|
||||
sentAt,
|
||||
lastReminderSentAt,
|
||||
});
|
||||
|
||||
await prisma.recipient.update({
|
||||
where: { id: recipientId },
|
||||
data: { nextReminderAt },
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Recompute `nextReminderAt` for all active (unsigned, sent) recipients
|
||||
* of a given envelope. Call when document-level reminder settings change.
|
||||
*/
|
||||
export const recomputeNextReminderForEnvelope = async (envelopeId: string) => {
|
||||
const envelope = await prisma.envelope.findFirst({
|
||||
where: { id: envelopeId },
|
||||
select: {
|
||||
documentMeta: {
|
||||
select: { reminderSettings: true, distributionMethod: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// No reminders for manually distributed documents.
|
||||
const isEmailDistribution = envelope?.documentMeta?.distributionMethod !== DocumentDistributionMethod.NONE;
|
||||
|
||||
const settings =
|
||||
isEmailDistribution && envelope?.documentMeta?.reminderSettings
|
||||
? ZEnvelopeReminderSettings.parse(envelope.documentMeta.reminderSettings)
|
||||
: null;
|
||||
|
||||
const now = new Date();
|
||||
|
||||
const recipients = await prisma.recipient.findMany({
|
||||
where: {
|
||||
envelopeId,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
sentAt: { not: null },
|
||||
role: { not: RecipientRole.CC },
|
||||
// Don't reschedule reminders for recipients whose deadline has passed.
|
||||
OR: [{ expiresAt: null }, { expiresAt: { gt: now } }],
|
||||
},
|
||||
select: { id: true, sentAt: true, lastReminderSentAt: true },
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
recipients.map(async (recipient) => {
|
||||
if (!recipient.sentAt) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextReminderAt = resolveNextReminderAt({
|
||||
config: settings,
|
||||
sentAt: recipient.sentAt,
|
||||
lastReminderSentAt: recipient.lastReminderSentAt,
|
||||
});
|
||||
|
||||
await prisma.recipient.update({
|
||||
where: { id: recipient.id },
|
||||
data: { nextReminderAt },
|
||||
});
|
||||
}),
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user