mirror of
https://github.com/documenso/documenso.git
synced 2025-11-20 03:32:14 +10:00
Compare commits
1 Commits
fix/embedd
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 11d9bde8f8 |
@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|||||||
|
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import type { EnvelopeItem, FieldType } from '@prisma/client';
|
import type { DocumentData, FieldType } from '@prisma/client';
|
||||||
import { ReadStatus, type Recipient, SendStatus, SigningStatus } from '@prisma/client';
|
import { ReadStatus, type Recipient, SendStatus, SigningStatus } from '@prisma/client';
|
||||||
import { base64 } from '@scure/base';
|
import { base64 } from '@scure/base';
|
||||||
import { ChevronsUpDown } from 'lucide-react';
|
import { ChevronsUpDown } from 'lucide-react';
|
||||||
@ -40,8 +40,7 @@ const DEFAULT_WIDTH_PX = MIN_WIDTH_PX * 2.5;
|
|||||||
|
|
||||||
export type ConfigureFieldsViewProps = {
|
export type ConfigureFieldsViewProps = {
|
||||||
configData: TConfigureEmbedFormSchema;
|
configData: TConfigureEmbedFormSchema;
|
||||||
presignToken?: string | undefined;
|
documentData?: DocumentData;
|
||||||
envelopeItem?: Pick<EnvelopeItem, 'id' | 'envelopeId'>;
|
|
||||||
defaultValues?: Partial<TConfigureFieldsFormSchema>;
|
defaultValues?: Partial<TConfigureFieldsFormSchema>;
|
||||||
onBack?: (data: TConfigureFieldsFormSchema) => void;
|
onBack?: (data: TConfigureFieldsFormSchema) => void;
|
||||||
onSubmit: (data: TConfigureFieldsFormSchema) => void;
|
onSubmit: (data: TConfigureFieldsFormSchema) => void;
|
||||||
@ -49,8 +48,7 @@ export type ConfigureFieldsViewProps = {
|
|||||||
|
|
||||||
export const ConfigureFieldsView = ({
|
export const ConfigureFieldsView = ({
|
||||||
configData,
|
configData,
|
||||||
presignToken,
|
documentData,
|
||||||
envelopeItem,
|
|
||||||
defaultValues,
|
defaultValues,
|
||||||
onBack,
|
onBack,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
@ -84,12 +82,12 @@ export const ConfigureFieldsView = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const normalizedDocumentData = useMemo(() => {
|
const normalizedDocumentData = useMemo(() => {
|
||||||
if (envelopeItem) {
|
if (documentData) {
|
||||||
return undefined;
|
return documentData.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!configData.documentData) {
|
if (!configData.documentData) {
|
||||||
return undefined;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return base64.encode(configData.documentData.data);
|
return base64.encode(configData.documentData.data);
|
||||||
@ -536,55 +534,56 @@ export const ConfigureFieldsView = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
{normalizedDocumentData ||
|
{normalizedDocumentData && (
|
||||||
(envelopeItem && (
|
<div>
|
||||||
<div>
|
<PDFViewer
|
||||||
<PDFViewer
|
overrideData={normalizedDocumentData}
|
||||||
presignToken={presignToken}
|
envelopeItem={{
|
||||||
overrideData={normalizedDocumentData}
|
id: '',
|
||||||
envelopeItem={envelopeItem}
|
envelopeId: '',
|
||||||
token={undefined}
|
}}
|
||||||
version="signed"
|
token={undefined}
|
||||||
/>
|
version="signed"
|
||||||
|
/>
|
||||||
|
|
||||||
<ElementVisible
|
<ElementVisible
|
||||||
target={`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${highestPageNumber}"]`}
|
target={`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${highestPageNumber}"]`}
|
||||||
>
|
>
|
||||||
{localFields.map((field, index) => {
|
{localFields.map((field, index) => {
|
||||||
const recipientIndex = recipients.findIndex(
|
const recipientIndex = recipients.findIndex(
|
||||||
(r) => r.id === field.recipientId,
|
(r) => r.id === field.recipientId,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FieldItem
|
<FieldItem
|
||||||
key={field.formId}
|
key={field.formId}
|
||||||
field={field}
|
field={field}
|
||||||
minHeight={MIN_HEIGHT_PX}
|
minHeight={MIN_HEIGHT_PX}
|
||||||
minWidth={MIN_WIDTH_PX}
|
minWidth={MIN_WIDTH_PX}
|
||||||
defaultHeight={DEFAULT_HEIGHT_PX}
|
defaultHeight={DEFAULT_HEIGHT_PX}
|
||||||
defaultWidth={DEFAULT_WIDTH_PX}
|
defaultWidth={DEFAULT_WIDTH_PX}
|
||||||
onResize={(node) => onFieldResize(node, index)}
|
onResize={(node) => onFieldResize(node, index)}
|
||||||
onMove={(node) => onFieldMove(node, index)}
|
onMove={(node) => onFieldMove(node, index)}
|
||||||
onRemove={() => remove(index)}
|
onRemove={() => remove(index)}
|
||||||
onDuplicate={() => onFieldCopy(null, { duplicate: true })}
|
onDuplicate={() => onFieldCopy(null, { duplicate: true })}
|
||||||
onDuplicateAllPages={() => onFieldCopy(null, { duplicateAll: true })}
|
onDuplicateAllPages={() => onFieldCopy(null, { duplicateAll: true })}
|
||||||
onFocus={() => setLastActiveField(field)}
|
onFocus={() => setLastActiveField(field)}
|
||||||
onBlur={() => setLastActiveField(null)}
|
onBlur={() => setLastActiveField(null)}
|
||||||
onAdvancedSettings={() => {
|
onAdvancedSettings={() => {
|
||||||
setCurrentField(field);
|
setCurrentField(field);
|
||||||
setShowAdvancedSettings(true);
|
setShowAdvancedSettings(true);
|
||||||
}}
|
}}
|
||||||
recipientIndex={recipientIndex}
|
recipientIndex={recipientIndex}
|
||||||
active={activeFieldId === field.formId}
|
active={activeFieldId === field.formId}
|
||||||
onFieldActivate={() => setActiveFieldId(field.formId)}
|
onFieldActivate={() => setActiveFieldId(field.formId)}
|
||||||
onFieldDeactivate={() => setActiveFieldId(null)}
|
onFieldDeactivate={() => setActiveFieldId(null)}
|
||||||
disabled={selectedRecipient?.id !== field.recipientId}
|
disabled={selectedRecipient?.id !== field.recipientId}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ElementVisible>
|
</ElementVisible>
|
||||||
</div>
|
</div>
|
||||||
))}
|
)}
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -75,7 +75,6 @@ export const loader = async ({ request, params }: Route.LoaderArgs) => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
token,
|
|
||||||
document: {
|
document: {
|
||||||
...document,
|
...document,
|
||||||
fields,
|
fields,
|
||||||
@ -87,7 +86,7 @@ export default function EmbeddingAuthoringDocumentEditPage() {
|
|||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const { document, token } = useLoaderData<typeof loader>();
|
const { document } = useLoaderData<typeof loader>();
|
||||||
|
|
||||||
const [hasFinishedInit, setHasFinishedInit] = useState(false);
|
const [hasFinishedInit, setHasFinishedInit] = useState(false);
|
||||||
|
|
||||||
@ -322,8 +321,7 @@ export default function EmbeddingAuthoringDocumentEditPage() {
|
|||||||
|
|
||||||
<ConfigureFieldsView
|
<ConfigureFieldsView
|
||||||
configData={configuration!}
|
configData={configuration!}
|
||||||
presignToken={token}
|
documentData={document.documentData}
|
||||||
envelopeItem={document.envelopeItems[0]}
|
|
||||||
defaultValues={fields ?? undefined}
|
defaultValues={fields ?? undefined}
|
||||||
onBack={canGoBack ? handleBackToConfig : undefined}
|
onBack={canGoBack ? handleBackToConfig : undefined}
|
||||||
onSubmit={handleConfigureFieldsSubmit}
|
onSubmit={handleConfigureFieldsSubmit}
|
||||||
|
|||||||
@ -75,7 +75,6 @@ export const loader = async ({ request, params }: Route.LoaderArgs) => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
token,
|
|
||||||
template: {
|
template: {
|
||||||
...template,
|
...template,
|
||||||
fields,
|
fields,
|
||||||
@ -87,7 +86,7 @@ export default function EmbeddingAuthoringTemplateEditPage() {
|
|||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const { template, token } = useLoaderData<typeof loader>();
|
const { template } = useLoaderData<typeof loader>();
|
||||||
|
|
||||||
const [hasFinishedInit, setHasFinishedInit] = useState(false);
|
const [hasFinishedInit, setHasFinishedInit] = useState(false);
|
||||||
|
|
||||||
@ -322,8 +321,7 @@ export default function EmbeddingAuthoringTemplateEditPage() {
|
|||||||
|
|
||||||
<ConfigureFieldsView
|
<ConfigureFieldsView
|
||||||
configData={configuration!}
|
configData={configuration!}
|
||||||
presignToken={token}
|
documentData={template.templateDocumentData}
|
||||||
envelopeItem={template.envelopeItems[0]}
|
|
||||||
defaultValues={fields ?? undefined}
|
defaultValues={fields ?? undefined}
|
||||||
onBack={canGoBack ? handleBackToConfig : undefined}
|
onBack={canGoBack ? handleBackToConfig : undefined}
|
||||||
onSubmit={handleConfigureFieldsSubmit}
|
onSubmit={handleConfigureFieldsSubmit}
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { Hono } from 'hono';
|
|||||||
import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session';
|
import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session';
|
||||||
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
|
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
|
||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
import { verifyEmbeddingPresignToken } from '@documenso/lib/server-only/embedding-presign/verify-embedding-presign-token';
|
|
||||||
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
|
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
|
||||||
import { putNormalizedPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
import { putNormalizedPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
||||||
import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
|
import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
|
||||||
@ -17,7 +16,6 @@ import {
|
|||||||
type TGetPresignedPostUrlResponse,
|
type TGetPresignedPostUrlResponse,
|
||||||
ZGetEnvelopeItemFileDownloadRequestParamsSchema,
|
ZGetEnvelopeItemFileDownloadRequestParamsSchema,
|
||||||
ZGetEnvelopeItemFileRequestParamsSchema,
|
ZGetEnvelopeItemFileRequestParamsSchema,
|
||||||
ZGetEnvelopeItemFileRequestQuerySchema,
|
|
||||||
ZGetEnvelopeItemFileTokenDownloadRequestParamsSchema,
|
ZGetEnvelopeItemFileTokenDownloadRequestParamsSchema,
|
||||||
ZGetEnvelopeItemFileTokenRequestParamsSchema,
|
ZGetEnvelopeItemFileTokenRequestParamsSchema,
|
||||||
ZGetPresignedPostUrlRequestSchema,
|
ZGetPresignedPostUrlRequestSchema,
|
||||||
@ -70,24 +68,12 @@ export const filesRoute = new Hono<HonoEnv>()
|
|||||||
.get(
|
.get(
|
||||||
'/envelope/:envelopeId/envelopeItem/:envelopeItemId',
|
'/envelope/:envelopeId/envelopeItem/:envelopeItemId',
|
||||||
sValidator('param', ZGetEnvelopeItemFileRequestParamsSchema),
|
sValidator('param', ZGetEnvelopeItemFileRequestParamsSchema),
|
||||||
sValidator('query', ZGetEnvelopeItemFileRequestQuerySchema),
|
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const { envelopeId, envelopeItemId } = c.req.valid('param');
|
const { envelopeId, envelopeItemId } = c.req.valid('param');
|
||||||
const { token } = c.req.query();
|
|
||||||
|
|
||||||
const session = await getOptionalSession(c);
|
const session = await getOptionalSession(c);
|
||||||
|
|
||||||
let userId = session.user?.id;
|
if (!session.user) {
|
||||||
|
|
||||||
if (token) {
|
|
||||||
const presignToken = await verifyEmbeddingPresignToken({
|
|
||||||
token,
|
|
||||||
}).catch(() => undefined);
|
|
||||||
|
|
||||||
userId = presignToken?.userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!userId) {
|
|
||||||
return c.json({ error: 'Unauthorized' }, 401);
|
return c.json({ error: 'Unauthorized' }, 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,7 +104,7 @@ export const filesRoute = new Hono<HonoEnv>()
|
|||||||
}
|
}
|
||||||
|
|
||||||
const team = await getTeamById({
|
const team = await getTeamById({
|
||||||
userId: userId,
|
userId: session.user.id,
|
||||||
teamId: envelope.teamId,
|
teamId: envelope.teamId,
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|||||||
@ -36,14 +36,6 @@ export type TGetEnvelopeItemFileRequestParams = z.infer<
|
|||||||
typeof ZGetEnvelopeItemFileRequestParamsSchema
|
typeof ZGetEnvelopeItemFileRequestParamsSchema
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const ZGetEnvelopeItemFileRequestQuerySchema = z.object({
|
|
||||||
token: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TGetEnvelopeItemFileRequestQuery = z.infer<
|
|
||||||
typeof ZGetEnvelopeItemFileRequestQuerySchema
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const ZGetEnvelopeItemFileTokenRequestParamsSchema = z.object({
|
export const ZGetEnvelopeItemFileTokenRequestParamsSchema = z.object({
|
||||||
token: z.string().min(1),
|
token: z.string().min(1),
|
||||||
envelopeItemId: z.string().min(1),
|
envelopeItemId: z.string().min(1),
|
||||||
|
|||||||
@ -25,7 +25,6 @@ import { signPdf } from '@documenso/signing';
|
|||||||
|
|
||||||
import { AppError, AppErrorCode } from '../../../errors/app-error';
|
import { AppError, AppErrorCode } from '../../../errors/app-error';
|
||||||
import { sendCompletedEmail } from '../../../server-only/document/send-completed-email';
|
import { sendCompletedEmail } from '../../../server-only/document/send-completed-email';
|
||||||
import PostHogServerClient from '../../../server-only/feature-flags/get-post-hog-server-client';
|
|
||||||
import { getAuditLogsPdf } from '../../../server-only/htmltopdf/get-audit-logs-pdf';
|
import { getAuditLogsPdf } from '../../../server-only/htmltopdf/get-audit-logs-pdf';
|
||||||
import { getCertificatePdf } from '../../../server-only/htmltopdf/get-certificate-pdf';
|
import { getCertificatePdf } from '../../../server-only/htmltopdf/get-certificate-pdf';
|
||||||
import { addRejectionStampToPdf } from '../../../server-only/pdf/add-rejection-stamp-to-pdf';
|
import { addRejectionStampToPdf } from '../../../server-only/pdf/add-rejection-stamp-to-pdf';
|
||||||
@ -62,171 +61,120 @@ export const run = async ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { documentId, sendEmail = true, isResealing = false, requestMetadata } = payload;
|
const { documentId, sendEmail = true, isResealing = false, requestMetadata } = payload;
|
||||||
|
|
||||||
const envelope = await prisma.envelope.findFirstOrThrow({
|
const { envelopeId, envelopeStatus, isRejected } = await io.runTask('seal-document', async () => {
|
||||||
where: {
|
const envelope = await prisma.envelope.findFirstOrThrow({
|
||||||
type: EnvelopeType.DOCUMENT,
|
where: {
|
||||||
secondaryId: mapDocumentIdToSecondaryId(documentId),
|
type: EnvelopeType.DOCUMENT,
|
||||||
},
|
secondaryId: mapDocumentIdToSecondaryId(documentId),
|
||||||
include: {
|
},
|
||||||
documentMeta: true,
|
include: {
|
||||||
recipients: true,
|
documentMeta: true,
|
||||||
envelopeItems: {
|
recipients: true,
|
||||||
include: {
|
envelopeItems: {
|
||||||
documentData: true,
|
include: {
|
||||||
field: {
|
documentData: true,
|
||||||
include: {
|
field: {
|
||||||
signature: true,
|
include: {
|
||||||
|
signature: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (envelope.envelopeItems.length === 0) {
|
|
||||||
throw new Error('At least one envelope item required');
|
|
||||||
}
|
|
||||||
|
|
||||||
const settings = await getTeamSettings({
|
|
||||||
userId: envelope.userId,
|
|
||||||
teamId: envelope.teamId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const isComplete =
|
|
||||||
envelope.recipients.some((recipient) => recipient.signingStatus === SigningStatus.REJECTED) ||
|
|
||||||
envelope.recipients.every((recipient) => recipient.signingStatus === SigningStatus.SIGNED);
|
|
||||||
|
|
||||||
if (!isComplete) {
|
|
||||||
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
|
||||||
message: 'Document is not complete',
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Seems silly but we need to do this in case the job is re-ran
|
if (envelope.envelopeItems.length === 0) {
|
||||||
// after it has already run through the update task further below.
|
throw new Error('At least one envelope item required');
|
||||||
// eslint-disable-next-line @typescript-eslint/require-await
|
}
|
||||||
const documentStatus = await io.runTask('get-document-status', async () => {
|
|
||||||
return envelope.status;
|
|
||||||
});
|
|
||||||
|
|
||||||
// This is the same case as above.
|
const settings = await getTeamSettings({
|
||||||
let envelopeItems = await io.runTask(
|
userId: envelope.userId,
|
||||||
'get-document-data-id',
|
teamId: envelope.teamId,
|
||||||
// eslint-disable-next-line @typescript-eslint/require-await
|
});
|
||||||
async () => {
|
|
||||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
||||||
return envelope.envelopeItems.map(({ field, ...rest }) => ({
|
|
||||||
...rest,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (envelopeItems.length < 1) {
|
const isComplete =
|
||||||
throw new Error(`Document ${envelope.id} has no envelope items`);
|
envelope.recipients.some((recipient) => recipient.signingStatus === SigningStatus.REJECTED) ||
|
||||||
}
|
envelope.recipients.every((recipient) => recipient.signingStatus === SigningStatus.SIGNED);
|
||||||
|
|
||||||
const recipients = await prisma.recipient.findMany({
|
if (!isComplete) {
|
||||||
where: {
|
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
||||||
envelopeId: envelope.id,
|
message: 'Document is not complete',
|
||||||
role: {
|
});
|
||||||
not: RecipientRole.CC,
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Determine if the document has been rejected by checking if any recipient has rejected it
|
let envelopeItems = envelope.envelopeItems;
|
||||||
const rejectedRecipient = recipients.find(
|
|
||||||
(recipient) => recipient.signingStatus === SigningStatus.REJECTED,
|
|
||||||
);
|
|
||||||
|
|
||||||
const isRejected = Boolean(rejectedRecipient);
|
if (envelopeItems.length < 1) {
|
||||||
|
throw new Error(`Document ${envelope.id} has no envelope items`);
|
||||||
|
}
|
||||||
|
|
||||||
// Get the rejection reason from the rejected recipient
|
const recipients = await prisma.recipient.findMany({
|
||||||
const rejectionReason = rejectedRecipient?.rejectionReason ?? '';
|
|
||||||
|
|
||||||
const fields = await prisma.field.findMany({
|
|
||||||
where: {
|
|
||||||
envelopeId: envelope.id,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
signature: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Skip the field check if the document is rejected
|
|
||||||
if (!isRejected && fieldsContainUnsignedRequiredField(fields)) {
|
|
||||||
throw new Error(`Document ${envelope.id} has unsigned required fields`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isResealing) {
|
|
||||||
// If we're resealing we want to use the initial data for the document
|
|
||||||
// so we aren't placing fields on top of eachother.
|
|
||||||
envelopeItems = envelopeItems.map((envelopeItem) => ({
|
|
||||||
...envelopeItem,
|
|
||||||
documentData: {
|
|
||||||
...envelopeItem.documentData,
|
|
||||||
data: envelopeItem.documentData.initialData,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!envelope.qrToken) {
|
|
||||||
await prisma.envelope.update({
|
|
||||||
where: {
|
where: {
|
||||||
id: envelope.id,
|
envelopeId: envelope.id,
|
||||||
},
|
role: {
|
||||||
data: {
|
not: RecipientRole.CC,
|
||||||
qrToken: prefixedId('qr'),
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const legacyDocumentId = mapSecondaryIdToDocumentId(envelope.secondaryId);
|
// Determine if the document has been rejected by checking if any recipient has rejected it
|
||||||
|
const rejectedRecipient = recipients.find(
|
||||||
|
(recipient) => recipient.signingStatus === SigningStatus.REJECTED,
|
||||||
|
);
|
||||||
|
|
||||||
const { certificateData, auditLogData } = await getCertificateAndAuditLogData({
|
const isRejected = Boolean(rejectedRecipient);
|
||||||
legacyDocumentId,
|
|
||||||
documentMeta: envelope.documentMeta,
|
|
||||||
settings,
|
|
||||||
});
|
|
||||||
|
|
||||||
// !: The commented out code is our desired implementation but we're seemingly
|
// Get the rejection reason from the rejected recipient
|
||||||
// !: running into issues with inngest parallelism in production.
|
const rejectionReason = rejectedRecipient?.rejectionReason ?? '';
|
||||||
// !: Until this is resolved we will do this sequentially which is slower but
|
|
||||||
// !: will actually work.
|
|
||||||
// const decoratePromises: Array<Promise<{ oldDocumentDataId: string; newDocumentDataId: string }>> =
|
|
||||||
// [];
|
|
||||||
|
|
||||||
// for (const envelopeItem of envelopeItems) {
|
const fields = await prisma.field.findMany({
|
||||||
// const task = io.runTask(`decorate-${envelopeItem.id}`, async () => {
|
where: {
|
||||||
// const envelopeItemFields = envelope.envelopeItems.find(
|
envelopeId: envelope.id,
|
||||||
// (item) => item.id === envelopeItem.id,
|
},
|
||||||
// )?.field;
|
include: {
|
||||||
|
signature: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// if (!envelopeItemFields) {
|
// Skip the field check if the document is rejected
|
||||||
// throw new Error(`Envelope item fields not found for envelope item ${envelopeItem.id}`);
|
if (!isRejected && fieldsContainUnsignedRequiredField(fields)) {
|
||||||
// }
|
throw new Error(`Document ${envelope.id} has unsigned required fields`);
|
||||||
|
}
|
||||||
|
|
||||||
// return decorateAndSignPdf({
|
if (isResealing) {
|
||||||
// envelope,
|
// If we're resealing we want to use the initial data for the document
|
||||||
// envelopeItem,
|
// so we aren't placing fields on top of eachother.
|
||||||
// envelopeItemFields,
|
envelopeItems = envelopeItems.map((envelopeItem) => ({
|
||||||
// isRejected,
|
...envelopeItem,
|
||||||
// rejectionReason,
|
documentData: {
|
||||||
// certificateData,
|
...envelopeItem.documentData,
|
||||||
// auditLogData,
|
data: envelopeItem.documentData.initialData,
|
||||||
// });
|
},
|
||||||
// });
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
// decoratePromises.push(task);
|
if (!envelope.qrToken) {
|
||||||
// }
|
await prisma.envelope.update({
|
||||||
|
where: {
|
||||||
|
id: envelope.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
qrToken: prefixedId('qr'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// const newDocumentData = await Promise.all(decoratePromises);
|
const legacyDocumentId = mapSecondaryIdToDocumentId(envelope.secondaryId);
|
||||||
|
|
||||||
// TODO: Remove once parallelization is working
|
const { certificateData, auditLogData } = await getCertificateAndAuditLogData({
|
||||||
const newDocumentData: Array<{ oldDocumentDataId: string; newDocumentDataId: string }> = [];
|
legacyDocumentId,
|
||||||
|
documentMeta: envelope.documentMeta,
|
||||||
|
settings,
|
||||||
|
});
|
||||||
|
|
||||||
for (const envelopeItem of envelopeItems) {
|
const newDocumentData: Array<{ oldDocumentDataId: string; newDocumentDataId: string }> = [];
|
||||||
const result = await io.runTask(`decorate-${envelopeItem.id}`, async () => {
|
|
||||||
|
for (const envelopeItem of envelopeItems) {
|
||||||
const envelopeItemFields = envelope.envelopeItems.find(
|
const envelopeItemFields = envelope.envelopeItems.find(
|
||||||
(item) => item.id === envelopeItem.id,
|
(item) => item.id === envelopeItem.id,
|
||||||
)?.field;
|
)?.field;
|
||||||
@ -235,7 +183,7 @@ export const run = async ({
|
|||||||
throw new Error(`Envelope item fields not found for envelope item ${envelopeItem.id}`);
|
throw new Error(`Envelope item fields not found for envelope item ${envelopeItem.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return decorateAndSignPdf({
|
const result = await decorateAndSignPdf({
|
||||||
envelope,
|
envelope,
|
||||||
envelopeItem,
|
envelopeItem,
|
||||||
envelopeItemFields,
|
envelopeItemFields,
|
||||||
@ -244,25 +192,10 @@ export const run = async ({
|
|||||||
certificateData,
|
certificateData,
|
||||||
auditLogData,
|
auditLogData,
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
newDocumentData.push(result);
|
newDocumentData.push(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
const postHog = PostHogServerClient();
|
|
||||||
|
|
||||||
if (postHog) {
|
|
||||||
postHog.capture({
|
|
||||||
distinctId: nanoid(),
|
|
||||||
event: 'App: Document Sealed',
|
|
||||||
properties: {
|
|
||||||
documentId: envelope.id,
|
|
||||||
isRejected,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await io.runTask('update-document', async () => {
|
|
||||||
await prisma.$transaction(async (tx) => {
|
await prisma.$transaction(async (tx) => {
|
||||||
for (const { oldDocumentDataId, newDocumentDataId } of newDocumentData) {
|
for (const { oldDocumentDataId, newDocumentDataId } of newDocumentData) {
|
||||||
const newData = await tx.documentData.findFirstOrThrow({
|
const newData = await tx.documentData.findFirstOrThrow({
|
||||||
@ -304,18 +237,24 @@ export const run = async ({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
envelopeId: envelope.id,
|
||||||
|
envelopeStatus: envelope.status,
|
||||||
|
isRejected,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
await io.runTask('send-completed-email', async () => {
|
await io.runTask('send-completed-email', async () => {
|
||||||
let shouldSendCompletedEmail = sendEmail && !isResealing && !isRejected;
|
let shouldSendCompletedEmail = sendEmail && !isResealing && !isRejected;
|
||||||
|
|
||||||
if (isResealing && !isDocumentCompleted(envelope.status)) {
|
if (isResealing && !isDocumentCompleted(envelopeStatus)) {
|
||||||
shouldSendCompletedEmail = sendEmail;
|
shouldSendCompletedEmail = sendEmail;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldSendCompletedEmail) {
|
if (shouldSendCompletedEmail) {
|
||||||
await sendCompletedEmail({
|
await sendCompletedEmail({
|
||||||
id: { type: 'envelopeId', id: envelope.id },
|
id: { type: 'envelopeId', id: envelopeId },
|
||||||
requestMetadata,
|
requestMetadata,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -323,7 +262,7 @@ export const run = async ({
|
|||||||
|
|
||||||
const updatedEnvelope = await prisma.envelope.findFirstOrThrow({
|
const updatedEnvelope = await prisma.envelope.findFirstOrThrow({
|
||||||
where: {
|
where: {
|
||||||
id: envelope.id,
|
id: envelopeId,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
documentMeta: true,
|
documentMeta: true,
|
||||||
|
|||||||
@ -8,17 +8,15 @@ export type EnvelopeItemPdfUrlOptions =
|
|||||||
envelopeItem: Pick<EnvelopeItem, 'id' | 'envelopeId'>;
|
envelopeItem: Pick<EnvelopeItem, 'id' | 'envelopeId'>;
|
||||||
token: string | undefined;
|
token: string | undefined;
|
||||||
version: 'original' | 'signed';
|
version: 'original' | 'signed';
|
||||||
presignToken?: undefined;
|
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'view';
|
type: 'view';
|
||||||
envelopeItem: Pick<EnvelopeItem, 'id' | 'envelopeId'>;
|
envelopeItem: Pick<EnvelopeItem, 'id' | 'envelopeId'>;
|
||||||
token: string | undefined;
|
token: string | undefined;
|
||||||
presignToken?: string | undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getEnvelopeItemPdfUrl = (options: EnvelopeItemPdfUrlOptions) => {
|
export const getEnvelopeItemPdfUrl = (options: EnvelopeItemPdfUrlOptions) => {
|
||||||
const { envelopeItem, token, type, presignToken } = options;
|
const { envelopeItem, token, type } = options;
|
||||||
|
|
||||||
const { id, envelopeId } = envelopeItem;
|
const { id, envelopeId } = envelopeItem;
|
||||||
|
|
||||||
@ -26,11 +24,11 @@ export const getEnvelopeItemPdfUrl = (options: EnvelopeItemPdfUrlOptions) => {
|
|||||||
const version = options.version;
|
const version = options.version;
|
||||||
|
|
||||||
return token
|
return token
|
||||||
? `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/token/${token}/envelopeItem/${id}/download/${version}${presignToken ? `?presignToken=${presignToken}` : ''}`
|
? `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/token/${token}/envelopeItem/${id}/download/${version}`
|
||||||
: `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/envelope/${envelopeId}/envelopeItem/${id}/download/${version}`;
|
: `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/envelope/${envelopeId}/envelopeItem/${id}/download/${version}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return token
|
return token
|
||||||
? `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/token/${token}/envelopeItem/${id}${presignToken ? `?presignToken=${presignToken}` : ''}`
|
? `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/token/${token}/envelopeItem/${id}`
|
||||||
: `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/envelope/${envelopeId}/envelopeItem/${id}`;
|
: `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/envelope/${envelopeId}/envelopeItem/${id}`;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -56,7 +56,6 @@ export type PDFViewerProps = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
envelopeItem: Pick<EnvelopeItem, 'id' | 'envelopeId'>;
|
envelopeItem: Pick<EnvelopeItem, 'id' | 'envelopeId'>;
|
||||||
token: string | undefined;
|
token: string | undefined;
|
||||||
presignToken?: string | undefined;
|
|
||||||
version: 'original' | 'signed';
|
version: 'original' | 'signed';
|
||||||
onDocumentLoad?: (_doc: LoadedPDFDocument) => void;
|
onDocumentLoad?: (_doc: LoadedPDFDocument) => void;
|
||||||
onPageClick?: OnPDFViewerPageClick;
|
onPageClick?: OnPDFViewerPageClick;
|
||||||
@ -68,7 +67,6 @@ export const PDFViewer = ({
|
|||||||
className,
|
className,
|
||||||
envelopeItem,
|
envelopeItem,
|
||||||
token,
|
token,
|
||||||
presignToken,
|
|
||||||
version,
|
version,
|
||||||
onDocumentLoad,
|
onDocumentLoad,
|
||||||
onPageClick,
|
onPageClick,
|
||||||
@ -168,7 +166,6 @@ export const PDFViewer = ({
|
|||||||
type: 'view',
|
type: 'view',
|
||||||
envelopeItem: envelopeItem,
|
envelopeItem: envelopeItem,
|
||||||
token,
|
token,
|
||||||
presignToken,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const bytes = await fetch(documentUrl).then(async (res) => await res.arrayBuffer());
|
const bytes = await fetch(documentUrl).then(async (res) => await res.arrayBuffer());
|
||||||
|
|||||||
Reference in New Issue
Block a user