mirror of
https://github.com/documenso/documenso
synced 2026-04-21 13:27:18 +00:00
fix: dont flatten forms for templates (#2386)
Templates shouldn't have their form flattened until they're converted to a document.
This commit is contained in:
parent
34f512bd55
commit
c976e747e3
14 changed files with 918 additions and 71 deletions
BIN
assets/form-fields-test.pdf
Normal file
BIN
assets/form-fields-test.pdf
Normal file
Binary file not shown.
|
|
@ -41,7 +41,7 @@ import {
|
|||
ZTextFieldMeta,
|
||||
} from '@documenso/lib/types/field-meta';
|
||||
import { getFileServerSide } from '@documenso/lib/universal/upload/get-file.server';
|
||||
import { putPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
||||
import { putNormalizedPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
||||
import {
|
||||
getPresignGetUrl,
|
||||
getPresignPostUrl,
|
||||
|
|
@ -822,7 +822,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
|||
formValues: body.formValues,
|
||||
});
|
||||
|
||||
const newDocumentData = await putPdfFileServerSide({
|
||||
const newDocumentData = await putNormalizedPdfFileServerSide({
|
||||
name: fileName,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(prefilled),
|
||||
|
|
@ -911,61 +911,13 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
|||
title: body.title,
|
||||
...body.meta,
|
||||
},
|
||||
formValues: body.formValues,
|
||||
requestMetadata: metadata,
|
||||
});
|
||||
} catch (err) {
|
||||
return AppError.toRestAPIError(err);
|
||||
}
|
||||
|
||||
if (envelope.envelopeItems.length !== 1) {
|
||||
throw new Error('API V1 does not support envelopes');
|
||||
}
|
||||
|
||||
const firstEnvelopeDocumentData = await prisma.envelopeItem.findFirstOrThrow({
|
||||
where: {
|
||||
envelopeId: envelope.id,
|
||||
},
|
||||
include: {
|
||||
documentData: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (body.formValues) {
|
||||
const fileName = envelope.title.endsWith('.pdf') ? envelope.title : `${envelope.title}.pdf`;
|
||||
|
||||
const pdf = await getFileServerSide(firstEnvelopeDocumentData.documentData);
|
||||
|
||||
const prefilled = await insertFormValuesInPdf({
|
||||
pdf: Buffer.from(pdf),
|
||||
formValues: body.formValues,
|
||||
});
|
||||
|
||||
const newDocumentData = await putPdfFileServerSide({
|
||||
name: fileName,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(prefilled),
|
||||
});
|
||||
|
||||
await prisma.envelope.update({
|
||||
where: {
|
||||
id: envelope.id,
|
||||
},
|
||||
data: {
|
||||
formValues: body.formValues,
|
||||
envelopeItems: {
|
||||
update: {
|
||||
where: {
|
||||
id: firstEnvelopeDocumentData.id,
|
||||
},
|
||||
data: {
|
||||
documentDataId: newDocumentData.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (body.authOptions) {
|
||||
await prisma.envelope.update({
|
||||
where: {
|
||||
|
|
|
|||
848
packages/app-tests/e2e/scenarios/form-flattening.spec.ts
Normal file
848
packages/app-tests/e2e/scenarios/form-flattening.spec.ts
Normal file
|
|
@ -0,0 +1,848 @@
|
|||
import { PDFDocument } from '@cantoo/pdf-lib';
|
||||
import { expect, test } from '@playwright/test';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
|
||||
import { getFileServerSide } from '@documenso/lib/universal/upload/get-file.server';
|
||||
import { mapSecondaryIdToTemplateId } from '@documenso/lib/utils/envelope';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType, RecipientRole } from '@documenso/prisma/client';
|
||||
import { seedUser } from '@documenso/prisma/seed/users';
|
||||
import type {
|
||||
TCreateEnvelopePayload,
|
||||
TCreateEnvelopeResponse,
|
||||
} from '@documenso/trpc/server/envelope-router/create-envelope.types';
|
||||
|
||||
const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
|
||||
const baseUrl = `${WEBAPP_BASE_URL}/api/v2-beta`;
|
||||
|
||||
// Form field names in the test PDF
|
||||
const FORM_FIELDS = {
|
||||
TEXT_FIELD: 'test_text_field',
|
||||
COMPANY_NAME: 'company_name',
|
||||
CHECKBOX: 'accept_terms',
|
||||
DROPDOWN: 'country',
|
||||
} as const;
|
||||
|
||||
// Test values to insert into form fields
|
||||
const TEST_FORM_VALUES = {
|
||||
[FORM_FIELDS.TEXT_FIELD]: 'Hello World',
|
||||
[FORM_FIELDS.COMPANY_NAME]: 'Documenso Inc.',
|
||||
[FORM_FIELDS.CHECKBOX]: true,
|
||||
[FORM_FIELDS.DROPDOWN]: 'Germany',
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to check if a PDF has interactive form fields.
|
||||
* Returns true if the PDF has form fields, false if they've been flattened.
|
||||
*/
|
||||
async function pdfHasFormFields(pdfBuffer: Uint8Array): Promise<boolean> {
|
||||
const pdfDoc = await PDFDocument.load(pdfBuffer);
|
||||
|
||||
const form = pdfDoc.getForm();
|
||||
const fields = form.getFields();
|
||||
|
||||
return fields.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get form field names from a PDF.
|
||||
*/
|
||||
async function getPdfFormFieldNames(pdfBuffer: Uint8Array): Promise<string[]> {
|
||||
const pdfDoc = await PDFDocument.load(pdfBuffer);
|
||||
|
||||
const form = pdfDoc.getForm();
|
||||
const fields = form.getFields();
|
||||
|
||||
return fields.map((field) => field.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get the value of a text field in a PDF.
|
||||
*/
|
||||
async function getPdfTextFieldValue(
|
||||
pdfBuffer: Uint8Array,
|
||||
fieldName: string,
|
||||
): Promise<string | undefined> {
|
||||
const pdfDoc = await PDFDocument.load(pdfBuffer);
|
||||
|
||||
const form = pdfDoc.getForm();
|
||||
|
||||
try {
|
||||
const textField = form.getTextField(fieldName);
|
||||
|
||||
return textField.getText() ?? '';
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
test.describe.configure({
|
||||
mode: 'parallel',
|
||||
});
|
||||
|
||||
test.describe('Form Flattening', () => {
|
||||
const formFieldsPdf = fs.readFileSync(
|
||||
path.join(__dirname, '../../../../assets/form-fields-test.pdf'),
|
||||
);
|
||||
|
||||
test.describe('Envelope Creation (DOCUMENT type)', () => {
|
||||
test('should flatten form fields when creating a DOCUMENT envelope with formValues', async ({
|
||||
request,
|
||||
}) => {
|
||||
const { user, team } = await seedUser();
|
||||
const { token } = await createApiToken({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'test',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
const payload: TCreateEnvelopePayload = {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title: 'Document with Form Values',
|
||||
formValues: TEST_FORM_VALUES,
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('payload', JSON.stringify(payload));
|
||||
formData.append(
|
||||
'files',
|
||||
new File([formFieldsPdf], 'form-fields-test.pdf', { type: 'application/pdf' }),
|
||||
);
|
||||
|
||||
const res = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: formData,
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const response = (await res.json()) as TCreateEnvelopeResponse;
|
||||
|
||||
// Verify the envelope was created with the correct formValues
|
||||
const envelope = await prisma.envelope.findUniqueOrThrow({
|
||||
where: { id: response.id },
|
||||
include: {
|
||||
envelopeItems: {
|
||||
include: { documentData: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(envelope.formValues).toEqual(TEST_FORM_VALUES);
|
||||
expect(envelope.type).toBe(EnvelopeType.DOCUMENT);
|
||||
|
||||
// Get the PDF and verify form fields are flattened
|
||||
const documentData = envelope.envelopeItems[0].documentData;
|
||||
const pdfBuffer = await getFileServerSide(documentData);
|
||||
|
||||
const hasFormFields = await pdfHasFormFields(pdfBuffer);
|
||||
|
||||
expect(hasFormFields).toBe(false);
|
||||
});
|
||||
|
||||
test('should flatten form fields when creating a DOCUMENT envelope without formValues', async ({
|
||||
request,
|
||||
}) => {
|
||||
const { user, team } = await seedUser();
|
||||
const { token } = await createApiToken({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'test',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
const payload: TCreateEnvelopePayload = {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title: 'Document without Form Values',
|
||||
// No formValues - but form should still be flattened for DOCUMENT type
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('payload', JSON.stringify(payload));
|
||||
formData.append(
|
||||
'files',
|
||||
new File([formFieldsPdf], 'form-fields-test.pdf', { type: 'application/pdf' }),
|
||||
);
|
||||
|
||||
const res = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: formData,
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const response = (await res.json()) as TCreateEnvelopeResponse;
|
||||
|
||||
const envelope = await prisma.envelope.findUniqueOrThrow({
|
||||
where: { id: response.id },
|
||||
include: {
|
||||
envelopeItems: {
|
||||
include: { documentData: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Get the PDF and verify form fields are flattened
|
||||
const documentData = envelope.envelopeItems[0].documentData;
|
||||
const pdfBuffer = await getFileServerSide(documentData);
|
||||
|
||||
const hasFormFields = await pdfHasFormFields(pdfBuffer);
|
||||
|
||||
expect(hasFormFields).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Template Creation (TEMPLATE type)', () => {
|
||||
test('should NOT flatten form fields when creating a TEMPLATE envelope', async ({
|
||||
request,
|
||||
}) => {
|
||||
const { user, team } = await seedUser();
|
||||
const { token } = await createApiToken({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'test',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
const payload: TCreateEnvelopePayload = {
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
title: 'Template with Form Fields',
|
||||
// Note: formValues can be set but form should NOT be flattened for templates
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('payload', JSON.stringify(payload));
|
||||
formData.append(
|
||||
'files',
|
||||
new File([formFieldsPdf], 'form-fields-test.pdf', { type: 'application/pdf' }),
|
||||
);
|
||||
|
||||
const res = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: formData,
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const response = (await res.json()) as TCreateEnvelopeResponse;
|
||||
|
||||
const envelope = await prisma.envelope.findUniqueOrThrow({
|
||||
where: { id: response.id },
|
||||
include: {
|
||||
envelopeItems: {
|
||||
include: { documentData: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(envelope.type).toBe(EnvelopeType.TEMPLATE);
|
||||
|
||||
// Get the PDF and verify form fields are NOT flattened
|
||||
const documentData = envelope.envelopeItems[0].documentData;
|
||||
const pdfBuffer = await getFileServerSide(documentData);
|
||||
|
||||
const hasFormFields = await pdfHasFormFields(pdfBuffer);
|
||||
|
||||
expect(hasFormFields).toBe(true);
|
||||
|
||||
// Verify the specific form fields still exist
|
||||
const fieldNames = await getPdfFormFieldNames(pdfBuffer);
|
||||
|
||||
expect(fieldNames).toContain(FORM_FIELDS.TEXT_FIELD);
|
||||
expect(fieldNames).toContain(FORM_FIELDS.COMPANY_NAME);
|
||||
expect(fieldNames).toContain(FORM_FIELDS.CHECKBOX);
|
||||
expect(fieldNames).toContain(FORM_FIELDS.DROPDOWN);
|
||||
});
|
||||
|
||||
test('should preserve form fields in template even when formValues are provided', async ({
|
||||
request,
|
||||
}) => {
|
||||
const { user, team } = await seedUser();
|
||||
const { token } = await createApiToken({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'test',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
const payload: TCreateEnvelopePayload = {
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
title: 'Template with Form Values',
|
||||
formValues: TEST_FORM_VALUES,
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('payload', JSON.stringify(payload));
|
||||
formData.append(
|
||||
'files',
|
||||
new File([formFieldsPdf], 'form-fields-test.pdf', { type: 'application/pdf' }),
|
||||
);
|
||||
|
||||
const res = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: formData,
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const response = (await res.json()) as TCreateEnvelopeResponse;
|
||||
|
||||
const envelope = await prisma.envelope.findUniqueOrThrow({
|
||||
where: { id: response.id },
|
||||
include: {
|
||||
envelopeItems: {
|
||||
include: { documentData: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// formValues should be stored in the database
|
||||
expect(envelope.formValues).toEqual(TEST_FORM_VALUES);
|
||||
expect(envelope.type).toBe(EnvelopeType.TEMPLATE);
|
||||
|
||||
// But the PDF should still have interactive form fields
|
||||
const documentData = envelope.envelopeItems[0].documentData;
|
||||
const pdfBuffer = await getFileServerSide(documentData);
|
||||
|
||||
const hasFormFields = await pdfHasFormFields(pdfBuffer);
|
||||
|
||||
expect(hasFormFields).toBe(true);
|
||||
expect(await getPdfTextFieldValue(pdfBuffer, FORM_FIELDS.TEXT_FIELD)).toBe(
|
||||
TEST_FORM_VALUES[FORM_FIELDS.TEXT_FIELD],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Document from Template', () => {
|
||||
test('should flatten form fields when creating document from template with formValues', async ({
|
||||
request,
|
||||
}) => {
|
||||
const { user, team } = await seedUser();
|
||||
const { token } = await createApiToken({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'test',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
// First, create a template via API
|
||||
const templatePayload: TCreateEnvelopePayload = {
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
title: 'Template for Document Creation',
|
||||
recipients: [
|
||||
{
|
||||
email: 'recipient@example.com',
|
||||
name: 'Test Recipient',
|
||||
role: RecipientRole.SIGNER,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const templateFormData = new FormData();
|
||||
templateFormData.append('payload', JSON.stringify(templatePayload));
|
||||
templateFormData.append(
|
||||
'files',
|
||||
new File([formFieldsPdf], 'form-fields-test.pdf', { type: 'application/pdf' }),
|
||||
);
|
||||
|
||||
const templateRes = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: templateFormData,
|
||||
});
|
||||
|
||||
expect(templateRes.ok()).toBeTruthy();
|
||||
const templateResponse = (await templateRes.json()) as TCreateEnvelopeResponse;
|
||||
|
||||
// Verify template has form fields
|
||||
const template = await prisma.envelope.findUniqueOrThrow({
|
||||
where: { id: templateResponse.id },
|
||||
include: {
|
||||
envelopeItems: { include: { documentData: true } },
|
||||
recipients: true,
|
||||
},
|
||||
});
|
||||
|
||||
const templatePdfBuffer = await getFileServerSide(template.envelopeItems[0].documentData);
|
||||
|
||||
expect(await pdfHasFormFields(templatePdfBuffer)).toBe(true);
|
||||
|
||||
// Now create a document from the template with formValues
|
||||
const useTemplateRes = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/template/use`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: {
|
||||
templateId: mapSecondaryIdToTemplateId(template.secondaryId),
|
||||
recipients: [
|
||||
{
|
||||
id: template.recipients[0].id,
|
||||
email: 'recipient@example.com',
|
||||
name: 'Test Recipient',
|
||||
},
|
||||
],
|
||||
formValues: TEST_FORM_VALUES,
|
||||
},
|
||||
});
|
||||
|
||||
expect(useTemplateRes.ok()).toBeTruthy();
|
||||
expect(useTemplateRes.status()).toBe(200);
|
||||
|
||||
const documentResponse = await useTemplateRes.json();
|
||||
|
||||
// Get the created document
|
||||
const document = await prisma.envelope.findFirstOrThrow({
|
||||
where: {
|
||||
id: documentResponse.envelopeId,
|
||||
},
|
||||
include: {
|
||||
envelopeItems: { include: { documentData: true } },
|
||||
},
|
||||
});
|
||||
|
||||
expect(document.type).toBe(EnvelopeType.DOCUMENT);
|
||||
|
||||
// Verify form fields are flattened in the created document
|
||||
const documentPdfBuffer = await getFileServerSide(document.envelopeItems[0].documentData);
|
||||
const hasFormFields = await pdfHasFormFields(documentPdfBuffer);
|
||||
|
||||
expect(hasFormFields).toBe(false);
|
||||
});
|
||||
|
||||
test('should flatten form fields when creating document from template without formValues', async ({
|
||||
request,
|
||||
}) => {
|
||||
const { user, team } = await seedUser();
|
||||
const { token } = await createApiToken({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'test',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
// Create a template
|
||||
const templatePayload: TCreateEnvelopePayload = {
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
title: 'Template without Form Values',
|
||||
recipients: [
|
||||
{
|
||||
email: 'recipient@example.com',
|
||||
name: 'Test Recipient',
|
||||
role: RecipientRole.SIGNER,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const templateFormData = new FormData();
|
||||
templateFormData.append('payload', JSON.stringify(templatePayload));
|
||||
templateFormData.append(
|
||||
'files',
|
||||
new File([formFieldsPdf], 'form-fields-test.pdf', { type: 'application/pdf' }),
|
||||
);
|
||||
|
||||
const templateRes = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: templateFormData,
|
||||
});
|
||||
|
||||
expect(templateRes.ok()).toBeTruthy();
|
||||
const templateResponse = (await templateRes.json()) as TCreateEnvelopeResponse;
|
||||
|
||||
const template = await prisma.envelope.findUniqueOrThrow({
|
||||
where: { id: templateResponse.id },
|
||||
include: {
|
||||
envelopeItems: { include: { documentData: true } },
|
||||
recipients: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Create document from template WITHOUT formValues
|
||||
const useTemplateRes = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/template/use`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: {
|
||||
templateId: mapSecondaryIdToTemplateId(template.secondaryId),
|
||||
recipients: [
|
||||
{
|
||||
id: template.recipients[0].id,
|
||||
email: 'recipient@example.com',
|
||||
name: 'Test Recipient',
|
||||
},
|
||||
],
|
||||
// No formValues provided
|
||||
},
|
||||
});
|
||||
|
||||
expect(useTemplateRes.ok()).toBeTruthy();
|
||||
const documentResponse = await useTemplateRes.json();
|
||||
|
||||
const document = await prisma.envelope.findFirstOrThrow({
|
||||
where: { id: documentResponse.envelopeId },
|
||||
include: {
|
||||
envelopeItems: { include: { documentData: true } },
|
||||
},
|
||||
});
|
||||
|
||||
expect(document.type).toBe(EnvelopeType.DOCUMENT);
|
||||
|
||||
// Form fields should still be flattened even without formValues
|
||||
const documentPdfBuffer = await getFileServerSide(document.envelopeItems[0].documentData);
|
||||
const hasFormFields = await pdfHasFormFields(documentPdfBuffer);
|
||||
|
||||
expect(hasFormFields).toBe(false);
|
||||
});
|
||||
|
||||
test('should use template formValues when creating document without override', async ({
|
||||
request,
|
||||
}) => {
|
||||
const { user, team } = await seedUser();
|
||||
const { token } = await createApiToken({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'test',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
// Create a template with formValues
|
||||
const templatePayload: TCreateEnvelopePayload = {
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
title: 'Template with Default Form Values',
|
||||
formValues: {
|
||||
[FORM_FIELDS.TEXT_FIELD]: 'Default Value',
|
||||
[FORM_FIELDS.COMPANY_NAME]: 'Default Company',
|
||||
},
|
||||
recipients: [
|
||||
{
|
||||
email: 'recipient@example.com',
|
||||
name: 'Test Recipient',
|
||||
role: RecipientRole.SIGNER,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const templateFormData = new FormData();
|
||||
templateFormData.append('payload', JSON.stringify(templatePayload));
|
||||
templateFormData.append(
|
||||
'files',
|
||||
new File([formFieldsPdf], 'form-fields-test.pdf', { type: 'application/pdf' }),
|
||||
);
|
||||
|
||||
const templateRes = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: templateFormData,
|
||||
});
|
||||
|
||||
expect(templateRes.ok()).toBeTruthy();
|
||||
const templateResponse = (await templateRes.json()) as TCreateEnvelopeResponse;
|
||||
|
||||
const template = await prisma.envelope.findUniqueOrThrow({
|
||||
where: { id: templateResponse.id },
|
||||
include: {
|
||||
envelopeItems: { include: { documentData: true } },
|
||||
recipients: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Verify template stored the formValues
|
||||
expect(template.formValues).toEqual({
|
||||
[FORM_FIELDS.TEXT_FIELD]: 'Default Value',
|
||||
[FORM_FIELDS.COMPANY_NAME]: 'Default Company',
|
||||
});
|
||||
|
||||
// Create document from template without providing new formValues
|
||||
// The template's formValues should be used
|
||||
const useTemplateRes = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/template/use`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: {
|
||||
templateId: mapSecondaryIdToTemplateId(template.secondaryId),
|
||||
recipients: [
|
||||
{
|
||||
id: template.recipients[0].id,
|
||||
email: 'recipient@example.com',
|
||||
name: 'Test Recipient',
|
||||
},
|
||||
],
|
||||
// No formValues - should inherit from template
|
||||
},
|
||||
});
|
||||
|
||||
expect(useTemplateRes.ok()).toBeTruthy();
|
||||
const documentResponse = await useTemplateRes.json();
|
||||
|
||||
const document = await prisma.envelope.findFirstOrThrow({
|
||||
where: { id: documentResponse.envelopeId },
|
||||
include: {
|
||||
envelopeItems: { include: { documentData: true } },
|
||||
},
|
||||
});
|
||||
|
||||
// Form fields should be flattened
|
||||
const documentPdfBuffer = await getFileServerSide(document.envelopeItems[0].documentData);
|
||||
|
||||
expect(await pdfHasFormFields(documentPdfBuffer)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Form Values Verification', () => {
|
||||
test('should correctly insert form values into PDF before flattening', async ({ request }) => {
|
||||
const { user, team } = await seedUser();
|
||||
const { token } = await createApiToken({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'test',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
// Create a template first (form fields preserved)
|
||||
const templatePayload: TCreateEnvelopePayload = {
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
title: 'Template for Value Verification',
|
||||
recipients: [
|
||||
{
|
||||
email: 'recipient@example.com',
|
||||
name: 'Test Recipient',
|
||||
role: RecipientRole.SIGNER,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const templateFormData = new FormData();
|
||||
|
||||
templateFormData.append('payload', JSON.stringify(templatePayload));
|
||||
templateFormData.append(
|
||||
'files',
|
||||
new File([formFieldsPdf], 'form-fields-test.pdf', { type: 'application/pdf' }),
|
||||
);
|
||||
|
||||
const templateRes = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: templateFormData,
|
||||
});
|
||||
|
||||
const templateResponse = (await templateRes.json()) as TCreateEnvelopeResponse;
|
||||
const template = await prisma.envelope.findUniqueOrThrow({
|
||||
where: { id: templateResponse.id },
|
||||
include: {
|
||||
envelopeItems: { include: { documentData: true } },
|
||||
recipients: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Verify template PDF still has form fields
|
||||
const templatePdfBuffer = await getFileServerSide(template.envelopeItems[0].documentData);
|
||||
expect(await pdfHasFormFields(templatePdfBuffer)).toBe(true);
|
||||
|
||||
// Verify we can read a text field value (should be empty initially)
|
||||
const initialValue = await getPdfTextFieldValue(templatePdfBuffer, FORM_FIELDS.TEXT_FIELD);
|
||||
expect(initialValue).toBe('');
|
||||
|
||||
// Now create a document with form values
|
||||
const testValues = {
|
||||
[FORM_FIELDS.TEXT_FIELD]: 'Inserted Text Value',
|
||||
[FORM_FIELDS.COMPANY_NAME]: 'Test Company Name',
|
||||
};
|
||||
|
||||
const useTemplateRes = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/template/use`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: {
|
||||
templateId: mapSecondaryIdToTemplateId(template.secondaryId),
|
||||
recipients: [
|
||||
{
|
||||
id: template.recipients[0].id,
|
||||
email: 'recipient@example.com',
|
||||
name: 'Test Recipient',
|
||||
},
|
||||
],
|
||||
formValues: testValues,
|
||||
},
|
||||
});
|
||||
|
||||
expect(useTemplateRes.ok()).toBeTruthy();
|
||||
const documentResponse = await useTemplateRes.json();
|
||||
|
||||
const document = await prisma.envelope.findFirstOrThrow({
|
||||
where: { id: documentResponse.envelopeId },
|
||||
include: {
|
||||
envelopeItems: { include: { documentData: true } },
|
||||
},
|
||||
});
|
||||
|
||||
// The form should be flattened, so we can't read form fields
|
||||
const documentPdfBuffer = await getFileServerSide(document.envelopeItems[0].documentData);
|
||||
expect(await pdfHasFormFields(documentPdfBuffer)).toBe(false);
|
||||
|
||||
// The values should have been inserted before flattening
|
||||
// We can't verify the actual text content easily without visual inspection,
|
||||
// but we can verify the form fields are gone (flattened)
|
||||
const fieldNames = await getPdfFormFieldNames(documentPdfBuffer);
|
||||
expect(fieldNames.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Edge Cases', () => {
|
||||
test('should handle PDF without form fields gracefully', async ({ request }) => {
|
||||
const { user, team } = await seedUser();
|
||||
const { token } = await createApiToken({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'test',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
// Use a PDF without form fields
|
||||
const examplePdf = fs.readFileSync(path.join(__dirname, '../../../../assets/example.pdf'));
|
||||
|
||||
const payload: TCreateEnvelopePayload = {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title: 'Document with No Form Fields',
|
||||
formValues: {
|
||||
nonexistent_field: 'Some Value',
|
||||
},
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('payload', JSON.stringify(payload));
|
||||
formData.append('files', new File([examplePdf], 'example.pdf', { type: 'application/pdf' }));
|
||||
|
||||
const res = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: formData,
|
||||
});
|
||||
|
||||
// Should succeed even with formValues for non-existent fields
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const response = (await res.json()) as TCreateEnvelopeResponse;
|
||||
const envelope = await prisma.envelope.findUniqueOrThrow({
|
||||
where: { id: response.id },
|
||||
include: {
|
||||
envelopeItems: { include: { documentData: true } },
|
||||
},
|
||||
});
|
||||
|
||||
expect(envelope.formValues).toEqual({ nonexistent_field: 'Some Value' });
|
||||
});
|
||||
|
||||
test('should handle empty formValues object', async ({ request }) => {
|
||||
const { user, team } = await seedUser();
|
||||
const { token } = await createApiToken({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'test',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
const payload: TCreateEnvelopePayload = {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title: 'Document with Empty Form Values',
|
||||
formValues: {},
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('payload', JSON.stringify(payload));
|
||||
formData.append(
|
||||
'files',
|
||||
new File([formFieldsPdf], 'form-fields-test.pdf', { type: 'application/pdf' }),
|
||||
);
|
||||
|
||||
const res = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: formData,
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const response = (await res.json()) as TCreateEnvelopeResponse;
|
||||
const envelope = await prisma.envelope.findUniqueOrThrow({
|
||||
where: { id: response.id },
|
||||
include: {
|
||||
envelopeItems: { include: { documentData: true } },
|
||||
},
|
||||
});
|
||||
|
||||
// Form should still be flattened for DOCUMENT type
|
||||
const documentData = envelope.envelopeItems[0].documentData;
|
||||
const pdfBuffer = await getFileServerSide(documentData);
|
||||
|
||||
expect(await pdfHasFormFields(pdfBuffer)).toBe(false);
|
||||
});
|
||||
|
||||
test('should handle partial formValues (only some fields)', async ({ request }) => {
|
||||
const { user, team } = await seedUser();
|
||||
const { token } = await createApiToken({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'test',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
const payload: TCreateEnvelopePayload = {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title: 'Document with Partial Form Values',
|
||||
formValues: {
|
||||
[FORM_FIELDS.TEXT_FIELD]: 'Only this field',
|
||||
// Other fields not set
|
||||
},
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('payload', JSON.stringify(payload));
|
||||
formData.append(
|
||||
'files',
|
||||
new File([formFieldsPdf], 'form-fields-test.pdf', { type: 'application/pdf' }),
|
||||
);
|
||||
|
||||
const res = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: formData,
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const response = (await res.json()) as TCreateEnvelopeResponse;
|
||||
const envelope = await prisma.envelope.findUniqueOrThrow({
|
||||
where: { id: response.id },
|
||||
include: {
|
||||
envelopeItems: { include: { documentData: true } },
|
||||
},
|
||||
});
|
||||
|
||||
// Should store the partial formValues
|
||||
expect(envelope.formValues).toEqual({
|
||||
[FORM_FIELDS.TEXT_FIELD]: 'Only this field',
|
||||
});
|
||||
|
||||
// Form should still be flattened
|
||||
const documentData = envelope.envelopeItems[0].documentData;
|
||||
const pdfBuffer = await getFileServerSide(documentData);
|
||||
|
||||
expect(await pdfHasFormFields(pdfBuffer)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -505,7 +505,9 @@ test('[TEMPLATE]: should create a document from a template using template docume
|
|||
});
|
||||
|
||||
expect(document.title).toEqual('TEMPLATE_WITH_ORIGINAL_DOC');
|
||||
expect(firstDocumentData.data).toEqual(templateWithData.envelopeItems[0].documentData.data);
|
||||
expect(firstDocumentData.initialData).toEqual(
|
||||
templateWithData.envelopeItems[0].documentData.data,
|
||||
);
|
||||
expect(firstDocumentData.initialData).toEqual(
|
||||
templateWithData.envelopeItems[0].documentData.initialData,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import {
|
|||
mapEnvelopeToWebhookDocumentPayload,
|
||||
} from '../../types/webhook-payload';
|
||||
import { getFileServerSide } from '../../universal/upload/get-file.server';
|
||||
import { putPdfFileServerSide } from '../../universal/upload/put-file.server';
|
||||
import { putNormalizedPdfFileServerSide } from '../../universal/upload/put-file.server';
|
||||
import { isDocumentCompleted } from '../../utils/document';
|
||||
import { extractDocumentAuthMethods } from '../../utils/document-auth';
|
||||
import { type EnvelopeIdOptions, mapSecondaryIdToDocumentId } from '../../utils/envelope';
|
||||
|
|
@ -334,7 +334,7 @@ const injectFormValuesIntoDocument = async (
|
|||
fileName = `${envelope.title}.pdf`;
|
||||
}
|
||||
|
||||
const newDocumentData = await putPdfFileServerSide({
|
||||
const newDocumentData = await putNormalizedPdfFileServerSide({
|
||||
name: fileName,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(prefilled),
|
||||
|
|
|
|||
|
|
@ -185,7 +185,9 @@ export const createEnvelope = async ({
|
|||
|
||||
const buffer = await getFileServerSide(documentData);
|
||||
|
||||
const normalizedPdf = await makeNormalizedPdf(Buffer.from(buffer));
|
||||
const normalizedPdf = await makeNormalizedPdf(Buffer.from(buffer), {
|
||||
flattenForm: type !== EnvelopeType.TEMPLATE,
|
||||
});
|
||||
|
||||
const titleToUse = item.title || title;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import { AppError } from '../../errors/app-error';
|
|||
import { flattenAnnotations } from './flatten-annotations';
|
||||
import { flattenForm, removeOptionalContentGroups } from './flatten-form';
|
||||
|
||||
export const normalizePdf = async (pdf: Buffer) => {
|
||||
export const normalizePdf = async (pdf: Buffer, options: { flattenForm?: boolean } = {}) => {
|
||||
const shouldFlattenForm = options.flattenForm ?? true;
|
||||
|
||||
const pdfDoc = await PDFDocument.load(pdf).catch((e) => {
|
||||
console.error(`PDF normalization error: ${e.message}`);
|
||||
|
||||
|
|
@ -20,8 +22,11 @@ export const normalizePdf = async (pdf: Buffer) => {
|
|||
}
|
||||
|
||||
removeOptionalContentGroups(pdfDoc);
|
||||
await flattenForm(pdfDoc);
|
||||
flattenAnnotations(pdfDoc);
|
||||
|
||||
if (shouldFlattenForm) {
|
||||
await flattenForm(pdfDoc);
|
||||
flattenAnnotations(pdfDoc);
|
||||
}
|
||||
|
||||
return Buffer.from(await pdfDoc.save());
|
||||
};
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import { ZDefaultRecipientsSchema } from '../../types/default-recipients';
|
|||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||
import { ZRecipientAuthOptionsSchema } from '../../types/document-auth';
|
||||
import type { TDocumentEmailSettings } from '../../types/document-email';
|
||||
import type { TDocumentFormValues } from '../../types/document-form-values';
|
||||
import type {
|
||||
TCheckboxFieldMeta,
|
||||
TDropdownFieldMeta,
|
||||
|
|
@ -43,7 +44,7 @@ import {
|
|||
} 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 { putNormalizedPdfFileServerSide } from '../../universal/upload/put-file.server';
|
||||
import { extractDerivedDocumentMeta } from '../../utils/document';
|
||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||
import {
|
||||
|
|
@ -56,6 +57,7 @@ import { mapSecondaryIdToTemplateId } from '../../utils/envelope';
|
|||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
|
||||
import { incrementDocumentId } from '../envelope/increment-id';
|
||||
import { insertFormValuesInPdf } from '../pdf/insert-form-values-in-pdf';
|
||||
import { getTeamSettings } from '../team/get-team-settings';
|
||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||
|
||||
|
|
@ -118,6 +120,8 @@ export type CreateDocumentFromTemplateOptions = {
|
|||
uploadSignatureEnabled?: boolean;
|
||||
drawSignatureEnabled?: boolean;
|
||||
};
|
||||
|
||||
formValues?: TDocumentFormValues;
|
||||
requestMetadata: ApiRequestMetadata;
|
||||
};
|
||||
|
||||
|
|
@ -304,6 +308,7 @@ export const createDocumentFromTemplate = async ({
|
|||
folderId,
|
||||
prefillFields,
|
||||
attachments,
|
||||
formValues,
|
||||
}: CreateDocumentFromTemplateOptions) => {
|
||||
const { envelopeWhereInput } = await getEnvelopeWhereInput({
|
||||
id,
|
||||
|
|
@ -456,11 +461,19 @@ export const createDocumentFromTemplate = async ({
|
|||
});
|
||||
}
|
||||
|
||||
const buffer = await getFileServerSide(documentDataToDuplicate);
|
||||
let buffer = await getFileServerSide(documentDataToDuplicate);
|
||||
|
||||
const titleToUse = item.title || finalEnvelopeTitle;
|
||||
|
||||
const duplicatedFile = await putPdfFileServerSide({
|
||||
if (formValues) {
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
buffer = await insertFormValuesInPdf({
|
||||
pdf: Buffer.from(buffer),
|
||||
formValues,
|
||||
});
|
||||
}
|
||||
|
||||
const duplicatedFile = await putNormalizedPdfFileServerSide({
|
||||
name: titleToUse,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(buffer),
|
||||
|
|
@ -470,7 +483,7 @@ export const createDocumentFromTemplate = async ({
|
|||
data: {
|
||||
type: duplicatedFile.type,
|
||||
data: duplicatedFile.data,
|
||||
initialData: duplicatedFile.initialData,
|
||||
initialData: documentDataToDuplicate.data,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -47,10 +47,13 @@ export const putPdfFileServerSide = async (file: File) => {
|
|||
/**
|
||||
* Uploads a pdf file and normalizes it.
|
||||
*/
|
||||
export const putNormalizedPdfFileServerSide = async (file: File) => {
|
||||
export const putNormalizedPdfFileServerSide = async (
|
||||
file: File,
|
||||
options: { flattenForm?: boolean } = {},
|
||||
) => {
|
||||
const buffer = Buffer.from(await file.arrayBuffer());
|
||||
|
||||
const normalized = await normalizePdf(buffer);
|
||||
const normalized = await normalizePdf(buffer, options);
|
||||
|
||||
const fileName = file.name.endsWith('.pdf') ? file.name : `${file.name}.pdf`;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { createEnvelope } from '@documenso/lib/server-only/envelope/create-envelope';
|
||||
|
|
@ -80,11 +82,16 @@ export const createEnvelopeRoute = authenticatedProcedure
|
|||
});
|
||||
}
|
||||
|
||||
const { id: documentDataId } = await putNormalizedPdfFileServerSide({
|
||||
name: file.name,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(pdf),
|
||||
});
|
||||
const { id: documentDataId } = await putNormalizedPdfFileServerSide(
|
||||
{
|
||||
name: file.name,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(pdf),
|
||||
},
|
||||
{
|
||||
flattenForm: type !== EnvelopeType.TEMPLATE,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
title: file.name,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export const useEnvelopeRoute = authenticatedProcedure
|
|||
prefillFields,
|
||||
override,
|
||||
attachments,
|
||||
formValues,
|
||||
} = payload;
|
||||
|
||||
ctx.logger.info({
|
||||
|
|
@ -79,7 +80,10 @@ export const useEnvelopeRoute = authenticatedProcedure
|
|||
// Process uploaded files and create document data for them
|
||||
const uploadedFiles = await Promise.all(
|
||||
filesToUpload.map(async (file) => {
|
||||
const { id: documentDataId } = await putNormalizedPdfFileServerSide(file);
|
||||
// We disable flattening here since `createDocumentFromTemplate` will handle it.
|
||||
const { id: documentDataId } = await putNormalizedPdfFileServerSide(file, {
|
||||
flattenForm: false,
|
||||
});
|
||||
|
||||
return {
|
||||
name: file.name,
|
||||
|
|
@ -146,6 +150,7 @@ export const useEnvelopeRoute = authenticatedProcedure
|
|||
prefillFields,
|
||||
override,
|
||||
attachments,
|
||||
formValues,
|
||||
});
|
||||
|
||||
// Distribute document if requested
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { z } from 'zod';
|
|||
import { zfd } from 'zod-form-data';
|
||||
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { ZDocumentFormValuesSchema } from '@documenso/lib/types/document-form-values';
|
||||
import {
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaDistributionMethodSchema,
|
||||
|
|
@ -108,6 +109,8 @@ export const ZUseEnvelopePayloadSchema = z.object({
|
|||
}),
|
||||
)
|
||||
.optional(),
|
||||
|
||||
formValues: ZDocumentFormValuesSchema.optional(),
|
||||
});
|
||||
|
||||
export const ZUseEnvelopeRequestSchema = zodFormData({
|
||||
|
|
|
|||
|
|
@ -197,7 +197,9 @@ export const templateRouter = router({
|
|||
attachments,
|
||||
} = payload;
|
||||
|
||||
const { id: templateDocumentDataId } = await putNormalizedPdfFileServerSide(file);
|
||||
const { id: templateDocumentDataId } = await putNormalizedPdfFileServerSide(file, {
|
||||
flattenForm: false,
|
||||
});
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
|
|
@ -468,6 +470,7 @@ export const templateRouter = router({
|
|||
externalId,
|
||||
override,
|
||||
attachments,
|
||||
formValues,
|
||||
} = input;
|
||||
|
||||
ctx.logger.info({
|
||||
|
|
@ -507,6 +510,7 @@ export const templateRouter = router({
|
|||
externalId,
|
||||
override,
|
||||
attachments,
|
||||
formValues,
|
||||
});
|
||||
|
||||
if (distributeDocument) {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
ZDocumentActionAuthTypesSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { ZDocumentFormValuesSchema } from '@documenso/lib/types/document-form-values';
|
||||
import {
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaDistributionMethodSchema,
|
||||
|
|
@ -172,6 +173,8 @@ export const ZCreateDocumentFromTemplateRequestSchema = z.object({
|
|||
}),
|
||||
)
|
||||
.optional(),
|
||||
|
||||
formValues: ZDocumentFormValuesSchema.optional(),
|
||||
});
|
||||
|
||||
export const ZCreateDocumentFromTemplateResponseSchema = ZDocumentSchema;
|
||||
|
|
|
|||
Loading…
Reference in a new issue