mirror of
https://github.com/documenso/documenso.git
synced 2026-07-25 17:35:05 +10:00
fix: email dictated direct template signer (#2810)
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||||
|
import { FIELD_SIGNATURE_META_DEFAULT_VALUES } from '@documenso/lib/types/field-meta';
|
||||||
import { createDocumentAuthOptions } from '@documenso/lib/utils/document-auth';
|
import { createDocumentAuthOptions } from '@documenso/lib/utils/document-auth';
|
||||||
import { mapSecondaryIdToTemplateId } from '@documenso/lib/utils/envelope';
|
import { mapSecondaryIdToTemplateId } from '@documenso/lib/utils/envelope';
|
||||||
import { formatDirectTemplatePath } from '@documenso/lib/utils/templates';
|
import { formatDirectTemplatePath } from '@documenso/lib/utils/templates';
|
||||||
@@ -7,10 +8,11 @@ import { seedTeam } from '@documenso/prisma/seed/teams';
|
|||||||
import { seedDirectTemplate, seedTemplate } from '@documenso/prisma/seed/templates';
|
import { seedDirectTemplate, seedTemplate } from '@documenso/prisma/seed/templates';
|
||||||
import { seedTestEmail, seedUser } from '@documenso/prisma/seed/users';
|
import { seedTestEmail, seedUser } from '@documenso/prisma/seed/users';
|
||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
import { DocumentSigningOrder, RecipientRole } from '@prisma/client';
|
import { DocumentSigningOrder, FieldType, RecipientRole } from '@prisma/client';
|
||||||
import { customAlphabet } from 'nanoid';
|
import { customAlphabet } from 'nanoid';
|
||||||
|
|
||||||
import { apiSignin } from '../fixtures/authentication';
|
import { apiSignin } from '../fixtures/authentication';
|
||||||
|
import { signSignaturePad } from '../fixtures/signature';
|
||||||
|
|
||||||
// Duped from `packages/lib/utils/teams.ts` due to errors when importing that file.
|
// Duped from `packages/lib/utils/teams.ts` due to errors when importing that file.
|
||||||
const formatDocumentsPath = (teamUrl: string) => `/t/${teamUrl}/documents`;
|
const formatDocumentsPath = (teamUrl: string) => `/t/${teamUrl}/documents`;
|
||||||
@@ -18,6 +20,47 @@ const formatTemplatesPath = (teamUrl: string) => `/t/${teamUrl}/templates`;
|
|||||||
|
|
||||||
const nanoid = customAlphabet('1234567890abcdef', 10);
|
const nanoid = customAlphabet('1234567890abcdef', 10);
|
||||||
|
|
||||||
|
const expectSigningRequestJobForRecipient = async (recipientId: number) => {
|
||||||
|
const job = await prisma.backgroundJob.findFirst({
|
||||||
|
where: {
|
||||||
|
jobId: 'send.signing.requested.email',
|
||||||
|
payload: {
|
||||||
|
path: ['recipientId'],
|
||||||
|
equals: recipientId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(job).not.toBeNull();
|
||||||
|
};
|
||||||
|
|
||||||
|
const seedSignatureFieldForRecipient = async (options: {
|
||||||
|
envelopeId: string;
|
||||||
|
recipientId: number;
|
||||||
|
positionY: number;
|
||||||
|
}) => {
|
||||||
|
const envelopeItem = await prisma.envelopeItem.findFirstOrThrow({
|
||||||
|
where: { envelopeId: options.envelopeId },
|
||||||
|
});
|
||||||
|
|
||||||
|
return await prisma.field.create({
|
||||||
|
data: {
|
||||||
|
envelopeId: options.envelopeId,
|
||||||
|
envelopeItemId: envelopeItem.id,
|
||||||
|
recipientId: options.recipientId,
|
||||||
|
type: FieldType.SIGNATURE,
|
||||||
|
page: 1,
|
||||||
|
positionX: 5,
|
||||||
|
positionY: options.positionY,
|
||||||
|
width: 20,
|
||||||
|
height: 5,
|
||||||
|
customText: '',
|
||||||
|
inserted: false,
|
||||||
|
fieldMeta: FIELD_SIGNATURE_META_DEFAULT_VALUES,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
test('[DIRECT_TEMPLATES]: create direct link for template', async ({ page }) => {
|
test('[DIRECT_TEMPLATES]: create direct link for template', async ({ page }) => {
|
||||||
const { team, owner, organisation } = await seedTeam({
|
const { team, owner, organisation } = await seedTeam({
|
||||||
createTeamMembers: 1,
|
createTeamMembers: 1,
|
||||||
@@ -256,11 +299,24 @@ test('[DIRECT_TEMPLATES]: V1 use direct template link with 2 recipients with nex
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const directTemplateRecipient = template.recipients[0];
|
||||||
|
|
||||||
|
if (!directTemplateRecipient) {
|
||||||
|
throw new Error('Expected direct template recipient to exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
// All SIGNER recipients need a signature field for sendDocument to dispatch emails.
|
||||||
|
const directSignatureField = await seedSignatureFieldForRecipient({
|
||||||
|
envelopeId: template.id,
|
||||||
|
recipientId: directTemplateRecipient.id,
|
||||||
|
positionY: 10,
|
||||||
|
});
|
||||||
|
|
||||||
const originalName = 'Signer 2';
|
const originalName = 'Signer 2';
|
||||||
const originalSecondSignerEmail = seedTestEmail();
|
const originalSecondSignerEmail = seedTestEmail();
|
||||||
|
|
||||||
// Add another signer
|
// Add another signer
|
||||||
await prisma.recipient.create({
|
const secondRecipient = await prisma.recipient.create({
|
||||||
data: {
|
data: {
|
||||||
signingOrder: 2,
|
signingOrder: 2,
|
||||||
envelopeId: template.id,
|
envelopeId: template.id,
|
||||||
@@ -271,6 +327,12 @@ test('[DIRECT_TEMPLATES]: V1 use direct template link with 2 recipients with nex
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await seedSignatureFieldForRecipient({
|
||||||
|
envelopeId: template.id,
|
||||||
|
recipientId: secondRecipient.id,
|
||||||
|
positionY: 20,
|
||||||
|
});
|
||||||
|
|
||||||
// Check that the direct template link is accessible.
|
// Check that the direct template link is accessible.
|
||||||
await page.goto(formatDirectTemplatePath(template.directLink?.token || ''));
|
await page.goto(formatDirectTemplatePath(template.directLink?.token || ''));
|
||||||
await expect(page.getByRole('heading', { name: 'General' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'General' })).toBeVisible();
|
||||||
@@ -279,6 +341,12 @@ test('[DIRECT_TEMPLATES]: V1 use direct template link with 2 recipients with nex
|
|||||||
await page.getByPlaceholder('recipient@documenso.com').fill(seedTestEmail());
|
await page.getByPlaceholder('recipient@documenso.com').fill(seedTestEmail());
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Continue' }).click();
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
|
||||||
|
// Sign the direct template recipient's signature field via the UI.
|
||||||
|
await signSignaturePad(page);
|
||||||
|
await page.locator(`#field-${directSignatureField.id}`).getByRole('button').click();
|
||||||
|
await expect(page.locator(`#field-${directSignatureField.id}`)).toHaveAttribute('data-inserted', 'true');
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Complete' }).click();
|
await page.getByRole('button', { name: 'Complete' }).click();
|
||||||
|
|
||||||
await expect(page.getByText('Next Recipient Name')).toBeVisible();
|
await expect(page.getByText('Next Recipient Name')).toBeVisible();
|
||||||
@@ -309,8 +377,15 @@ test('[DIRECT_TEMPLATES]: V1 use direct template link with 2 recipients with nex
|
|||||||
|
|
||||||
const updatedSecondRecipient = createdEnvelopeRecipients.find((recipient) => recipient.signingOrder === 2);
|
const updatedSecondRecipient = createdEnvelopeRecipients.find((recipient) => recipient.signingOrder === 2);
|
||||||
|
|
||||||
expect(updatedSecondRecipient?.name).toBe(newName);
|
expect(updatedSecondRecipient).toBeDefined();
|
||||||
expect(updatedSecondRecipient?.email).toBe(newSecondSignerEmail);
|
|
||||||
|
if (!updatedSecondRecipient) {
|
||||||
|
throw new Error('Expected second recipient to exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(updatedSecondRecipient.name).toBe(newName);
|
||||||
|
expect(updatedSecondRecipient.email).toBe(newSecondSignerEmail);
|
||||||
|
await expectSigningRequestJobForRecipient(updatedSecondRecipient.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('[DIRECT_TEMPLATES]: V2 use direct template link with 2 recipients with next signer dictation', async ({
|
test('[DIRECT_TEMPLATES]: V2 use direct template link with 2 recipients with next signer dictation', async ({
|
||||||
@@ -338,11 +413,24 @@ test('[DIRECT_TEMPLATES]: V2 use direct template link with 2 recipients with nex
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const directTemplateRecipient = template.recipients[0];
|
||||||
|
|
||||||
|
if (!directTemplateRecipient) {
|
||||||
|
throw new Error('Expected direct template recipient to exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
// All SIGNER recipients need a signature field for sendDocument to dispatch emails.
|
||||||
|
const directSignatureField = await seedSignatureFieldForRecipient({
|
||||||
|
envelopeId: template.id,
|
||||||
|
recipientId: directTemplateRecipient.id,
|
||||||
|
positionY: 10,
|
||||||
|
});
|
||||||
|
|
||||||
const originalName = 'Signer 2';
|
const originalName = 'Signer 2';
|
||||||
const originalSecondSignerEmail = seedTestEmail();
|
const originalSecondSignerEmail = seedTestEmail();
|
||||||
|
|
||||||
// Add another signer
|
// Add another signer
|
||||||
await prisma.recipient.create({
|
const secondRecipient = await prisma.recipient.create({
|
||||||
data: {
|
data: {
|
||||||
signingOrder: 2,
|
signingOrder: 2,
|
||||||
envelopeId: template.id,
|
envelopeId: template.id,
|
||||||
@@ -353,10 +441,39 @@ test('[DIRECT_TEMPLATES]: V2 use direct template link with 2 recipients with nex
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await seedSignatureFieldForRecipient({
|
||||||
|
envelopeId: template.id,
|
||||||
|
recipientId: secondRecipient.id,
|
||||||
|
positionY: 20,
|
||||||
|
});
|
||||||
|
|
||||||
// Check that the direct template link is accessible.
|
// Check that the direct template link is accessible.
|
||||||
await page.goto(formatDirectTemplatePath(template.directLink?.token || ''));
|
await page.goto(formatDirectTemplatePath(template.directLink?.token || ''));
|
||||||
await expect(page.getByRole('heading', { name: 'Team direct template link 1' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'Team direct template link 1' })).toBeVisible();
|
||||||
await page.waitForTimeout(100);
|
|
||||||
|
// Wait for the PDF and the Konva canvas overlay to be ready.
|
||||||
|
await expect(page.locator('img[data-page-number]').first()).toBeVisible({ timeout: 30_000 });
|
||||||
|
const canvas = page.locator('.konva-container canvas').first();
|
||||||
|
await expect(canvas).toBeVisible({ timeout: 30_000 });
|
||||||
|
|
||||||
|
// Sign the direct template recipient's signature field via the canvas-based V2 UI.
|
||||||
|
await signSignaturePad(page);
|
||||||
|
|
||||||
|
const canvasBox = await canvas.boundingBox();
|
||||||
|
|
||||||
|
if (!canvasBox) {
|
||||||
|
throw new Error('Canvas bounding box not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const x =
|
||||||
|
(Number(directSignatureField.positionX) / 100) * canvasBox.width +
|
||||||
|
((Number(directSignatureField.width) / 100) * canvasBox.width) / 2;
|
||||||
|
const y =
|
||||||
|
(Number(directSignatureField.positionY) / 100) * canvasBox.height +
|
||||||
|
((Number(directSignatureField.height) / 100) * canvasBox.height) / 2;
|
||||||
|
|
||||||
|
await canvas.click({ position: { x, y } });
|
||||||
|
await expect(page.getByText('0 Fields Remaining').first()).toBeVisible({ timeout: 10_000 });
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Complete' }).click();
|
await page.getByRole('button', { name: 'Complete' }).click();
|
||||||
|
|
||||||
@@ -394,6 +511,13 @@ test('[DIRECT_TEMPLATES]: V2 use direct template link with 2 recipients with nex
|
|||||||
|
|
||||||
const updatedSecondRecipient = createdEnvelopeRecipients.find((recipient) => recipient.signingOrder === 2);
|
const updatedSecondRecipient = createdEnvelopeRecipients.find((recipient) => recipient.signingOrder === 2);
|
||||||
|
|
||||||
expect(updatedSecondRecipient?.name).toBe(newName);
|
expect(updatedSecondRecipient).toBeDefined();
|
||||||
expect(updatedSecondRecipient?.email).toBe(newSecondSignerEmail);
|
|
||||||
|
if (!updatedSecondRecipient) {
|
||||||
|
throw new Error('Expected second recipient to exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(updatedSecondRecipient.name).toBe(newName);
|
||||||
|
expect(updatedSecondRecipient.email).toBe(newSecondSignerEmail);
|
||||||
|
await expectSigningRequestJobForRecipient(updatedSecondRecipient.id);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -104,10 +104,6 @@ export const sendDocument = async ({ id, userId, teamId, sendEmail, requestMetad
|
|||||||
recipientsToNotify = envelope.recipients
|
recipientsToNotify = envelope.recipients
|
||||||
.filter((r) => r.signingStatus === SigningStatus.NOT_SIGNED && r.role !== RecipientRole.CC)
|
.filter((r) => r.signingStatus === SigningStatus.NOT_SIGNED && r.role !== RecipientRole.CC)
|
||||||
.slice(0, 1);
|
.slice(0, 1);
|
||||||
|
|
||||||
// Secondary filter so we aren't resending if the current active recipient has already
|
|
||||||
// received the envelope.
|
|
||||||
recipientsToNotify.filter((r) => r.sendStatus !== SendStatus.SENT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (envelope.envelopeItems.length === 0) {
|
if (envelope.envelopeItems.length === 0) {
|
||||||
|
|||||||
@@ -669,8 +669,6 @@ export const createDocumentFromDirectTemplate = async ({
|
|||||||
await tx.recipient.update({
|
await tx.recipient.update({
|
||||||
where: { id: nextRecipient.id },
|
where: { id: nextRecipient.id },
|
||||||
data: {
|
data: {
|
||||||
sendStatus: SendStatus.SENT,
|
|
||||||
sentAt: new Date(),
|
|
||||||
...(nextSigner && documentMeta?.allowDictateNextSigner
|
...(nextSigner && documentMeta?.allowDictateNextSigner
|
||||||
? {
|
? {
|
||||||
name: nextSigner.name,
|
name: nextSigner.name,
|
||||||
|
|||||||
Reference in New Issue
Block a user