Compare commits

...
Author SHA1 Message Date
Catalin Pit a39e034876 feat: hide the reject option when the document disallows rejection
Hides the reject dialog on the V2 signing page sidebar and mobile menu,
the embed V1 signing page and the multi-sign embed view when the
document's allowDocumentRejection is false. In embeds the reject option
is shown only when both the embed's allowDocumentRejection option and
the document setting allow it, and the embed V1 footer layout follows the
same condition. Exposes the field in the multi-sign document response so
the embed view can read it.

Documents the relationship between the embed's allowDocumentRejection
option and the document setting in the iframe and embedded editor docs,
and in the API description of the field.

Part of #2561
2026-09-14 16:45:27 +03:00
Catalin Pit 5ab94b86fd Merge branch 'main' into feat/org-team-allow-document-rejection-setting 2026-09-11 14:46:41 +03:00
Catalin Pit 2140d132d7 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.
2026-09-11 14:28:15 +03:00
Catalin Pit d91a3e3828 feat: add allow document rejection setting to document preferences
Adds an allowDocumentRejection column to organisation settings (default
true), team settings (nullable, inherits from the organisation) and
document meta (default true), with the migration.

Adds the "Allow Document Rejection" field to the organisation and team
document preferences form, wires it through the settings pages, and lists
it in the reset-to-defaults dialog and the admin settings section. Adds a
per-document Yes/No field to the envelope editor settings dialog.

