mirror of
https://github.com/documenso/documenso.git
synced 2026-10-05 03:04:06 +10:00
feat: persist and enforce the allow document rejection setting
Accepts allowDocumentRejection in the organisation and team settings update routes, adds it to the default organisation (true) and team (inherit) settings, and copies the merged value into the document meta when a document or template is created via extractDerivedDocumentMeta. Documents created from a template take the template's value, with an optional override on envelope.use. Exposes the field in the recipient signing response and refuses rejectDocumentWithToken requests for documents that disallow rejection, since the endpoint can be called directly without the UI. Extends the document preferences and envelope settings e2e tests to cover inheritance into the team settings and new documents, and persistence of the per-document value from the editor.
This commit is contained in:
@@ -24,6 +24,7 @@ const TEST_SETTINGS_VALUES = {
|
||||
subject: 'E2E settings subject',
|
||||
message: 'E2E settings message',
|
||||
language: 'French',
|
||||
allowDocumentRejection: 'No',
|
||||
dateFormat: 'DD/MM/YYYY',
|
||||
timezone: 'Europe/London',
|
||||
distributionMethod: 'None',
|
||||
@@ -104,6 +105,10 @@ const runSettingsFlow = async ({ root }: TEnvelopeEditorSurface, { externalId, i
|
||||
await root.getByRole('option', { name: 'Upload' }).click();
|
||||
await clickSettingsDialogHeader(root);
|
||||
|
||||
await getComboboxByLabel(root, 'Allow Document Rejection').click();
|
||||
await root.getByRole('option', { name: TEST_SETTINGS_VALUES.allowDocumentRejection, exact: true }).click();
|
||||
await clickSettingsDialogHeader(root);
|
||||
|
||||
await getComboboxByLabel(root, 'Date Format').click();
|
||||
await root.getByRole('option', { name: TEST_SETTINGS_VALUES.dateFormat, exact: true }).click();
|
||||
await clickSettingsDialogHeader(root);
|
||||
@@ -265,6 +270,9 @@ const runSettingsFlow = async ({ root }: TEnvelopeEditorSurface, { externalId, i
|
||||
await expect(root.locator('input[name="meta.redirectUrl"]')).toHaveValue(TEST_SETTINGS_VALUES.redirectUrl);
|
||||
await expect(getComboboxByLabel(root, 'Language')).toContainText(TEST_SETTINGS_VALUES.language);
|
||||
await expect(getComboboxByLabel(root, 'Allowed Signature Types')).not.toContainText('Upload');
|
||||
await expect(getComboboxByLabel(root, 'Allow Document Rejection')).toContainText(
|
||||
TEST_SETTINGS_VALUES.allowDocumentRejection,
|
||||
);
|
||||
await expect(getComboboxByLabel(root, 'Date Format')).toContainText(TEST_SETTINGS_VALUES.dateFormat);
|
||||
await expect(getComboboxByLabel(root, 'Time Zone')).toContainText(TEST_SETTINGS_VALUES.timezone);
|
||||
await expect(root.locator('[data-testid="documentDistributionMethodSelectValue"]')).toContainText(
|
||||
@@ -384,6 +392,7 @@ const assertEnvelopeSettingsPersistedInDatabase = async ({
|
||||
expect(envelope.documentMeta.drawSignatureEnabled).toBe(true);
|
||||
expect(envelope.documentMeta.typedSignatureEnabled).toBe(true);
|
||||
expect(envelope.documentMeta.uploadSignatureEnabled).toBe(false);
|
||||
expect(envelope.documentMeta.allowDocumentRejection).toBe(false);
|
||||
expect(envelope.documentMeta.emailSettings).toMatchObject(DB_EXPECTED_VALUES.emailSettings);
|
||||
|
||||
const authOptions = parseAuthOptions(envelope.authOptions);
|
||||
|
||||
@@ -37,6 +37,10 @@ test('[ORGANISATIONS]: manage document preferences', async ({ page }) => {
|
||||
await page.getByRole('option', { name: 'Upload' }).click();
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
// Disable document rejection
|
||||
await page.getByTestId('allow-document-rejection-trigger').click();
|
||||
await page.getByRole('option', { name: 'No', exact: true }).click();
|
||||
|
||||
await page.getByRole('button', { name: 'Save changes' }).first().click();
|
||||
await expect(page.getByText('Your document preferences have been updated').first()).toBeVisible();
|
||||
|
||||
@@ -68,10 +72,14 @@ test('[ORGANISATIONS]: manage document preferences', async ({ page }) => {
|
||||
expect(teamSettings.typedSignatureEnabled).toEqual(true);
|
||||
expect(teamSettings.uploadSignatureEnabled).toEqual(false);
|
||||
expect(teamSettings.drawSignatureEnabled).toEqual(false);
|
||||
expect(teamSettings.allowDocumentRejection).toEqual(false);
|
||||
|
||||
// Edit the team settings
|
||||
await page.goto(`/t/${team.url}/settings/document`);
|
||||
|
||||
// Document rejection is left untouched so it keeps inheriting from the organisation.
|
||||
await expect(page.getByTestId('allow-document-rejection-status')).toHaveText('Inherited');
|
||||
|
||||
await page.getByTestId('document-visibility-trigger').click();
|
||||
await page.getByRole('option', { name: 'Everyone can access and view' }).click();
|
||||
await page.getByTestId('document-language-trigger').click();
|
||||
@@ -102,6 +110,7 @@ test('[ORGANISATIONS]: manage document preferences', async ({ page }) => {
|
||||
expect(updatedTeamSettings.typedSignatureEnabled).toEqual(true);
|
||||
expect(updatedTeamSettings.uploadSignatureEnabled).toEqual(false);
|
||||
expect(updatedTeamSettings.drawSignatureEnabled).toEqual(false);
|
||||
expect(updatedTeamSettings.allowDocumentRejection).toEqual(false);
|
||||
|
||||
const document = await seedTeamDocumentWithMeta(team);
|
||||
|
||||
@@ -120,6 +129,7 @@ test('[ORGANISATIONS]: manage document preferences', async ({ page }) => {
|
||||
expect(documentMeta.language).toEqual('pl');
|
||||
expect(documentMeta.timezone).toEqual('Europe/London');
|
||||
expect(documentMeta.dateFormat).toEqual('MM/dd/yyyy');
|
||||
expect(documentMeta.allowDocumentRejection).toEqual(false);
|
||||
});
|
||||
|
||||
test('[ORGANISATIONS]: manage branding preferences', async ({ page }) => {
|
||||
|
||||
@@ -33,7 +33,11 @@ export async function rejectDocumentWithToken({ token, id, reason, requestMetada
|
||||
envelope: unsafeBuildEnvelopeIdQuery(id, EnvelopeType.DOCUMENT),
|
||||
},
|
||||
include: {
|
||||
envelope: true,
|
||||
envelope: {
|
||||
include: {
|
||||
documentMeta: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -51,6 +55,14 @@ export async function rejectDocumentWithToken({ token, id, reason, requestMetada
|
||||
});
|
||||
}
|
||||
|
||||
// Hiding the reject button on the signing page is not enough because the
|
||||
// recipient can call this endpoint directly, so enforce the setting here.
|
||||
if (!envelope.documentMeta.allowDocumentRejection) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: `Document ${envelope.id} does not allow rejection`,
|
||||
});
|
||||
}
|
||||
|
||||
assertRecipientNotExpired(recipient);
|
||||
|
||||
// Update the recipient status to rejected
|
||||
|
||||
@@ -48,6 +48,7 @@ export const ZEnvelopeForSigningResponse = z.object({
|
||||
uploadSignatureEnabled: true,
|
||||
drawSignatureEnabled: true,
|
||||
allowDictateNextSigner: true,
|
||||
allowDocumentRejection: true,
|
||||
language: true,
|
||||
}),
|
||||
recipients: ZRecipientLiteSchema.pick({
|
||||
|
||||
@@ -113,6 +113,7 @@ export type CreateDocumentFromTemplateOptions = {
|
||||
uploadSignatureEnabled?: boolean;
|
||||
drawSignatureEnabled?: boolean;
|
||||
envelopeExpirationPeriod?: TEnvelopeExpirationPeriod | null;
|
||||
allowDocumentRejection?: boolean;
|
||||
};
|
||||
|
||||
formValues?: TDocumentFormValues;
|
||||
@@ -542,6 +543,7 @@ export const createDocumentFromTemplate = async ({
|
||||
drawSignatureEnabled: override?.drawSignatureEnabled ?? template.documentMeta?.drawSignatureEnabled,
|
||||
allowDictateNextSigner: override?.allowDictateNextSigner ?? template.documentMeta?.allowDictateNextSigner,
|
||||
envelopeExpirationPeriod: override?.envelopeExpirationPeriod ?? template.documentMeta?.envelopeExpirationPeriod,
|
||||
allowDocumentRejection: override?.allowDocumentRejection ?? template.documentMeta?.allowDocumentRejection,
|
||||
},
|
||||
signatureLevel,
|
||||
),
|
||||
|
||||
@@ -70,6 +70,9 @@ export const extractDerivedDocumentMeta = (
|
||||
|
||||
// Reminder settings.
|
||||
reminderSettings: meta.reminderSettings ?? settings.reminderSettings ?? null,
|
||||
|
||||
// Rejection settings.
|
||||
allowDocumentRejection: meta.allowDocumentRejection ?? settings.allowDocumentRejection,
|
||||
} satisfies Omit<DocumentMeta, 'id'>;
|
||||
};
|
||||
|
||||
|
||||
@@ -111,6 +111,7 @@ export const generateDefaultOrganisationSettings = (): Omit<OrganisationGlobalSe
|
||||
documentTimezone: null, // Null means local timezone.
|
||||
documentDateFormat: DEFAULT_DOCUMENT_DATE_FORMAT,
|
||||
delegateDocumentOwnership: false,
|
||||
allowDocumentRejection: true,
|
||||
|
||||
includeSenderDetails: true,
|
||||
includeSigningCertificate: true,
|
||||
|
||||
@@ -178,6 +178,7 @@ export const generateDefaultTeamSettings = (): Omit<TeamGlobalSettings, 'id' | '
|
||||
documentTimezone: null,
|
||||
documentDateFormat: null,
|
||||
delegateDocumentOwnership: null,
|
||||
allowDocumentRejection: null,
|
||||
|
||||
includeSenderDetails: null,
|
||||
includeSigningCertificate: null,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ZEnvelopeExpirationPeriod } from '@documenso/lib/constants/envelope-exp
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { ZDocumentFormValuesSchema } from '@documenso/lib/types/document-form-values';
|
||||
import {
|
||||
ZDocumentMetaAllowDocumentRejectionSchema,
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaDistributionMethodSchema,
|
||||
ZDocumentMetaDrawSignatureEnabledSchema,
|
||||
@@ -96,6 +97,7 @@ export const ZUseEnvelopePayloadSchema = z.object({
|
||||
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
|
||||
allowDictateNextSigner: z.boolean().optional(),
|
||||
envelopeExpirationPeriod: ZEnvelopeExpirationPeriod.nullish(),
|
||||
allowDocumentRejection: ZDocumentMetaAllowDocumentRejectionSchema.optional(),
|
||||
})
|
||||
.describe('Override values from the template for the created document.')
|
||||
.optional(),
|
||||
|
||||
@@ -39,6 +39,7 @@ export const updateOrganisationSettingsRoute = authenticatedProcedure
|
||||
drawSignatureEnabled,
|
||||
defaultRecipients,
|
||||
delegateDocumentOwnership,
|
||||
allowDocumentRejection,
|
||||
envelopeExpirationPeriod,
|
||||
reminderSettings,
|
||||
|
||||
@@ -167,6 +168,7 @@ export const updateOrganisationSettingsRoute = authenticatedProcedure
|
||||
drawSignatureEnabled,
|
||||
defaultRecipients: defaultRecipients === null ? Prisma.DbNull : defaultRecipients,
|
||||
delegateDocumentOwnership: derivedDelegateDocumentOwnership,
|
||||
allowDocumentRejection,
|
||||
envelopeExpirationPeriod: envelopeExpirationPeriod === null ? Prisma.DbNull : envelopeExpirationPeriod,
|
||||
reminderSettings: reminderSettings === null ? Prisma.DbNull : reminderSettings,
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ export const ZUpdateOrganisationSettingsRequestSchema = z.object({
|
||||
drawSignatureEnabled: z.boolean().optional(),
|
||||
defaultRecipients: ZDefaultRecipientsSchema.nullish(),
|
||||
delegateDocumentOwnership: z.boolean().nullish(),
|
||||
allowDocumentRejection: z.boolean().optional(),
|
||||
envelopeExpirationPeriod: ZEnvelopeExpirationPeriod.optional(),
|
||||
reminderSettings: ZEnvelopeReminderSettings.optional(),
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ export const updateTeamSettingsRoute = authenticatedProcedure
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
delegateDocumentOwnership,
|
||||
allowDocumentRejection,
|
||||
envelopeExpirationPeriod,
|
||||
reminderSettings,
|
||||
|
||||
@@ -169,6 +170,7 @@ export const updateTeamSettingsRoute = authenticatedProcedure
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
delegateDocumentOwnership,
|
||||
allowDocumentRejection,
|
||||
envelopeExpirationPeriod: envelopeExpirationPeriod === null ? Prisma.DbNull : envelopeExpirationPeriod,
|
||||
reminderSettings: reminderSettings === null ? Prisma.DbNull : reminderSettings,
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ export const ZUpdateTeamSettingsRequestSchema = z.object({
|
||||
uploadSignatureEnabled: z.boolean().nullish(),
|
||||
drawSignatureEnabled: z.boolean().nullish(),
|
||||
delegateDocumentOwnership: z.boolean().nullish(),
|
||||
allowDocumentRejection: z.boolean().nullish(),
|
||||
envelopeExpirationPeriod: ZEnvelopeExpirationPeriod.nullish(),
|
||||
reminderSettings: ZEnvelopeReminderSettings.nullish(),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user