429 lines
15 KiB
JavaScript
429 lines
15 KiB
JavaScript
|
|
const { Documenso } = require("@documenso/sdk-typescript");
|
|
const axios = require("axios");
|
|
const { jsrAuthString } = require("../utils/utils");
|
|
const logger = require("../utils/logger");
|
|
const DOCUMENSO_API_KEY = "api_asojim0czruv13ud";//Done on a by team basis,
|
|
const documenso = new Documenso({
|
|
apiKey: DOCUMENSO_API_KEY,//Done on a by team basis,
|
|
serverURL: "https://stg-app.documenso.com/api/v2",
|
|
});
|
|
const JSR_SERVER = "https://reports.test.imex.online";
|
|
const jsreport = require("@jsreport/nodejs-client");
|
|
const { QUERY_JOB_FOR_SIGNATURE, INSERT_ESIGNATURE_DOCUMENT, DISTRIBUTE_ESIGNATURE_DOCUMENT, QUERY_ESIGNATURE_BY_EXTERNAL_ID, UPDATE_ESIGNATURE_DOCUMENT } = require("../graphql-client/queries");
|
|
const _ = require("lodash");
|
|
|
|
function parseJsonField(value, fallback = null) {
|
|
if (value === undefined || value === null) {
|
|
return fallback;
|
|
}
|
|
|
|
if (typeof value !== "string") {
|
|
return value;
|
|
}
|
|
|
|
try {
|
|
return JSON.parse(value);
|
|
} catch {
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
function getDefaultEsignData({ esigData, bodyshop, fileName }) {
|
|
const fallbackTitle = fileName || `Esign request from ${bodyshop.shopname}`;
|
|
|
|
return {
|
|
...esigData,
|
|
title: esigData?.title || fallbackTitle,
|
|
subject: esigData?.subject || `Esign request from ${bodyshop.shopname}`,
|
|
message: esigData?.message || `Please review and sign the document from ${bodyshop.shopname}.`
|
|
};
|
|
}
|
|
|
|
async function createEsignDocumentFromPdf({ client, req, bodyshop, pdfBuffer, esigData, fileName }) {
|
|
const resolvedEsigData = getDefaultEsignData({ esigData, bodyshop, fileName });
|
|
const fileBlob = new Blob([pdfBuffer], { type: "application/pdf" });
|
|
const jobid = req.body.jobid;
|
|
|
|
const { jobs_by_pk: jobData } = await client.request(QUERY_JOB_FOR_SIGNATURE, { jobid });
|
|
const recipients = [{
|
|
email: "patrick@imexsystems.ca",
|
|
name: `${jobData.ownr_fn} ${jobData.ownr_ln}`,
|
|
role: "SIGNER",
|
|
}];
|
|
|
|
const createDocumentResponse = await documenso.documents.create({
|
|
payload: {
|
|
title: resolvedEsigData.title,
|
|
externalId: `${jobid}|${req.user?.email}`,
|
|
recipients,
|
|
meta: {
|
|
timezone: bodyshop.timezone,
|
|
dateFormat: "MM/dd/yyyy hh:mm a",
|
|
language: "en",
|
|
subject: resolvedEsigData.subject,
|
|
message: resolvedEsigData.message,
|
|
}
|
|
},
|
|
file: fileBlob
|
|
});
|
|
|
|
const documentResult = await documenso.documents.get({
|
|
documentId: createDocumentResponse.id,
|
|
});
|
|
|
|
if (resolvedEsigData?.fields && resolvedEsigData.fields.length > 0) {
|
|
try {
|
|
await documenso.envelopes.fields.createMany({
|
|
envelopeId: createDocumentResponse.envelopeId,
|
|
data: resolvedEsigData.fields.map(sigField => ({ ...sigField, recipientId: documentResult.recipients[0].id, }))
|
|
});
|
|
} catch (error) {
|
|
logger.log(`esig-new-fields-error`, "ERROR", "esig", "api", {
|
|
message: error.message, stack: error.stack,
|
|
body: req.body
|
|
});
|
|
}
|
|
}
|
|
|
|
const presignToken = await documenso.embedding.embeddingPresignCreateEmbeddingPresignToken({});
|
|
|
|
await client.request(INSERT_ESIGNATURE_DOCUMENT, {
|
|
audit: {
|
|
jobid,
|
|
bodyshopid: bodyshop.id,
|
|
operation: `Esignature document created. Subject: ${resolvedEsigData.subject || "No subject"}, Message: ${resolvedEsigData.message || "No message"}. Document ID: ${createDocumentResponse.id} Envlope ID: ${createDocumentResponse.envelopeId}`,
|
|
useremail: req.user?.email,
|
|
type: 'esig-create'
|
|
},
|
|
esig: {
|
|
jobid,
|
|
external_document_id: createDocumentResponse.id.toString(),
|
|
subject: resolvedEsigData.subject || "No subject",
|
|
message: resolvedEsigData.message || "No message",
|
|
title: resolvedEsigData.title || "No title",
|
|
status: "DRAFT",
|
|
recipients: recipients,
|
|
}
|
|
});
|
|
|
|
return {
|
|
token: presignToken.token,
|
|
documentId: createDocumentResponse.id,
|
|
envelopeId: createDocumentResponse.envelopeId
|
|
};
|
|
}
|
|
|
|
|
|
async function distributeDocument(req, res) {
|
|
try {
|
|
const client = req.userGraphQLClient;
|
|
|
|
const { documentId } = req.body;
|
|
const distributeResult = await documenso.documents.distribute({
|
|
documentId,
|
|
});
|
|
|
|
await client.request(DISTRIBUTE_ESIGNATURE_DOCUMENT, {
|
|
external_document_id: documentId.toString(),
|
|
esig_update: {
|
|
status: "SENT"
|
|
},
|
|
audit: {
|
|
jobid: req.body.jobid,
|
|
bodyshopid: req.body.bodyshopid,
|
|
operation: `Esignature document with title ${distributeResult.title} (ID: ${documentId}) distributed to recipients.`,
|
|
useremail: req.user?.email,
|
|
type: 'esig-distribute'
|
|
}
|
|
})
|
|
|
|
res.json({ success: true, distributeResult });
|
|
} catch (error) {
|
|
|
|
logger.log(`esig-distribute-error`, "ERROR", "esig", "api", {
|
|
message: error.message, stack: error.stack,
|
|
body: req.body
|
|
});
|
|
res.status(500).json({ error: "An error occurred while distributing the document.", message: error.message });
|
|
}
|
|
}
|
|
|
|
async function deleteDocument(req, res) {
|
|
try {
|
|
//TODO: Add in logic to check if doc exists, is deletable etc.
|
|
const client = req.userGraphQLClient;
|
|
|
|
const { documentId } = req.body;
|
|
const { esignature_documents } = await client.request(QUERY_ESIGNATURE_BY_EXTERNAL_ID, { external_document_id: documentId.toString() });
|
|
|
|
if (!esignature_documents || esignature_documents.length === 0) {
|
|
//return res.status(404).json({ error: "Document not found" });
|
|
}
|
|
const deleteResult = await documenso.documents.delete({
|
|
documentId: (documentId)
|
|
});
|
|
|
|
await client.request(UPDATE_ESIGNATURE_DOCUMENT, {
|
|
external_document_id: documentId.toString(),
|
|
esig_update: {
|
|
status: "DELETED"
|
|
}
|
|
})
|
|
res.json({ success: true, deleteResult });
|
|
} catch (error) {
|
|
|
|
logger.log(`esig-delete-error`, "ERROR", "esig", "api", {
|
|
message: error.message, stack: error.stack,
|
|
body: req.body
|
|
});
|
|
res.status(500).json({ error: "An error occurred while deleting the document." });
|
|
}
|
|
}
|
|
|
|
async function viewDocument(req, res) {
|
|
try {
|
|
const { documentId } = req.body;
|
|
const document = await documenso.document.documentDownload({
|
|
documentId: parseInt(documentId)
|
|
});
|
|
res.json({ success: true, document });
|
|
} catch (error) {
|
|
|
|
logger.log(`esig-view-error`, "ERROR", "esig", "api", {
|
|
message: error.message, stack: error.stack,
|
|
body: req.body
|
|
});
|
|
res.status(500).json({ error: "An error occurred while retrieving the document.", message: error.message });
|
|
}
|
|
}
|
|
|
|
async function newEsignDocument(req, res) {
|
|
try {
|
|
const client = req.userGraphQLClient;
|
|
const { bodyshop } = req.body;
|
|
const { pdf: fileBuffer, esigData } = await RenderTemplate({ client, req });
|
|
const result = await createEsignDocumentFromPdf({
|
|
client,
|
|
req,
|
|
bodyshop,
|
|
pdfBuffer: fileBuffer,
|
|
esigData
|
|
});
|
|
|
|
res.json(result);
|
|
}
|
|
catch (error) {
|
|
logger.log(`esig-new-error`, "ERROR", "esig", "api", {
|
|
message: error.message, stack: error.stack,
|
|
body: req.body
|
|
});
|
|
res.status(500).json({ error: "An error occurred while creating the e-sign document." });
|
|
}
|
|
}
|
|
|
|
async function newCustomEsignDocument(req, res) {
|
|
try {
|
|
const client = req.userGraphQLClient;
|
|
const bodyshop = parseJsonField(req.body.bodyshop, req.body.bodyshop);
|
|
const esigData = parseJsonField(req.body.esigData, {});
|
|
const uploadedDocument = req.file;
|
|
|
|
if (!uploadedDocument?.buffer) {
|
|
return res.status(400).json({ error: "A PDF document is required." });
|
|
}
|
|
|
|
if (uploadedDocument.mimetype !== "application/pdf") {
|
|
return res.status(400).json({ error: "Only PDF documents can be used for e-signature." });
|
|
}
|
|
|
|
req.body.bodyshop = bodyshop;
|
|
|
|
const fileName = uploadedDocument.originalname?.replace(/\.[^.]+$/, "") || undefined;
|
|
const result = await createEsignDocumentFromPdf({
|
|
client,
|
|
req,
|
|
bodyshop,
|
|
pdfBuffer: uploadedDocument.buffer,
|
|
esigData,
|
|
fileName
|
|
});
|
|
|
|
res.json(result);
|
|
}
|
|
catch (error) {
|
|
logger.log(`esig-new-custom-error`, "ERROR", "esig", "api", {
|
|
message: error.message, stack: error.stack,
|
|
body: _.omit(req.body, ["bodyshop"]) // bodyshop can be large, so we omit it from the logs
|
|
});
|
|
res.status(500).json({ error: "An error occurred while creating the custom e-sign document.", message: error.message });
|
|
}
|
|
}
|
|
|
|
async function RenderTemplate({ req }) {
|
|
//TODO Refactor to pull
|
|
const jsrAuth = jsrAuthString()
|
|
|
|
const jsreportClient = new jsreport("https://reports.test.imex.online", process.env.JSR_USER, process.env.JSR_PASSWORD);
|
|
const { templateObject, bodyshop } = req.body;
|
|
let { contextData, useShopSpecificTemplate, shopSpecificFolder, esigData } = await fetchContextData({ templateObject, jsrAuth, req });
|
|
|
|
const { ignoreCustomMargins } = { ignoreCustomMargins: false }// Templates[templateObject.name];
|
|
let reportRequest = {
|
|
template: {
|
|
name: useShopSpecificTemplate ? `/${bodyshop.imexshopid}/${templateObject.name}` : `/${templateObject.name}`,
|
|
|
|
recipe: "chrome-pdf",
|
|
...(!ignoreCustomMargins && {
|
|
chrome: {
|
|
marginTop:
|
|
bodyshop.logo_img_path &&
|
|
bodyshop.logo_img_path.headerMargin &&
|
|
bodyshop.logo_img_path.headerMargin > 36
|
|
? bodyshop.logo_img_path.headerMargin
|
|
: "36px",
|
|
marginBottom:
|
|
bodyshop.logo_img_path &&
|
|
bodyshop.logo_img_path.footerMargin &&
|
|
bodyshop.logo_img_path.footerMargin > 50
|
|
? bodyshop.logo_img_path.footerMargin
|
|
: "50px"
|
|
}
|
|
}),
|
|
},
|
|
data: {
|
|
...contextData,
|
|
...templateObject.variables,
|
|
...templateObject.context,
|
|
headerpath: shopSpecificFolder ? `/${bodyshop.imexshopid}/header.html` : `/GENERIC/header.html`,
|
|
footerpath: shopSpecificFolder ? `/${bodyshop.imexshopid}/footer.html` : `/GENERIC/footer.html`,
|
|
bodyshop: bodyshop,
|
|
esignature: true,
|
|
filters: templateObject?.filters,
|
|
sorters: templateObject?.sorters,
|
|
offset: bodyshop.timezone, //dayjs().utcOffset(),
|
|
defaultSorters: templateObject?.defaultSorters
|
|
}
|
|
};
|
|
const render = await jsreportClient.render(reportRequest);
|
|
|
|
//Check render object and download. It should be the PDF?
|
|
const pdfBuffer = await render.body()
|
|
return { pdf: pdfBuffer, esigData }
|
|
}
|
|
|
|
const fetchContextData = async ({ templateObject, jsrAuth, req, }) => {
|
|
const { bodyshop } = req.body
|
|
|
|
|
|
const folders = await axios.get(`${JSR_SERVER}/odata/folders`, {
|
|
headers: { Authorization: jsrAuth }
|
|
});
|
|
const shopSpecificFolder = folders.data.value.find((f) => f.name === bodyshop.imexshopid);
|
|
|
|
const jsReportQueries = await axios.get(
|
|
`${JSR_SERVER}/odata/assets?$filter=name eq '${templateObject.name}.query'`,
|
|
{ headers: { Authorization: jsrAuth } }
|
|
);
|
|
const jsReportEsig = await axios.get(
|
|
`${JSR_SERVER}/odata/assets?$filter=name eq '${templateObject.name}.esig'`,
|
|
{ headers: { Authorization: jsrAuth } }
|
|
);
|
|
|
|
let templateQueryToExecute;
|
|
let esigData;
|
|
let useShopSpecificTemplate = false;
|
|
// let shopSpecificTemplate;
|
|
|
|
if (shopSpecificFolder) {
|
|
let shopSpecificTemplate = jsReportQueries.data.value.find(
|
|
(f) => f?.folder?.shortid === shopSpecificFolder.shortid
|
|
);
|
|
if (shopSpecificTemplate) {
|
|
useShopSpecificTemplate = true;
|
|
templateQueryToExecute = atob(shopSpecificTemplate.content);
|
|
}
|
|
let shopSpecificEsig = jsReportEsig.data.value.find(
|
|
(f) => f?.folder?.shortid === shopSpecificFolder.shortid
|
|
);
|
|
if (shopSpecificEsig) {
|
|
esigData = (atob(shopSpecificEsig.content));
|
|
}
|
|
}
|
|
|
|
if (!templateQueryToExecute) {
|
|
const generalTemplate = jsReportQueries.data.value.find((f) => !f.folder);
|
|
useShopSpecificTemplate = false;
|
|
templateQueryToExecute = atob(generalTemplate.content);
|
|
}
|
|
if (!esigData) {
|
|
const generalTemplate = jsReportEsig.data.value.find((f) => !f.folder);
|
|
useShopSpecificTemplate = false;
|
|
if (generalTemplate && generalTemplate.content) {
|
|
esigData = atob(generalTemplate?.content);
|
|
}
|
|
}
|
|
|
|
const client = req.userGraphQLClient;
|
|
|
|
|
|
// In the print center, we will never have sorters or filters.
|
|
// We have no template filters or sorters, so we can just execute the query and return the data
|
|
// if (!hasFilters && !hasSorters && !hasDefaultSorters) {
|
|
let contextData = {};
|
|
if (templateQueryToExecute) {
|
|
const data = await client.request(
|
|
templateQueryToExecute,
|
|
templateObject.variables,
|
|
);
|
|
contextData = data;
|
|
}
|
|
|
|
let parsedEsigData
|
|
try {
|
|
parsedEsigData = esigData ? JSON.parse(esigData) : null;
|
|
} catch (error) {
|
|
logger.log(`esig-data-parse-error`, "ERROR", "esig", "api", {
|
|
message: error.message, stack: error.stack,
|
|
esigData,
|
|
body: req.body
|
|
});
|
|
parsedEsigData = {}
|
|
}
|
|
|
|
return {
|
|
contextData,
|
|
useShopSpecificTemplate,
|
|
shopSpecificFolder,
|
|
esigData: parsedEsigData
|
|
};
|
|
// }
|
|
|
|
// return await generateTemplate(templateQueryToExecute, templateObject, useShopSpecificTemplate, shopSpecificFolder);
|
|
};
|
|
|
|
module.exports = {
|
|
newEsignDocument,
|
|
newCustomEsignDocument,
|
|
distributeDocument,
|
|
deleteDocument,
|
|
viewDocument
|
|
}
|
|
|
|
|
|
|
|
// const sample_esig_for_jsr = {
|
|
// "fields": [
|
|
// {
|
|
// "placeholder": "[[signature]]",
|
|
// "type": "SIGNATURE"
|
|
// },
|
|
// {
|
|
// "placeholder": "[[date]]",
|
|
// "type": "DATE"
|
|
// }
|
|
// ],
|
|
// "subject": "CASL Auth Set in JSR",
|
|
// "message": "CASL Message set in JSR",
|
|
// "title": "CASL Title set in JSR"
|
|
// }
|