Exposes the field on the document meta create/update schemas and the
document, envelope, template and editor envelope response schemas, and
hides the reject dialog on the V1 signing page when the document
disallows rejection.
2026-09-11 09:38:44 +03:00
34 changed files with 235 additions and 9 deletions
@@ -186,6 +186,15 @@ Controls envelope configuration options. Set to `null` to hide envelope settings
| `allowConfigureEmailSender` | `boolean` | `true` | Allow configuring the email sender |
| `allowConfigureEmailReplyTo` | `boolean` | `true` | Allow configuring the email reply-to |
<Callout title="Allow Document Rejection">
The settings dialog also contains an "Allow Document Rejection" option that is always shown. It sets the document's
own rejection setting (`meta.allowDocumentRejection` in the API) and defaults to the organisation or team document
preference. This
is separate from the `allowDocumentRejection` option of the embedded signing views: that option only controls the
signing embed's UI, while this one is stored on the document and enforced by the server. A recipient can reject only
when both allow it.
</Callout>
### Actions
Controls available actions during editing:
@@ -47,10 +47,20 @@ Documenso expects the fragment to be **base64** of:
| `lockEmail` | `boolean` | Lock the email field (prevents editing). |
| `language` | `string` | Force the embed language (e.g. `en`). |
| `darkModeDisabled` | `boolean` | Disable dark mode behavior. |
| `allowDocumentRejection` | `boolean` | Allow or disallow document rejection. |
| `allowDocumentRejection` | `boolean` | Show the reject option in the embed. Defaults to `false`. The document must also allow rejection, see below. |
| `css` | `string` | Inject custom CSS into the embed. |
| `cssVars` | `object` | Override embed CSS variables (see the CSS Variables page). |
<Callout title="allowDocumentRejection and the document's own setting">
Two settings control whether a recipient can reject a document in an embed, and both must allow it:
- `allowDocumentRejection` above, which you pass per embed. It only controls your embed's UI and defaults to `false`.
- The document's own "Allow Document Rejection" setting. Its default comes from the organisation or team document
preferences, and it can be changed per document in the editor or through the API (`meta.allowDocumentRejection`).
When it is disabled, the reject option stays hidden even if you pass `allowDocumentRejection: true`, and the server
refuses rejection requests for that document.
</Callout>
#### Example
```ts
@@ -96,6 +96,9 @@ export const DocumentPreferencesResetDialog = ({
<li>
<Trans>Delegate document ownership</Trans>
</li>
<li>
<Trans>Allow document rejection</Trans>
</li>
{showAiFeatures && (
<li>
<Trans>AI features</Trans>
@@ -96,6 +96,9 @@ export const EmbedSignDocumentV1ClientPage = ({
const [allowDocumentRejection, setAllowDocumentRejection] = useState(false);
// Both the embed host and the document settings must allow rejection.
const isDocumentRejectionAllowed = allowDocumentRejection && (metadata?.allowDocumentRejection ?? true);
const selectedSigner = allRecipients.find((r) => r.id === selectedSignerId);
const isAssistantMode = recipient.role === RecipientRole.ASSISTANT;
@@ -303,7 +306,7 @@ export const EmbedSignDocumentV1ClientPage = ({
<div className="embed--Actions mb-4 flex w-full flex-row-reverse items-baseline justify-between">
<DocumentSigningAttachmentsPopover envelopeId={envelopeId} token={token} />
{allowDocumentRejection && (
{isDocumentRejectionAllowed && (
<DocumentSigningRejectDialog documentId={documentId} token={token} onRejected={onDocumentRejected} />
)}
</div>
@@ -502,7 +505,7 @@ export const EmbedSignDocumentV1ClientPage = ({
</Button>
) : (
<Button
className={allowDocumentRejection ? 'col-start-2' : 'col-span-2'}
className={isDocumentRejectionAllowed ? 'col-start-2' : 'col-span-2'}
disabled={isThrottled}
loading={isSubmitting}
onClick={() => throttledOnCompleteClick()}
@@ -228,7 +228,7 @@ export const MultiSignDocumentSigningView = ({
</Button>
</div>
{allowDocumentRejection && (
{allowDocumentRejection && (document.documentMeta?.allowDocumentRejection ?? true) && (
<div className="embed--Actions mt-8 mb-4 flex w-full flex-row-reverse items-baseline justify-between">
<DocumentSigningRejectDialog documentId={document.id} token={token} onRejected={onRejected} />
</div>
@@ -43,6 +43,7 @@ export type TDocumentPreferencesFormSchema = {
signatureTypes: DocumentSignatureType[];
defaultRecipients: TDefaultRecipients | null;
delegateDocumentOwnership: boolean | null;
allowDocumentRejection: boolean | null;
aiFeaturesEnabled: boolean | null;
};
@@ -57,6 +58,7 @@ type SettingsSubset = Pick<
| 'drawSignatureEnabled'
| 'defaultRecipients'
| 'delegateDocumentOwnership'
| 'allowDocumentRejection'
| 'aiFeaturesEnabled'
>;
@@ -77,6 +79,7 @@ const getDocumentPreferencesFormValues = (settings: SettingsSubset): TDocumentPr
signatureTypes: extractTeamSignatureSettings({ ...settings }),
defaultRecipients: settings.defaultRecipients ? ZDefaultRecipientsSchema.parse(settings.defaultRecipients) : null,
delegateDocumentOwnership: settings.delegateDocumentOwnership,
allowDocumentRejection: settings.allowDocumentRejection,
aiFeaturesEnabled: settings.aiFeaturesEnabled,
};
};
@@ -101,6 +104,7 @@ export const DocumentPreferencesForm = ({ settings, onFormSubmit, canInherit }:
}),
defaultRecipients: ZDefaultRecipientsSchema.nullable(),
delegateDocumentOwnership: z.boolean().nullable(),
allowDocumentRejection: z.boolean().nullable(),
aiFeaturesEnabled: z.boolean().nullable(),
});
@@ -486,6 +490,60 @@ export const DocumentPreferencesForm = ({ settings, onFormSubmit, canInherit }:
)}
/>
<FormField
control={form.control}
name="allowDocumentRejection"
render={({ field }) => (
<InheritableField
className="flex-1"
canInherit={canInherit}
isInherited={field.value === null}
label={<Trans>Allow Document Rejection</Trans>}
testId="allow-document-rejection"
>
<FormControl>
<Select
{...field}
value={field.value === null ? '-1' : field.value.toString()}
onValueChange={(value) =>
field.onChange(value === 'true' ? true : value === 'false' ? false : null)
}
>
<SelectTrigger
className="bg-background text-muted-foreground"
data-testid="allow-document-rejection-trigger"
>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="true">
<Trans>Yes</Trans>
</SelectItem>
<SelectItem value="false">
<Trans>No</Trans>
</SelectItem>
{canInherit && (
<SelectItem value={'-1'}>
<Trans>Inherit from organisation</Trans>
</SelectItem>
)}
</SelectContent>
</Select>
</FormControl>
<FormDescription>
<Trans>
Controls whether recipients can reject newly created documents from the signing page. Existing
documents keep their current setting. You can change it per document in the document settings.
</Trans>
</FormDescription>
</InheritableField>
)}
/>
{isAiFeaturesConfigured && (
<FormField
control={form.control}
@@ -138,6 +138,12 @@ export const AdminGlobalSettingsSection = ({
</DetailsValue>
</DetailsCard>
<DetailsCard label={<Trans>Allow document rejection</Trans>}>
<DetailsValue>
{booleanValue(settings.allowDocumentRejection, inheritedSettings?.allowDocumentRejection)}
</DetailsValue>
</DetailsCard>
<DetailsCard label={<Trans>Typed signature</Trans>}>
<DetailsValue>
{booleanValue(settings.typedSignatureEnabled, inheritedSettings?.typedSignatureEnabled)}
@@ -261,7 +261,9 @@ export const DocumentSigningPageViewV1 = ({
<div className="flex items-center gap-x-4">
<DocumentSigningAttachmentsPopover envelopeId={document.envelopeId} token={recipient.token} />
<DocumentSigningRejectDialog documentId={document.id} token={recipient.token} />
{documentMeta.allowDocumentRejection && (
<DocumentSigningRejectDialog documentId={document.id} token={recipient.token} />
)}
</div>
</div>
@@ -63,6 +63,10 @@ export const DocumentSigningPageViewV2 = () => {
onDocumentRejected,
} = useEmbedSigningContext() || {};
// Both the document settings and the embed host (if any) must allow rejection.
const isDocumentRejectionAllowed =
envelope.type === EnvelopeType.DOCUMENT && envelope.documentMeta.allowDocumentRejection && allowDocumentRejection;
const { t } = useLingui();
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
@@ -193,7 +197,7 @@ export const DocumentSigningPageViewV2 = () => {
}
/>
{envelope.type === EnvelopeType.DOCUMENT && allowDocumentRejection && (
{isDocumentRejectionAllowed && (
<DocumentSigningRejectDialog
documentId={mapSecondaryIdToDocumentId(envelope.secondaryId)}
token={recipient.token}
@@ -110,6 +110,7 @@ export const ZAddSettingsFormSchema = z.object({
signatureTypes: z.array(z.nativeEnum(DocumentSignatureType)).min(1, {
message: msg`At least one signature type must be enabled`.id,
}),
allowDocumentRejection: z.boolean(),
envelopeExpirationPeriod: ZEnvelopeExpirationPeriod.nullish(),
reminderSettings: ZEnvelopeReminderSettings.nullish(),
}),
@@ -200,6 +201,7 @@ export const EnvelopeEditorSettingsDialog = ({ trigger, ...props }: EnvelopeEdit
emailReplyTo: envelope.documentMeta.emailReplyTo ?? undefined,
emailSettings: ZDocumentEmailSettingsSchema.parse(envelope.documentMeta.emailSettings),
signatureTypes: extractTeamSignatureSettings(envelope.documentMeta),
allowDocumentRejection: envelope.documentMeta.allowDocumentRejection,
envelopeExpirationPeriod: envelope.documentMeta?.envelopeExpirationPeriod ?? null,
reminderSettings: envelope.documentMeta?.reminderSettings ?? null,
},
@@ -249,6 +251,7 @@ export const EnvelopeEditorSettingsDialog = ({ trigger, ...props }: EnvelopeEdit
message,
subject,
emailReplyTo,
allowDocumentRejection,
envelopeExpirationPeriod,
reminderSettings,
} = data.meta;
@@ -278,6 +281,7 @@ export const EnvelopeEditorSettingsDialog = ({ trigger, ...props }: EnvelopeEdit
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
typedSignatureEnabled: signatureTypes.includes(DocumentSignatureType.TYPE),
uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
allowDocumentRejection,
envelopeExpirationPeriod,
reminderSettings,
},
@@ -465,6 +469,52 @@ export const EnvelopeEditorSettingsDialog = ({ trigger, ...props }: EnvelopeEdit
/>
)}
<FormField
control={form.control}
name="meta.allowDocumentRejection"
render={({ field }) => (
<FormItem>
<FormLabel className="flex flex-row items-center">
<Trans>Allow Document Rejection</Trans>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger>
<TooltipContent className="max-w-xs text-muted-foreground">
<Trans>
Controls whether recipients can reject the document from the signing page.
</Trans>
</TooltipContent>
</Tooltip>
</FormLabel>
<FormControl>
<Select
value={field.value ? 'true' : 'false'}
onValueChange={(value) => field.onChange(value === 'true')}
>
<SelectTrigger className="bg-background" data-testid="allow-document-rejection-trigger">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="true">
<Trans>Yes</Trans>
</SelectItem>
<SelectItem value="false">
<Trans>No</Trans>
</SelectItem>
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{settings.allowConfigureDateFormat && (
<FormField
control={form.control}
@@ -87,7 +87,11 @@ export const EnvelopeSignerHeader = () => {
const MobileDropdownMenu = () => {
const { envelope, recipient } = useRequiredEnvelopeSigningContext();
const { allowDocumentRejection } = useEmbedSigningContext() || {};
const { allowDocumentRejection = true } = useEmbedSigningContext() || {};
// Both the document settings and the embed host (if any) must allow rejection.
const isDocumentRejectionAllowed =
envelope.type === EnvelopeType.DOCUMENT && envelope.documentMeta.allowDocumentRejection && allowDocumentRejection;
return (
<DropdownMenu>
@@ -113,7 +117,7 @@ const MobileDropdownMenu = () => {
}
/>
{envelope.type === EnvelopeType.DOCUMENT && allowDocumentRejection !== false && (
{isDocumentRejectionAllowed && (
<DocumentSigningRejectDialog
documentId={mapSecondaryIdToDocumentId(envelope.secondaryId)}
token={recipient.token}
@@ -39,6 +39,7 @@ export default function OrganisationSettingsDocumentPage() {
signatureTypes,
defaultRecipients,
delegateDocumentOwnership,
allowDocumentRejection,
aiFeaturesEnabled,
} = data;
@@ -46,6 +47,7 @@ export default function OrganisationSettingsDocumentPage() {
documentVisibility === null ||
documentLanguage === null ||
documentDateFormat === null ||
allowDocumentRejection === null ||
aiFeaturesEnabled === null
) {
throw new Error('Should not be possible.');
@@ -63,6 +65,7 @@ export default function OrganisationSettingsDocumentPage() {
uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
delegateDocumentOwnership,
allowDocumentRejection,
aiFeaturesEnabled,
},
});
@@ -33,6 +33,7 @@ export default function TeamsSettingsPage() {
signatureTypes,
defaultRecipients,
delegateDocumentOwnership,
allowDocumentRejection,
aiFeaturesEnabled,
} = data;
@@ -57,6 +58,7 @@ export default function TeamsSettingsPage() {
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
}),
delegateDocumentOwnership,
allowDocumentRejection,
},
});
@@ -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,
),
+8
View File
@@ -28,6 +28,7 @@ export const ZDocumentMetaSchema = DocumentMetaSchema.pick({
typedSignatureEnabled: true,
uploadSignatureEnabled: true,
drawSignatureEnabled: true,
allowDocumentRejection: true,
language: true,
emailSettings: true,
});
@@ -105,6 +106,12 @@ export const ZDocumentMetaUploadSignatureEnabledSchema = z
.boolean()
.describe('Whether to allow recipients to sign using an uploaded signature.');
export const ZDocumentMetaAllowDocumentRejectionSchema = z
.boolean()
.describe(
'Whether recipients can reject the document from the signing page. Defaults to the organisation or team document preference. In embedded signing the embed must also enable its own allowDocumentRejection option.',
);
/**
* Note: Any updates to this will cause public API changes. You will need to update
* all corresponding areas where this is used (some places that use this needs to pass
@@ -123,6 +130,7 @@ export const ZDocumentMetaCreateSchema = z.object({
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
allowDocumentRejection: ZDocumentMetaAllowDocumentRejectionSchema.optional(),
emailId: z.string().nullish(),
emailReplyTo: zEmail().nullish(),
emailSettings: ZDocumentEmailSettingsSchema.nullish(),
+1
View File
@@ -63,6 +63,7 @@ export const ZDocumentSchema = LegacyDocumentSchema.pick({
uploadSignatureEnabled: true,
drawSignatureEnabled: true,
allowDictateNextSigner: true,
allowDocumentRejection: true,
language: true,
emailSettings: true,
emailId: true,
+1
View File
@@ -280,6 +280,7 @@ export const ZEditorEnvelopeSchema = EnvelopeSchema.pick({
uploadSignatureEnabled: true,
drawSignatureEnabled: true,
allowDictateNextSigner: true,
allowDocumentRejection: true,
language: true,
emailSettings: true,
emailId: true,
+1
View File
@@ -50,6 +50,7 @@ export const ZEnvelopeSchema = EnvelopeSchema.pick({
uploadSignatureEnabled: true,
drawSignatureEnabled: true,
allowDictateNextSigner: true,
allowDocumentRejection: true,
language: true,
emailSettings: true,
emailId: true,
+1
View File
@@ -55,6 +55,7 @@ export const ZTemplateSchema = TemplateSchema.pick({
uploadSignatureEnabled: true,
drawSignatureEnabled: true,
allowDictateNextSigner: true,
allowDocumentRejection: true,
distributionMethod: true,
redirectUrl: true,
language: true,
+3
View File
@@ -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'>;
};
+1
View File
@@ -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,
+1
View File
@@ -178,6 +178,7 @@ export const generateDefaultTeamSettings = (): Omit<TeamGlobalSettings, 'id' | '
documentTimezone: null,
documentDateFormat: null,
delegateDocumentOwnership: null,
allowDocumentRejection: null,
includeSenderDetails: null,
includeSigningCertificate: null,
@@ -0,0 +1,8 @@
-- AlterTable
ALTER TABLE "DocumentMeta" ADD COLUMN "allowDocumentRejection" BOOLEAN NOT NULL DEFAULT true;
-- AlterTable
ALTER TABLE "OrganisationGlobalSettings" ADD COLUMN "allowDocumentRejection" BOOLEAN NOT NULL DEFAULT true;
-- AlterTable
ALTER TABLE "TeamGlobalSettings" ADD COLUMN "allowDocumentRejection" BOOLEAN;
+4
View File
@@ -583,6 +583,8 @@ model DocumentMeta {
reminderSettings Json? /// [EnvelopeReminderSettings] @zod.custom.use(ZEnvelopeReminderSettingsSchema)
allowDocumentRejection Boolean @default(true)
envelope Envelope?
}
@@ -966,6 +968,7 @@ model OrganisationGlobalSettings {
documentTimezone String? // Nullable to allow using local timezones if not set.
documentDateFormat String @default("yyyy-MM-dd hh:mm a")
delegateDocumentOwnership Boolean @default(false)
allowDocumentRejection Boolean @default(true)
typedSignatureEnabled Boolean @default(true)
uploadSignatureEnabled Boolean @default(true)
@@ -1005,6 +1008,7 @@ model TeamGlobalSettings {
documentTimezone String?
documentDateFormat String?
delegateDocumentOwnership Boolean?
allowDocumentRejection Boolean?
includeSenderDetails Boolean?
includeSigningCertificate Boolean?
@@ -31,6 +31,7 @@ export const ZGetMultiSignDocumentResponseSchema = ZDocumentLiteSchema.extend({
uploadSignatureEnabled: true,
drawSignatureEnabled: true,
allowDictateNextSigner: true,
allowDocumentRejection: true,
language: true,
emailSettings: true,
}).nullable(),
@@ -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(),