fix: merge conflicts

This commit is contained in:
Ephraim Atta-Duncan
2025-10-20 14:54:42 +00:00
448 changed files with 33524 additions and 9229 deletions

View File

@ -1,6 +1,7 @@
import type { DocumentDistributionMethod, DocumentSigningOrder } from '@prisma/client';
import {
DocumentSource,
EnvelopeType,
type Field,
FolderType,
type Recipient,
@ -37,9 +38,11 @@ import {
} from '../../types/field-meta';
import {
ZWebhookDocumentSchema,
mapDocumentToWebhookDocumentPayload,
mapEnvelopeToWebhookDocumentPayload,
} from '../../types/webhook-payload';
import type { ApiRequestMetadata } from '../../universal/extract-request-metadata';
import { getFileServerSide } from '../../universal/upload/get-file.server';
import { putPdfFileServerSide } from '../../universal/upload/put-file.server';
import { extractDerivedDocumentMeta } from '../../utils/document';
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
import {
@ -47,8 +50,12 @@ import {
createRecipientAuthOptions,
extractDocumentAuthMethods,
} from '../../utils/document-auth';
import type { EnvelopeIdOptions } from '../../utils/envelope';
import { mapSecondaryIdToTemplateId } from '../../utils/envelope';
import { calculateRecipientExpiry } from '../../utils/expiry';
import { buildTeamWhereQuery } from '../../utils/teams';
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
import { incrementDocumentId } from '../envelope/increment-id';
import { getTeamSettings } from '../team/get-team-settings';
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
@ -61,7 +68,7 @@ type FinalRecipient = Pick<
};
export type CreateDocumentFromTemplateOptions = {
templateId: number;
id: EnvelopeIdOptions;
externalId?: string | null;
userId: number;
teamId: number;
@ -73,7 +80,17 @@ export type CreateDocumentFromTemplateOptions = {
}[];
folderId?: string;
prefillFields?: TFieldMetaPrefillFieldsSchema[];
customDocumentDataId?: string;
customDocumentData?: {
documentDataId: string;
/**
* The envelope item ID which will be updated to use the custom document data.
*
* If undefined, will use the first envelope item. This is done for backwards compatibility reasons.
*/
envelopeItemId?: string;
}[];
/**
* Values that will override the predefined values in the template.
@ -271,30 +288,38 @@ const getUpdatedFieldMeta = (field: Field, prefillField?: TFieldMetaPrefillField
};
export const createDocumentFromTemplate = async ({
templateId,
id,
externalId,
userId,
teamId,
recipients,
customDocumentDataId,
customDocumentData = [],
override,
requestMetadata,
folderId,
prefillFields,
}: CreateDocumentFromTemplateOptions) => {
const template = await prisma.template.findUnique({
where: {
id: templateId,
team: buildTeamWhereQuery({ teamId, userId }),
},
const { envelopeWhereInput } = await getEnvelopeWhereInput({
id,
type: EnvelopeType.TEMPLATE,
userId,
teamId,
});
const template = await prisma.envelope.findUnique({
where: envelopeWhereInput,
include: {
recipients: {
include: {
fields: true,
},
},
templateDocumentData: true,
templateMeta: true,
envelopeItems: {
include: {
documentData: true,
},
},
documentMeta: true,
},
});
@ -320,6 +345,15 @@ export const createDocumentFromTemplate = async ({
}
}
const legacyTemplateId = mapSecondaryIdToTemplateId(template.secondaryId);
const finalEnvelopeTitle = override?.title || template.title;
if (template.envelopeItems.length < 1) {
throw new AppError(AppErrorCode.INVALID_BODY, {
message: 'Template must have at least 1 envelope item',
});
}
const settings = await getTeamSettings({
userId,
teamId,
@ -357,87 +391,130 @@ export const createDocumentFromTemplate = async ({
};
});
let parentDocumentData = template.templateDocumentData;
const firstEnvelopeItemId = template.envelopeItems[0].id;
if (customDocumentDataId) {
const customDocumentData = await prisma.documentData.findFirst({
where: {
id: customDocumentDataId,
},
});
// Key = original envelope item ID
// Value = duplicated envelope item ID.
const oldEnvelopeItemToNewEnvelopeItemIdMap: Record<string, string> = {};
if (!customDocumentData) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Custom document data not found',
// Duplicate the envelope item data.
// Note: This is duplicated in createDocumentFromDirectTemplate
const envelopeItemsToCreate = await Promise.all(
template.envelopeItems.map(async (item, i) => {
let documentDataIdToDuplicate = item.documentDataId;
const foundCustomDocumentData = customDocumentData.find(
(customDocumentDataItem) =>
customDocumentDataItem.envelopeItemId || firstEnvelopeItemId === item.id,
);
if (foundCustomDocumentData) {
documentDataIdToDuplicate = foundCustomDocumentData.documentDataId;
}
const documentDataToDuplicate = await prisma.documentData.findFirst({
where: {
id: documentDataIdToDuplicate,
},
});
}
parentDocumentData = customDocumentData;
}
if (!documentDataToDuplicate) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Document data not found',
});
}
const documentData = await prisma.documentData.create({
data: {
type: parentDocumentData.type,
data: parentDocumentData.data,
initialData: parentDocumentData.initialData,
},
const buffer = await getFileServerSide(documentDataToDuplicate);
const titleToUse = item.title || finalEnvelopeTitle;
const duplicatedFile = await putPdfFileServerSide({
name: titleToUse,
type: 'application/pdf',
arrayBuffer: async () => Promise.resolve(buffer),
});
const newDocumentData = await prisma.documentData.create({
data: {
type: duplicatedFile.type,
data: duplicatedFile.data,
initialData: duplicatedFile.initialData,
},
});
const newEnvelopeItemId = prefixedId('envelope_item');
oldEnvelopeItemToNewEnvelopeItemIdMap[item.id] = newEnvelopeItemId;
return {
id: newEnvelopeItemId,
title: titleToUse.endsWith('.pdf') ? titleToUse.slice(0, -4) : titleToUse,
documentDataId: newDocumentData.id,
order: item.order !== undefined ? item.order : i + 1,
};
}),
);
const incrementedDocumentId = await incrementDocumentId();
const documentMeta = await prisma.documentMeta.create({
data: extractDerivedDocumentMeta(settings, {
subject: override?.subject || template.documentMeta?.subject,
message: override?.message || template.documentMeta?.message,
timezone: override?.timezone || template.documentMeta?.timezone,
dateFormat: override?.dateFormat || template.documentMeta?.dateFormat,
redirectUrl: override?.redirectUrl || template.documentMeta?.redirectUrl,
distributionMethod: override?.distributionMethod || template.documentMeta?.distributionMethod,
emailSettings: override?.emailSettings || template.documentMeta?.emailSettings,
signingOrder: override?.signingOrder || template.documentMeta?.signingOrder,
language: override?.language || template.documentMeta?.language || settings.documentLanguage,
typedSignatureEnabled:
override?.typedSignatureEnabled ?? template.documentMeta?.typedSignatureEnabled,
uploadSignatureEnabled:
override?.uploadSignatureEnabled ?? template.documentMeta?.uploadSignatureEnabled,
drawSignatureEnabled:
override?.drawSignatureEnabled ?? template.documentMeta?.drawSignatureEnabled,
allowDictateNextSigner:
override?.allowDictateNextSigner ?? template.documentMeta?.allowDictateNextSigner,
}),
});
return await prisma.$transaction(async (tx) => {
const document = await tx.document.create({
const envelope = await tx.envelope.create({
data: {
id: prefixedId('envelope'),
secondaryId: incrementedDocumentId.formattedDocumentId,
type: EnvelopeType.DOCUMENT,
internalVersion: template.internalVersion,
qrToken: prefixedId('qr'),
source: DocumentSource.TEMPLATE,
externalId: externalId || template.externalId,
templateId: template.id,
templateId: legacyTemplateId, // The template this envelope was created from.
userId,
folderId,
teamId: template.teamId,
title: override?.title || template.title,
documentDataId: documentData.id,
title: finalEnvelopeTitle,
envelopeItems: {
createMany: {
data: envelopeItemsToCreate,
},
},
authOptions: createDocumentAuthOptions({
globalAccessAuth: templateAuthOptions.globalAccessAuth,
globalActionAuth: templateAuthOptions.globalActionAuth,
}),
visibility: template.visibility || settings.documentVisibility,
useLegacyFieldInsertion: template.useLegacyFieldInsertion ?? false,
documentMeta: {
create: extractDerivedDocumentMeta(settings, {
subject: override?.subject || template.templateMeta?.subject,
message: override?.message || template.templateMeta?.message,
timezone: override?.timezone || template.templateMeta?.timezone,
password: override?.password || template.templateMeta?.password,
dateFormat: override?.dateFormat || template.templateMeta?.dateFormat,
redirectUrl: override?.redirectUrl || template.templateMeta?.redirectUrl,
distributionMethod:
override?.distributionMethod || template.templateMeta?.distributionMethod,
emailSettings: override?.emailSettings || template.templateMeta?.emailSettings,
signingOrder: override?.signingOrder || template.templateMeta?.signingOrder,
language:
override?.language || template.templateMeta?.language || settings.documentLanguage,
typedSignatureEnabled:
override?.typedSignatureEnabled ?? template.templateMeta?.typedSignatureEnabled,
uploadSignatureEnabled:
override?.uploadSignatureEnabled ?? template.templateMeta?.uploadSignatureEnabled,
drawSignatureEnabled:
override?.drawSignatureEnabled ?? template.templateMeta?.drawSignatureEnabled,
allowDictateNextSigner:
override?.allowDictateNextSigner ?? template.templateMeta?.allowDictateNextSigner,
defaultExpiryAmount:
override?.expiryAmount ?? template.templateMeta?.defaultExpiryAmount,
defaultExpiryUnit: override?.expiryUnit ?? template.templateMeta?.defaultExpiryUnit,
}),
},
documentMetaId: documentMeta.id,
recipients: {
createMany: {
data: finalRecipients.map((recipient) => {
const authOptions = ZRecipientAuthOptionsSchema.parse(recipient?.authOptions);
// Calculate expiry date based on template defaults
const expiryAmount =
override?.expiryAmount ?? template.templateMeta?.defaultExpiryAmount ?? null;
const expiryUnit =
override?.expiryUnit ?? template.templateMeta?.defaultExpiryUnit ?? null;
// Calculate expiry date based on override
// Note: Templates no longer have default expiry settings (TemplateMeta removed)
const expiryAmount = override?.expiryAmount ?? null;
const expiryUnit = override?.expiryUnit ?? null;
const recipientExpiryDate = calculateRecipientExpiry(
expiryAmount,
expiryUnit,
@ -472,11 +549,15 @@ export const createDocumentFromTemplate = async ({
id: 'asc',
},
},
documentData: true,
envelopeItems: {
select: {
id: true,
},
},
},
});
let fieldsToCreate: Omit<Field, 'id' | 'secondaryId' | 'templateId'>[] = [];
let fieldsToCreate: Omit<Field, 'id' | 'secondaryId'>[] = [];
// Get all template field IDs first so we can validate later
const allTemplateFieldIds = finalRecipients.flatMap((recipient) =>
@ -520,7 +601,7 @@ export const createDocumentFromTemplate = async ({
}
Object.values(finalRecipients).forEach(({ token, fields }) => {
const recipient = document.recipients.find((recipient) => recipient.token === token);
const recipient = envelope.recipients.find((recipient) => recipient.token === token);
if (!recipient) {
throw new Error('Recipient not found.');
@ -531,7 +612,8 @@ export const createDocumentFromTemplate = async ({
const prefillField = prefillFields?.find((value) => value.id === field.id);
const payload = {
documentId: document.id,
envelopeItemId: oldEnvelopeItemToNewEnvelopeItemIdMap[field.envelopeItemId],
envelopeId: envelope.id,
recipientId: recipient.id,
type: field.type,
page: field.page,
@ -562,7 +644,7 @@ export const createDocumentFromTemplate = async ({
}
payload.customText = DateTime.fromJSDate(date).toFormat(
template.templateMeta?.dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT,
template.documentMeta?.dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT,
);
payload.inserted = true;
@ -587,21 +669,21 @@ export const createDocumentFromTemplate = async ({
await tx.documentAuditLog.create({
data: createDocumentAuditLogData({
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_CREATED,
documentId: document.id,
envelopeId: envelope.id,
metadata: requestMetadata,
data: {
title: document.title,
title: envelope.title,
source: {
type: DocumentSource.TEMPLATE,
templateId: template.id,
templateId: legacyTemplateId,
},
},
}),
});
const createdDocument = await tx.document.findFirst({
const createdEnvelope = await tx.envelope.findFirst({
where: {
id: document.id,
id: envelope.id,
},
include: {
documentMeta: true,
@ -609,17 +691,17 @@ export const createDocumentFromTemplate = async ({
},
});
if (!createdDocument) {
if (!createdEnvelope) {
throw new Error('Document not found');
}
await triggerWebhook({
event: WebhookTriggerEvents.DOCUMENT_CREATED,
data: ZWebhookDocumentSchema.parse(mapDocumentToWebhookDocumentPayload(createdDocument)),
data: ZWebhookDocumentSchema.parse(mapEnvelopeToWebhookDocumentPayload(createdEnvelope)),
userId,
teamId,
});
return document;
return envelope;
});
};