mirror of
https://github.com/documenso/documenso.git
synced 2025-11-16 01:32:06 +10:00
feat: dictate next signers in signing ordeR
This commit is contained in:
@ -28,6 +28,7 @@ export type CreateDocumentMetaOptions = {
|
||||
distributionMethod?: DocumentDistributionMethod;
|
||||
typedSignatureEnabled?: boolean;
|
||||
language?: SupportedLanguageCodes;
|
||||
modifyNextSigner?: boolean;
|
||||
requestMetadata: ApiRequestMetadata;
|
||||
};
|
||||
|
||||
@ -46,6 +47,7 @@ export const upsertDocumentMeta = async ({
|
||||
distributionMethod,
|
||||
typedSignatureEnabled,
|
||||
language,
|
||||
modifyNextSigner,
|
||||
requestMetadata,
|
||||
}: CreateDocumentMetaOptions) => {
|
||||
const document = await prisma.document.findFirst({
|
||||
@ -98,6 +100,7 @@ export const upsertDocumentMeta = async ({
|
||||
distributionMethod,
|
||||
typedSignatureEnabled,
|
||||
language,
|
||||
modifyNextSigner,
|
||||
},
|
||||
update: {
|
||||
subject,
|
||||
@ -111,6 +114,7 @@ export const upsertDocumentMeta = async ({
|
||||
distributionMethod,
|
||||
typedSignatureEnabled,
|
||||
language,
|
||||
modifyNextSigner,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -28,6 +28,10 @@ export type CompleteDocumentWithTokenOptions = {
|
||||
userId?: number;
|
||||
authOptions?: TRecipientActionAuth;
|
||||
requestMetadata?: RequestMetadata;
|
||||
nextSigner?: {
|
||||
email: string;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
|
||||
const getDocument = async ({ token, documentId }: CompleteDocumentWithTokenOptions) => {
|
||||
@ -51,11 +55,56 @@ const getDocument = async ({ token, documentId }: CompleteDocumentWithTokenOptio
|
||||
});
|
||||
};
|
||||
|
||||
export const delegateNextSigner = async ({
|
||||
documentId,
|
||||
currentRecipientId,
|
||||
nextSigner,
|
||||
}: {
|
||||
documentId: number;
|
||||
currentRecipientId: number;
|
||||
nextSigner: { email: string; name: string };
|
||||
}) => {
|
||||
const document = await prisma.document.findUnique({
|
||||
where: { id: documentId },
|
||||
include: {
|
||||
recipients: {
|
||||
orderBy: [{ signingOrder: 'asc' }, { id: 'asc' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!document) {
|
||||
throw new Error('Document not found');
|
||||
}
|
||||
|
||||
const currentRecipient = document.recipients.find((r) => r.id === currentRecipientId);
|
||||
const nextRecipient = document.recipients.find(
|
||||
(r) => r.signingOrder === (currentRecipient?.signingOrder ?? 0) + 1,
|
||||
);
|
||||
|
||||
if (!nextRecipient) {
|
||||
throw new Error('Next recipient not found');
|
||||
}
|
||||
|
||||
await prisma.recipient.update({
|
||||
where: { id: nextRecipient.id },
|
||||
data: {
|
||||
email: nextSigner.email,
|
||||
name: nextSigner.name,
|
||||
},
|
||||
});
|
||||
|
||||
return nextRecipient;
|
||||
};
|
||||
|
||||
export const completeDocumentWithToken = async ({
|
||||
token,
|
||||
documentId,
|
||||
requestMetadata,
|
||||
nextSigner,
|
||||
}: CompleteDocumentWithTokenOptions) => {
|
||||
console.log('completeDocumentWithToken == document-router', token, documentId, nextSigner);
|
||||
|
||||
const document = await getDocument({ token, documentId });
|
||||
|
||||
if (document.status !== DocumentStatus.PENDING) {
|
||||
@ -112,6 +161,20 @@ export const completeDocumentWithToken = async ({
|
||||
// throw new AppError(AppErrorCode.UNAUTHORIZED, 'Invalid authentication values');
|
||||
// }
|
||||
|
||||
if (
|
||||
nextSigner &&
|
||||
document.documentMeta?.modifyNextSigner &&
|
||||
document.documentMeta?.signingOrder === DocumentSigningOrder.SEQUENTIAL
|
||||
) {
|
||||
console.log('delegateNextSigner == document-router', document.id, recipient.id, nextSigner);
|
||||
|
||||
await delegateNextSigner({
|
||||
documentId: document.id,
|
||||
currentRecipientId: recipient.id,
|
||||
nextSigner,
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.recipient.update({
|
||||
where: {
|
||||
|
||||
@ -55,6 +55,7 @@ export const ZDocumentSchema = DocumentSchema.pick({
|
||||
typedSignatureEnabled: true,
|
||||
language: true,
|
||||
emailSettings: true,
|
||||
modifyNextSigner: true,
|
||||
}).nullable(),
|
||||
recipients: ZRecipientLiteSchema.array(),
|
||||
fields: ZFieldSchema.array(),
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "DocumentMeta" ADD COLUMN "modifyNextSigner" BOOLEAN NOT NULL DEFAULT false;
|
||||
@ -390,6 +390,7 @@ model DocumentMeta {
|
||||
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
||||
redirectUrl String?
|
||||
signingOrder DocumentSigningOrder @default(PARALLEL)
|
||||
modifyNextSigner Boolean @default(false)
|
||||
typedSignatureEnabled Boolean @default(true)
|
||||
language String @default("en")
|
||||
distributionMethod DocumentDistributionMethod @default(EMAIL)
|
||||
|
||||
@ -266,15 +266,14 @@ export const documentRouter = router({
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
* Todo: Refactor to updateDocument.
|
||||
*/
|
||||
setSettingsForDocument: authenticatedProcedure
|
||||
updateDocument: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/update',
|
||||
summary: 'Update document',
|
||||
description: 'Update an existing document',
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
@ -286,9 +285,9 @@ export const documentRouter = router({
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
if (Object.values(meta).length > 0) {
|
||||
if (Object.keys(meta).length > 0) {
|
||||
await upsertDocumentMeta({
|
||||
userId: ctx.user.id,
|
||||
userId,
|
||||
teamId,
|
||||
documentId,
|
||||
subject: meta.subject,
|
||||
@ -301,6 +300,7 @@ export const documentRouter = router({
|
||||
distributionMethod: meta.distributionMethod,
|
||||
signingOrder: meta.signingOrder,
|
||||
emailSettings: meta.emailSettings,
|
||||
modifyNextSigner: meta.modifyNextSigner,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}
|
||||
|
||||
@ -251,6 +251,7 @@ export const ZUpdateDocumentRequestSchema = z.object({
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
modifyNextSigner: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
@ -437,13 +437,22 @@ export const recipientRouter = router({
|
||||
completeDocumentWithToken: procedure
|
||||
.input(ZCompleteDocumentWithTokenMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { token, documentId, authOptions } = input;
|
||||
const { token, documentId, authOptions, nextSigner } = input;
|
||||
|
||||
console.log(
|
||||
'completeDocumentWithToken == recipient-router',
|
||||
token,
|
||||
documentId,
|
||||
authOptions,
|
||||
nextSigner,
|
||||
);
|
||||
|
||||
return await completeDocumentWithToken({
|
||||
token,
|
||||
documentId,
|
||||
authOptions,
|
||||
userId: ctx.user?.id,
|
||||
nextSigner,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
@ -212,6 +212,12 @@ export const ZCompleteDocumentWithTokenMutationSchema = z.object({
|
||||
token: z.string(),
|
||||
documentId: z.number(),
|
||||
authOptions: ZRecipientActionAuthSchema.optional(),
|
||||
nextSigner: z
|
||||
.object({
|
||||
email: z.string().email(),
|
||||
name: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type TCompleteDocumentWithTokenMutationSchema = z.infer<
|
||||
|
||||
@ -49,6 +49,7 @@ export type AddSignersFormProps = {
|
||||
recipients: Recipient[];
|
||||
fields: Field[];
|
||||
signingOrder?: DocumentSigningOrder | null;
|
||||
modifyNextSigner?: boolean | null;
|
||||
isDocumentEnterprise: boolean;
|
||||
onSubmit: (_data: TAddSignersFormSchema) => void;
|
||||
isDocumentPdfLoaded: boolean;
|
||||
@ -59,6 +60,7 @@ export const AddSignersFormPartial = ({
|
||||
recipients,
|
||||
fields,
|
||||
signingOrder,
|
||||
modifyNextSigner,
|
||||
isDocumentEnterprise,
|
||||
onSubmit,
|
||||
isDocumentPdfLoaded,
|
||||
@ -107,6 +109,7 @@ export const AddSignersFormPartial = ({
|
||||
)
|
||||
: defaultRecipients,
|
||||
signingOrder: signingOrder || DocumentSigningOrder.PARALLEL,
|
||||
modifyNextSigner: modifyNextSigner ?? false,
|
||||
},
|
||||
});
|
||||
|
||||
@ -404,6 +407,35 @@ export const AddSignersFormPartial = ({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{isSigningOrderSequential && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="modifyNextSigner"
|
||||
render={({ field }) => (
|
||||
<FormItem className="mb-6 flex flex-row items-center space-x-2 space-y-0">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
id="modifyNextSigner"
|
||||
checked={field.value}
|
||||
onCheckedChange={(checked) => {
|
||||
field.onChange(checked);
|
||||
}}
|
||||
disabled={isSubmitting || hasDocumentBeenSent}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormLabel
|
||||
htmlFor="modifyNextSigner"
|
||||
className="text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
<Trans>Modify next signer</Trans>
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<DragDropContext
|
||||
onDragEnd={onDragEnd}
|
||||
sensors={[
|
||||
|
||||
@ -25,6 +25,7 @@ export const ZAddSignersFormSchema = z
|
||||
}),
|
||||
),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder),
|
||||
modifyNextSigner: z.boolean(),
|
||||
})
|
||||
.refine(
|
||||
(schema) => {
|
||||
|
||||
Reference in New Issue
Block a user