mirror of
https://github.com/documenso/documenso.git
synced 2026-08-25 15:52:29 +10:00
fix: include assistant fields in pending PDFs
This commit is contained in:
@@ -11,6 +11,10 @@ import {
|
|||||||
DocumentStatus,
|
DocumentStatus,
|
||||||
type EnvelopeType,
|
type EnvelopeType,
|
||||||
EnvelopeType as EnvelopeTypeEnum,
|
EnvelopeType as EnvelopeTypeEnum,
|
||||||
|
FieldType,
|
||||||
|
type Prisma,
|
||||||
|
type Recipient,
|
||||||
|
RecipientRole,
|
||||||
SigningStatus,
|
SigningStatus,
|
||||||
type TemplateType,
|
type TemplateType,
|
||||||
TemplateType as TemplateTypeEnum,
|
TemplateType as TemplateTypeEnum,
|
||||||
@@ -56,6 +60,8 @@ type EnvelopeForPendingDownload = {
|
|||||||
internalVersion: number;
|
internalVersion: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type PendingDownloadRecipient = Pick<Recipient, 'role' | 'signingOrder'>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options shape varies by `version`:
|
* Options shape varies by `version`:
|
||||||
* - `signed` / `original`: serves stored bytes; only needs envelope `status` for cache headers.
|
* - `signed` / `original`: serves stored bytes; only needs envelope `status` for cache headers.
|
||||||
@@ -78,11 +84,11 @@ type HandleEnvelopeItemFileRequestOptions = {
|
|||||||
envelope: EnvelopeForPendingDownload;
|
envelope: EnvelopeForPendingDownload;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When set, only fields from recipients who have signed, plus the fields of
|
* Limits the PDF to fields visible to this recipient. Assistants can also
|
||||||
* the recipient owning this token, are burned in. Keeps recipient downloads
|
* see non-signature fields for unsigned recipients after them.
|
||||||
* in parity with what the signing page shows them.
|
|
||||||
*/
|
*/
|
||||||
recipientToken?: string;
|
recipientToken?: string;
|
||||||
|
recipient?: PendingDownloadRecipient;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -168,6 +174,7 @@ const handlePendingFileRequest = async ({
|
|||||||
envelope,
|
envelope,
|
||||||
documentData,
|
documentData,
|
||||||
recipientToken,
|
recipientToken,
|
||||||
|
recipient,
|
||||||
context: c,
|
context: c,
|
||||||
}: PendingFileRequestOptions) => {
|
}: PendingFileRequestOptions) => {
|
||||||
if (envelope.status !== DocumentStatus.PENDING) {
|
if (envelope.status !== DocumentStatus.PENDING) {
|
||||||
@@ -190,17 +197,44 @@ const handlePendingFileRequest = async ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let visibleFieldWhere: Prisma.FieldWhereInput = {};
|
||||||
|
|
||||||
|
if (recipient?.role === RecipientRole.ASSISTANT) {
|
||||||
|
visibleFieldWhere = {
|
||||||
|
OR: [
|
||||||
|
{
|
||||||
|
recipient: {
|
||||||
|
signingStatus: SigningStatus.SIGNED,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: {
|
||||||
|
not: FieldType.SIGNATURE,
|
||||||
|
},
|
||||||
|
recipient: {
|
||||||
|
signingStatus: {
|
||||||
|
not: SigningStatus.SIGNED,
|
||||||
|
},
|
||||||
|
signingOrder: {
|
||||||
|
gt: recipient.signingOrder ?? 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} else if (recipientToken) {
|
||||||
|
visibleFieldWhere = {
|
||||||
|
recipient: {
|
||||||
|
OR: [{ signingStatus: SigningStatus.SIGNED }, { token: recipientToken }],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const fields = await prisma.field.findMany({
|
const fields = await prisma.field.findMany({
|
||||||
where: {
|
where: {
|
||||||
envelopeItemId,
|
envelopeItemId,
|
||||||
inserted: true,
|
inserted: true,
|
||||||
...(recipientToken
|
...visibleFieldWhere,
|
||||||
? {
|
|
||||||
recipient: {
|
|
||||||
OR: [{ signingStatus: SigningStatus.SIGNED }, { token: recipientToken }],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
signature: true,
|
signature: true,
|
||||||
|
|||||||
@@ -311,7 +311,20 @@ export const filesRoute = new Hono<HonoEnv>()
|
|||||||
const envelopeItem = await prisma.envelopeItem.findUnique({
|
const envelopeItem = await prisma.envelopeItem.findUnique({
|
||||||
where: envelopeWhereQuery,
|
where: envelopeWhereQuery,
|
||||||
include: {
|
include: {
|
||||||
envelope: true,
|
envelope: {
|
||||||
|
include: {
|
||||||
|
recipients: {
|
||||||
|
where: {
|
||||||
|
token,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
role: true,
|
||||||
|
signingOrder: true,
|
||||||
|
token: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
documentData: true,
|
documentData: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -338,6 +351,7 @@ export const filesRoute = new Hono<HonoEnv>()
|
|||||||
envelopeItemId: envelopeItem.id,
|
envelopeItemId: envelopeItem.id,
|
||||||
envelope: envelopeItem.envelope,
|
envelope: envelopeItem.envelope,
|
||||||
recipientToken: token,
|
recipientToken: token,
|
||||||
|
recipient: envelopeItem.envelope.recipients[0],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -324,6 +324,80 @@ test.describe('API V2 partial signed PDF downloads', () => {
|
|||||||
expect(afterCompletionResponse.headers().etag).toBe(recipientOneResponse.headers().etag);
|
expect(afterCompletionResponse.headers().etag).toBe(recipientOneResponse.headers().etag);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('includes fields filled by an assistant in their partial PDF', async ({ request }) => {
|
||||||
|
const { envelope, distributeResult } = await apiSeedPendingDocument(request, {
|
||||||
|
recipients: [
|
||||||
|
{
|
||||||
|
email: 'partial-assistant@test.documenso.com',
|
||||||
|
name: 'Partial Assistant',
|
||||||
|
role: 'ASSISTANT',
|
||||||
|
signingOrder: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: 'partial-assisted-signer@test.documenso.com',
|
||||||
|
name: 'Partial Assisted Signer',
|
||||||
|
role: 'SIGNER',
|
||||||
|
signingOrder: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: 'partial-other-signer@test.documenso.com',
|
||||||
|
name: 'Partial Other Signer',
|
||||||
|
role: 'SIGNER',
|
||||||
|
signingOrder: 3,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
fieldsPerRecipient: [
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
{ type: FieldType.SIGNATURE, page: 1, positionX: 5, positionY: 5, width: 15, height: 5 },
|
||||||
|
{ type: FieldType.TEXT, page: 1, positionX: 5, positionY: 15, width: 15, height: 5 },
|
||||||
|
],
|
||||||
|
[{ type: FieldType.SIGNATURE, page: 1, positionX: 5, positionY: 25, width: 15, height: 5 }],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const assistant = distributeResult.recipients.find(
|
||||||
|
(recipient) => recipient.email === 'partial-assistant@test.documenso.com',
|
||||||
|
);
|
||||||
|
const signer = distributeResult.recipients.find(
|
||||||
|
(recipient) => recipient.email === 'partial-assisted-signer@test.documenso.com',
|
||||||
|
);
|
||||||
|
const other = distributeResult.recipients.find(
|
||||||
|
(recipient) => recipient.email === 'partial-other-signer@test.documenso.com',
|
||||||
|
);
|
||||||
|
const textField = envelope.fields.find(
|
||||||
|
(field) => field.recipientId === signer?.id && field.type === FieldType.TEXT,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!assistant || !signer || !other || !textField) {
|
||||||
|
throw new Error('Expected assistant test fields');
|
||||||
|
}
|
||||||
|
|
||||||
|
await trpcMutation(request, 'field.signFieldWithToken', {
|
||||||
|
token: assistant.token,
|
||||||
|
fieldId: textField.id,
|
||||||
|
value: 'Filled by assistant',
|
||||||
|
isBase64: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const download = (token: string) =>
|
||||||
|
request.get(
|
||||||
|
`${WEBAPP_BASE_URL}/api/files/token/${token}/envelopeItem/${envelope.envelopeItems[0].id}/download/pending`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [assistantPdf, signerPdf, otherPdf] = await Promise.all([
|
||||||
|
download(assistant.token),
|
||||||
|
download(signer.token),
|
||||||
|
download(other.token),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(assistantPdf.status()).toBe(200);
|
||||||
|
expect(signerPdf.status()).toBe(200);
|
||||||
|
expect(otherPdf.status()).toBe(200);
|
||||||
|
expect(assistantPdf.headers().etag).toBe(signerPdf.headers().etag);
|
||||||
|
expect(assistantPdf.headers().etag).not.toBe(otherPdf.headers().etag);
|
||||||
|
});
|
||||||
|
|
||||||
test('rejects a recipient token pending download once the envelope is completed', async ({ request }) => {
|
test('rejects a recipient token pending download once the envelope is completed', async ({ request }) => {
|
||||||
const { envelope, distributeResult } = await apiSeedPendingDocument(request);
|
const { envelope, distributeResult } = await apiSeedPendingDocument(request);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user