From 2f24a8eab25c050855272f513906b371fd3f5a8a Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Mon, 22 Jun 2026 17:00:24 +1000 Subject: [PATCH] fix: set send status on resend (#3011) --- .../api/v2/redistribute-send-status.spec.ts | 64 +++++++++++++++++++ .../server-only/document/resend-document.ts | 13 ++++ 2 files changed, 77 insertions(+) create mode 100644 packages/app-tests/e2e/api/v2/redistribute-send-status.spec.ts diff --git a/packages/app-tests/e2e/api/v2/redistribute-send-status.spec.ts b/packages/app-tests/e2e/api/v2/redistribute-send-status.spec.ts new file mode 100644 index 000000000..11bbb149b --- /dev/null +++ b/packages/app-tests/e2e/api/v2/redistribute-send-status.spec.ts @@ -0,0 +1,64 @@ +import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; +import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token'; +import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope'; +import { prisma } from '@documenso/prisma'; +import { SendStatus, SigningStatus } from '@documenso/prisma/client'; +import { seedPendingDocument } from '@documenso/prisma/seed/documents'; +import { seedUser } from '@documenso/prisma/seed/users'; +import { expect, test } from '@playwright/test'; +import type { Team, User } from '@prisma/client'; + +const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL(); +const baseUrl = `${WEBAPP_BASE_URL}/api/v2-beta`; + +test.describe.configure({ + mode: 'parallel', +}); + +test.describe('Redistribute updates recipient send status', () => { + let user: User, team: Team, token: string; + + test.beforeEach(async () => { + ({ user, team } = await seedUser()); + ({ token } = await createApiToken({ + userId: user.id, + teamId: team.id, + tokenName: 'test', + expiresIn: null, + })); + }); + + test('marks a NOT_SENT signer as SENT after a successful resend', async ({ request }) => { + const document = await seedPendingDocument(user, team.id, ['recipient@test.documenso.com']); + + const [recipient] = document.recipients; + + // Simulate a recipient that is stuck at NOT_SENT on a pending document + // (e.g. the initial send did not dispatch an email for them). + await prisma.recipient.update({ + where: { id: recipient.id }, + data: { + sendStatus: SendStatus.NOT_SENT, + signingStatus: SigningStatus.NOT_SIGNED, + sentAt: null, + }, + }); + + const res = await request.post(`${baseUrl}/document/redistribute`, { + headers: { Authorization: `Bearer ${token}` }, + data: { + documentId: mapSecondaryIdToDocumentId(document.secondaryId), + recipients: [recipient.id], + }, + }); + + expect(res.ok(), `redistribute should succeed: ${await res.text()}`).toBeTruthy(); + + const updatedRecipient = await prisma.recipient.findFirstOrThrow({ + where: { id: recipient.id }, + }); + + expect(updatedRecipient.sendStatus).toBe(SendStatus.SENT); + expect(updatedRecipient.sentAt).not.toBeNull(); + }); +}); diff --git a/packages/lib/server-only/document/resend-document.ts b/packages/lib/server-only/document/resend-document.ts index 050d973c7..c270199a3 100644 --- a/packages/lib/server-only/document/resend-document.ts +++ b/packages/lib/server-only/document/resend-document.ts @@ -13,6 +13,7 @@ import { EnvelopeType, OrganisationType, RecipientRole, + SendStatus, SigningStatus, WebhookTriggerEvents, } from '@prisma/client'; @@ -276,6 +277,18 @@ export const resendDocument = async ({ id, userId, recipients, teamId, requestMe }), }); + // Mark the recipient as sent if they were not already sent. + await prisma.recipient.updateMany({ + where: { + id: recipient.id, + sendStatus: SendStatus.NOT_SENT, + }, + data: { + sendStatus: SendStatus.SENT, + sentAt: new Date(), + }, + }); + await prisma.documentAuditLog.create({ data: createDocumentAuditLogData({ type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,