Introduced JS report and refactored some doc generation. IO-585
This commit is contained in:
40
_reference/JSReportSetup.md
Normal file
40
_reference/JSReportSetup.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# install node.js
|
||||
wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
|
||||
# you may need to reopen terminal
|
||||
nvm install 8.11.3
|
||||
|
||||
mkdir jsreportapp
|
||||
cd jsreportapp
|
||||
npm i -g jsreport-cli
|
||||
jsreport init
|
||||
jsreport configure
|
||||
|
||||
# chrome dependencies
|
||||
sudo apt-get install -y libgconf-2-4
|
||||
sudo wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
|
||||
sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst --no-install-recommends
|
||||
|
||||
# on ubuntu 20 run also
|
||||
sudo apt-get install -y libxtst6 libxss1
|
||||
|
||||
# start jsreport to see it running on port 5488
|
||||
jsreport start
|
||||
|
||||
# the next steps are optional to start jsreport on boot
|
||||
npm install pm2 -g
|
||||
pm2 start server.js
|
||||
pm2 startup
|
||||
# run the output of previous command
|
||||
|
||||
# optionally if you want to use older phantomjs for pdf rendering
|
||||
sudo apt-get install -y --no-install-recommends gnupg git curl wget ca-certificates
|
||||
sudo apt-get install -y --no-install-recommends xfonts-base xfonts-75dpi
|
||||
npm i jsreport-phantom-pdf --save --save-exact
|
||||
|
||||
Running on port 80 and 443 without SU
|
||||
$ setcap 'cap_net_bind_service=+ep' /path/to/.nvm/v0.10.17/bin/node
|
||||
$ apt-get remove nginx
|
||||
$ cd /path/to/app
|
||||
$ PORT=80 node app
|
||||
5
client/package-lock.json
generated
5
client/package-lock.json
generated
@@ -12390,6 +12390,11 @@
|
||||
"verror": "1.10.0"
|
||||
}
|
||||
},
|
||||
"jsreport-browser-client-dist": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/jsreport-browser-client-dist/-/jsreport-browser-client-dist-1.3.0.tgz",
|
||||
"integrity": "sha512-E83cVmxQ5np3rxns6dhFu15m5kZ5yXJDIyfbHjLPxO0AZXVQOuMrdvYsUCj+j/ZSaiROoBCstZRO7pa4HmZNGw=="
|
||||
},
|
||||
"jsx-ast-utils": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz",
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"i18next-browser-languagedetector": "^6.0.1",
|
||||
"jsoneditor": "^9.1.7",
|
||||
"jsoneditor-react": "^3.1.0",
|
||||
"jsreport-browser-client-dist": "^1.3.0",
|
||||
"logrocket": "^1.0.13",
|
||||
"moment-business-days": "^1.2.0",
|
||||
"node-sass": "^4.14.1",
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
body {
|
||||
font-family: "Open Sans", sans-serif;
|
||||
line-height: 1.25;
|
||||
padding: 10mm 10mm 10mm 10mm !important;
|
||||
}
|
||||
|
||||
@page {
|
||||
margin: 50px;
|
||||
}
|
||||
|
||||
table.imex-table {
|
||||
border: 1px solid #ccc;
|
||||
border-collapse: collapse;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
font-size: inherit;
|
||||
|
||||
page-break-inside: auto;
|
||||
}
|
||||
|
||||
table.imex-table caption {
|
||||
/* font-size: 1.5em; */
|
||||
margin: 0.5em 0 0.75em;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
table.imex-table tr {
|
||||
/* background-color: #f8f8f8; */
|
||||
border: 1px solid #ddd;
|
||||
padding: 0.2rem;
|
||||
font-size: inherit;
|
||||
|
||||
page-break-inside: avoid;
|
||||
page-break-after: auto;
|
||||
}
|
||||
|
||||
table.imex-table th,
|
||||
table.imex-table td {
|
||||
padding: 0.3rem;
|
||||
text-align: center;
|
||||
font-size: inherit;
|
||||
}
|
||||
table.imex-table th.left,
|
||||
table.imex-table td.left {
|
||||
padding: 0.3rem;
|
||||
text-align: left;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
table.imex-table th {
|
||||
/* font-size: 0.85em; */
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
|
||||
/* display: table-header-group; */
|
||||
}
|
||||
@@ -109,11 +109,18 @@ export function EmailOverlayContainer({
|
||||
const render = async () => {
|
||||
logImEXEvent("email_render_template", { template: emailConfig.template });
|
||||
setLoading(true);
|
||||
let html = await RenderTemplate(emailConfig.template, bodyshop);
|
||||
let html = await RenderTemplate(emailConfig.template, bodyshop, true);
|
||||
|
||||
const response = await axios.post("/render/inlinecss", {
|
||||
html: html,
|
||||
url: `${window.location.protocol}://${window.location.host}/`,
|
||||
});
|
||||
|
||||
console.log("response", response);
|
||||
setMessageOptions({
|
||||
...emailConfig.messageOptions,
|
||||
...defaultEmailFrom,
|
||||
html: html,
|
||||
html: response.data,
|
||||
fileList: [],
|
||||
});
|
||||
setLoading(false);
|
||||
|
||||
@@ -25,14 +25,14 @@ export function PrintCenterItemComponent({
|
||||
disabled,
|
||||
}) {
|
||||
const renderToNewWindow = async () => {
|
||||
const html = await RenderTemplate(
|
||||
await RenderTemplate(
|
||||
{
|
||||
name: item.key,
|
||||
variables: { id: id },
|
||||
},
|
||||
bodyshop
|
||||
);
|
||||
displayTemplateInWindow(html);
|
||||
// displayTemplateInWindow(html);
|
||||
};
|
||||
|
||||
if (disabled) return <li className="print-center-item">{item.title} </li>;
|
||||
|
||||
@@ -22,11 +22,7 @@ export default function ShopTemplateSaveButton({
|
||||
html: data.html,
|
||||
url: `${window.location.protocol}://${window.location.host}/`,
|
||||
});
|
||||
console.log(
|
||||
"🚀 ~ file: shop-template-editor-save-button.component.jsx ~ line 25 ~ emailEditorRef.current.exportHtml ~ inlineHtml",
|
||||
response
|
||||
);
|
||||
|
||||
|
||||
const result = await updateTemplate({
|
||||
variables: {
|
||||
templateId: templateId,
|
||||
|
||||
@@ -2,3 +2,5 @@
|
||||
[1008/114603.993:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
|
||||
[1008/121110.259:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
|
||||
[1008/122424.146:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
|
||||
[0106/150233.556:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
|
||||
[0106/152014.936:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
|
||||
|
||||
@@ -74,6 +74,7 @@ export const QUERY_BODYSHOP = gql`
|
||||
appt_alt_transport
|
||||
schedule_start_time
|
||||
schedule_end_time
|
||||
imexshopid
|
||||
employees {
|
||||
id
|
||||
first_name
|
||||
@@ -145,7 +146,8 @@ export const UPDATE_SHOP = gql`
|
||||
appt_colors
|
||||
appt_alt_transport
|
||||
schedule_start_time
|
||||
schedule_end_time
|
||||
schedule_end_time
|
||||
imexshopid
|
||||
employees {
|
||||
id
|
||||
first_name
|
||||
|
||||
@@ -1,43 +1,90 @@
|
||||
import axios from "axios";
|
||||
import gql from "graphql-tag";
|
||||
import { QUERY_TEMPLATES_BY_NAME } from "../graphql/templates.queries";
|
||||
import jsreport from "jsreport-browser-client-dist";
|
||||
import client from "../utils/GraphQLClient";
|
||||
import cleanAxios from "./CleanAxios";
|
||||
|
||||
export default async function RenderTemplate(templateObject, bodyshop) {
|
||||
const { data: templateRecords } = await client.query({
|
||||
query: QUERY_TEMPLATES_BY_NAME,
|
||||
variables: { name: templateObject.name },
|
||||
fetchPolicy: "network-only",
|
||||
});
|
||||
const server = "https://reports.bodyshop.app";
|
||||
jsreport.serverUrl = server;
|
||||
jsreport.headers["Authorization"] = "Basic " + btoa("admin:admin");
|
||||
|
||||
let templateToUse;
|
||||
export default async function RenderTemplate(
|
||||
templateObject,
|
||||
bodyshop,
|
||||
renderAsHtml = false
|
||||
) {
|
||||
//Template Object
|
||||
// {
|
||||
// name: TemplateList().parts_order_confirmation.key,
|
||||
// variables: {
|
||||
// id: insertResult.data.insert_parts_orders.returning[0].id,
|
||||
// },
|
||||
// },
|
||||
|
||||
if (templateRecords.templates.length === 1) {
|
||||
console.log("[ITE] Using OOTB template.");
|
||||
templateToUse = templateRecords.templates[0];
|
||||
} else if (templateRecords.templates.length === 2) {
|
||||
console.log("[ITE] Found custom template.");
|
||||
templateToUse = templateRecords.templates.filter((t) => !!t.bodyshopid)[0];
|
||||
console.log("templateToUse", templateToUse);
|
||||
//Query assets that match the template name. Must be in format <<templateName>>.query
|
||||
const jsReportQueries = await cleanAxios.get(
|
||||
`${server}/odata/assets?$filter=name eq '${templateObject.name}.query'`,
|
||||
{ headers: { Authorization: "Basic " + btoa("admin:admin") } }
|
||||
);
|
||||
|
||||
let templateQueryToExecute,
|
||||
useShopSpecificTemplate = false;
|
||||
if (jsReportQueries.data.value.length === 0) {
|
||||
//We have no query to execute. Just render the template.
|
||||
} else if (jsReportQueries.data.value.length === 1) {
|
||||
//We're using the default template. Get the query and execute.
|
||||
templateQueryToExecute = atob(jsReportQueries.data.value[0].content);
|
||||
} else if (jsReportQueries.data.value.length === 2) {
|
||||
//There's a custom template. Use that query instead and execute. We find it because it has a parent folder.
|
||||
templateQueryToExecute = atob(
|
||||
jsReportQueries.data.value.filter((v) => !!v.folder)[0].content
|
||||
);
|
||||
useShopSpecificTemplate = true;
|
||||
} else {
|
||||
//No template found.Uh oh.
|
||||
alert("Error: Template key does not exist.");
|
||||
throw new Error("Template key does not exist.");
|
||||
//We have too many queries to choose from. Throw an error.
|
||||
alert(
|
||||
"There are too many queries to choose from. Please ensure there are no conflicting keys."
|
||||
);
|
||||
throw new Error(
|
||||
"There are too many queries to choose from. Please ensure there are no conflicting keys."
|
||||
);
|
||||
}
|
||||
|
||||
const { data: contextData } = await client.query({
|
||||
query: gql(templateToUse.query),
|
||||
query: gql(templateQueryToExecute),
|
||||
variables: { ...templateObject.variables },
|
||||
fetchPolicy: "network-only",
|
||||
});
|
||||
|
||||
const { data } = await axios.post("/render", {
|
||||
view: templateToUse.html,
|
||||
context: { ...contextData, bodyshop: bodyshop },
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(data);
|
||||
});
|
||||
let reportRequest = {
|
||||
template: {
|
||||
name: useShopSpecificTemplate
|
||||
? `/${bodyshop.imexshopid}/${templateObject.name}`
|
||||
: `/${templateObject.name}`,
|
||||
...(renderAsHtml ? {} : { recipe: "chrome-pdf" }),
|
||||
},
|
||||
data: {
|
||||
...contextData,
|
||||
headerpath: `/${bodyshop.imexshopid}/header.html`,
|
||||
bodyshop: bodyshop,
|
||||
},
|
||||
};
|
||||
const render = await jsreport.renderAsync(reportRequest);
|
||||
|
||||
if (!renderAsHtml) {
|
||||
render.download();
|
||||
var html =
|
||||
"<html>" +
|
||||
"<style>html,body {padding:0;margin:0;} iframe {width:100%;height:100%;border:0}</style>" +
|
||||
"<body>" +
|
||||
'<iframe type="application/pdf" src="' +
|
||||
render.toDataURI() +
|
||||
'"></iframe>' +
|
||||
"</body></html>";
|
||||
displayTemplateInWindowNoprint(html);
|
||||
} else {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(render.toString());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const displayTemplateInWindow = (html) => {
|
||||
|
||||
@@ -14,97 +14,5 @@ export const TemplateList = (type, object) => {
|
||||
drivingId: "job",
|
||||
key: "estimate_detail",
|
||||
},
|
||||
all_job_notes: {
|
||||
title: i18n.t("printcenter.jobs.all_job_notes"),
|
||||
description: "All jobs Notes including Private",
|
||||
drivingId: "job",
|
||||
key: "all_job_notes",
|
||||
},
|
||||
fippa_authorization: {
|
||||
title: i18n.t("printcenter.jobs.fippa_authorization"),
|
||||
description: "FIPPA Work Auth",
|
||||
drivingId: "job",
|
||||
key: "fippa_authorization",
|
||||
},
|
||||
casl_authorization: {
|
||||
title: i18n.t("printcenter.jobs.casl_authorization"),
|
||||
description: "CASL Auth",
|
||||
drivingId: "job",
|
||||
key: "casl_authorization",
|
||||
},
|
||||
window_tag: {
|
||||
title: i18n.t("printcenter.jobs.window_tag"),
|
||||
description: "Window Tag",
|
||||
drivingId: "job",
|
||||
key: "window_tag",
|
||||
},
|
||||
cover_sheet: {
|
||||
title: i18n.t("printcenter.jobs.cover_sheet"),
|
||||
description: "Cover Sheet",
|
||||
drivingId: "job",
|
||||
key: "cover_sheet",
|
||||
},
|
||||
|
||||
time_tickets_by_employee: {
|
||||
title: "Time Tickets by Employee",
|
||||
description: "Time tickets for employee with date range",
|
||||
drivingId: "employee",
|
||||
key: "time_tickets_by_employee",
|
||||
},
|
||||
|
||||
//Non Completed Items
|
||||
appointment_reminder: {
|
||||
title: "Appointment Reminder",
|
||||
description:
|
||||
"Sent to a customer as a reminder of an upcoming appointment.",
|
||||
drivingId: "appointment",
|
||||
key: "appointment_reminder",
|
||||
subject: `Appointment Reminder`,
|
||||
},
|
||||
appointment_confirmation: {
|
||||
title: "Appointment Confirmation",
|
||||
description:
|
||||
"Sent to a customer as a Confirmation of an upcoming appointment.",
|
||||
drivingId: "appointment",
|
||||
key: "appointment_confirmation",
|
||||
},
|
||||
parts_order_confirmation: {
|
||||
title: "Parts Order Confirmation",
|
||||
description: "Parts order template including part details",
|
||||
drivingId: "partsorder",
|
||||
key: "parts_order_confirmation",
|
||||
subject: `Parts Order for ${object}`,
|
||||
},
|
||||
|
||||
cover_sheet_landscape: {
|
||||
title: "Cover Sheet - Landscape",
|
||||
description: "Cover sheet landscape",
|
||||
drivingId: "job",
|
||||
key: "cover_sheet_landscape",
|
||||
},
|
||||
cover_sheet_portrait: {
|
||||
title: "Cover Sheet - portrait",
|
||||
description: "Cover sheet portrait",
|
||||
drivingId: "job",
|
||||
key: "cover_sheet_portrait",
|
||||
},
|
||||
parts_return_confirmation: {
|
||||
title: "Parts Return Confirmation",
|
||||
description: "Parts Return template including part details",
|
||||
drivingId: "partsorder",
|
||||
key: "parts_return_confirmation",
|
||||
},
|
||||
csi_invitation: {
|
||||
title: "Customer Survey Invitation",
|
||||
description: "Customer Survey Invitation",
|
||||
drivingId: "csi",
|
||||
key: "csi_invitation",
|
||||
},
|
||||
payment_receipt: {
|
||||
title: "Payment Receipt",
|
||||
description: "Receipt of payment for customer",
|
||||
drivingId: "payment",
|
||||
key: "payment_receipt",
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
query EMAIL_APPOINTMENT_CONFIRMATION($id: uuid!) {
|
||||
appointments_by_pk(id: $id) {
|
||||
start
|
||||
title
|
||||
job {
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_ea
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<div style="font-family: Arial, Helvetica, sans-serif;">
|
||||
<p style="text-align: center;">Hello {{appointments_by_pk.job.ownr_fn}},</p>
|
||||
<p style="text-align: center;">
|
||||
This is a confirmation that you have an appointment at
|
||||
{{appointments_by_pk.start}} to bring your car in for repair. Please email
|
||||
us at {{bodyshop.email}} if you can't make it.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
query EMAIL_APPOINTMENT_CONFIRMATION($id: uuid!) {
|
||||
appointments_by_pk(id: $id) {
|
||||
start
|
||||
title
|
||||
job {
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_ea
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
<div style="font-family: Arial, Helvetica, sans-serif;">
|
||||
<p style="text-align: center;">Hello {{appointments_by_pk.job.ownr_fn}},</p>
|
||||
<p style="text-align: center;">
|
||||
This is a reminder that you have an appointment at
|
||||
{{appointments_by_pk.start}} to bring your car in for repair. Please email
|
||||
us at {{bodyshop.email}} if you can't make it.
|
||||
</p>
|
||||
</div>
|
||||
@@ -1,6 +0,0 @@
|
||||
query ($id: uuid!){
|
||||
csi_by_pk(id: $id){
|
||||
id
|
||||
relateddata
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
<div>Hi {{csi_by_pk.relateddata.job.ownr_fn}}, </div>
|
||||
<div> </div>
|
||||
<div>
|
||||
Thank you for getting your car repaired at
|
||||
{{csi_by_pk.relateddata.bodyshop.shopname}}. We invite you to complete a
|
||||
survey about your experience.
|
||||
</div>
|
||||
<div> </div>
|
||||
<table border="0" width="100%" cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="text-align: center; vertical-align: middle;">
|
||||
<table style="height: 47px;" border="0" cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr style="height: 47px;">
|
||||
<td
|
||||
style="border-radius: 3px; height: 47px; width: 200px;"
|
||||
align="center"
|
||||
bgcolor="#e9703e"
|
||||
>
|
||||
<a
|
||||
style="
|
||||
font-size: 16px;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
border-radius: 3px;
|
||||
padding: 12px 18px;
|
||||
border: 1px solid #e9703e;
|
||||
display: inline-block;
|
||||
"
|
||||
href="https://imex.online/csi/{{csi_by_pk.id}}"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>Complete Survey →</a
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div> </div>
|
||||
@@ -1,32 +0,0 @@
|
||||
query TEMPLATE_ESTIMATE_DETAIL($id: uuid!) {
|
||||
jobs_by_pk(id: $id) {
|
||||
csr
|
||||
ded_amt
|
||||
ded_status
|
||||
id
|
||||
ownr_co_nm
|
||||
ownr_ln
|
||||
ownr_fn
|
||||
plate_no
|
||||
plate_st
|
||||
ro_number
|
||||
regie_number
|
||||
tlos_ind
|
||||
v_color
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
v_model_yr
|
||||
v_vin
|
||||
clm_no
|
||||
joblines(order_by: { line_no: asc }) {
|
||||
id
|
||||
mod_lbr_ty
|
||||
mod_lb_hrs
|
||||
part_qty
|
||||
oem_partno
|
||||
op_code_desc
|
||||
line_desc
|
||||
line_no
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
<div style="font-family: Arial, Helvetica, sans-serif;">
|
||||
<h1 style="text-align: center;">
|
||||
<span><strong>Job Detail Summary</strong></span>
|
||||
</h1>
|
||||
<table style="border-collapse: collapse; width: 100%;" border="1">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 33.3333%; vertical-align: top;">
|
||||
<strong>Owner:</strong> {{jobs_by_pk.ownr_fn}} {{jobs_by_pk.ownr_ln}}
|
||||
{{jobs_by_pk.ownr_co_nm}}
|
||||
</td>
|
||||
<td style="width: 33.3333%; vertical-align: top;">
|
||||
<strong>Vehicle: </strong>{{jobs_by_pk.v_model_yr}}
|
||||
{{jobs_by_pk.v_color}}{{jobs_by_pk.v_make_desc}}
|
||||
{{jobs_by_pk.v_model_desc}}
|
||||
</td>
|
||||
<td style="width: 33.3333%; vertical-align: top;">
|
||||
<p>
|
||||
<strong>Claim Number: </strong>{{jobs_by_pk.clm_no}}<br /><strong
|
||||
>Regie Number:</strong > {{jobs_by_pk.regie_number}}
|
||||
</strong>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Deductible:</strong> {{jobs_by_pk.ded_amt}}
|
||||
{{jobs_by_pk.ded_status}}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h2 style="text-align: center;"><span>Job Lines</span></h2>
|
||||
<table
|
||||
style="border-collapse: collapse; width: 100%; height: 88px;"
|
||||
border="1"
|
||||
>
|
||||
<tbody>
|
||||
<tr style="height: 22px;">
|
||||
<td style="width: 14.2857%; text-align: center; height: 22px;">
|
||||
<strong>Line No.</strong>
|
||||
</td>
|
||||
<td style="width: 14.2857%; text-align: center; height: 22px;">
|
||||
<strong>Line Desc.</strong>
|
||||
</td>
|
||||
<td style="width: 14.2857%; text-align: center; height: 22px;">
|
||||
<strong>OEM Part #</strong>
|
||||
</td>
|
||||
<td style="width: 14.2857%; text-align: center; height: 22px;">
|
||||
<strong>Qty.</strong>
|
||||
</td>
|
||||
<td style="width: 14.2857%; text-align: center; height: 22px;">
|
||||
<strong>Labor Type</strong>
|
||||
</td>
|
||||
<td style="width: 14.2857%; text-align: center; height: 22px;">
|
||||
<strong>Hours</strong>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="height: 22px; display: none;">
|
||||
<td style="width: 100%; height: 22px;">
|
||||
<span>{{#each jobs_by_pk.joblines}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="height: 22px;">
|
||||
<td style="width: 14.2857%; height: 22px; text-align: justify;">
|
||||
{{this.line_no}}
|
||||
</td>
|
||||
<td style="width: 14.2857%; height: 22px; text-align: justify;">
|
||||
{{this.line_desc}}
|
||||
</td>
|
||||
<td style="width: 14.2857%; height: 22px; text-align: justify;">
|
||||
{{this.oem_partno}}
|
||||
</td>
|
||||
<td style="width: 14.2857%; height: 22px; text-align: justify;">
|
||||
{{this.part_qty}}
|
||||
</td>
|
||||
<td style="width: 14.2857%; height: 22px; text-align: justify;">
|
||||
{{this.mod_lbr_ty}}
|
||||
</td>
|
||||
<td style="width: 14.2857%; height: 22px; text-align: justify;">
|
||||
{{this.mod_lb_hrs}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="height: 22px; display: none;">
|
||||
<td style="width: 100%; height: 22px;"><span>{{/each}}</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -1,3 +0,0 @@
|
||||
Table Styles
|
||||
|
||||
<link rel='stylesheet' href='http://localhost:3000/render-styles.css'/>
|
||||
@@ -1,29 +0,0 @@
|
||||
query REPORT_QUERY_PARTS_ORDER_BY_PK($id: uuid!) {
|
||||
parts_orders_by_pk(id: $id) {
|
||||
job {
|
||||
id
|
||||
vehicle {
|
||||
id
|
||||
v_model_desc
|
||||
v_make_desc
|
||||
v_model_yr
|
||||
v_vin
|
||||
}
|
||||
ro_number
|
||||
est_number
|
||||
}
|
||||
id
|
||||
deliver_by
|
||||
parts_order_lines {
|
||||
id
|
||||
db_price
|
||||
act_price
|
||||
line_desc
|
||||
line_remarks
|
||||
oem_partno
|
||||
status
|
||||
}
|
||||
status
|
||||
user_email
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
<div style="font-family: Arial, Helvetica, sans-serif;">
|
||||
<p style="text-align: center;">
|
||||
<span><strong>Deliver By: {{parts_orders_by_pk.deliver_by}}</strong></span>
|
||||
</p>
|
||||
<table
|
||||
style="
|
||||
border-collapse: collapse;
|
||||
width: 85%;
|
||||
height: 86px;
|
||||
border-color: initial;
|
||||
border-style: solid;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
"
|
||||
>
|
||||
<tbody>
|
||||
<tr style="height: 22px;">
|
||||
<td
|
||||
style="
|
||||
width: 33.7389%;
|
||||
text-align: center;
|
||||
padding: 7px;
|
||||
height: 22px;
|
||||
"
|
||||
>
|
||||
<strong>Line Description</strong>
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
width: 22.0208%;
|
||||
text-align: center;
|
||||
padding-top: 7px;
|
||||
padding-right: 7px;
|
||||
padding-bottom: 7px;
|
||||
height: 22px;
|
||||
"
|
||||
>
|
||||
<strong>Part #</strong>
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
width: 12.1897%;
|
||||
text-align: center;
|
||||
padding: 7px;
|
||||
height: 22px;
|
||||
"
|
||||
>
|
||||
<strong>Price</strong>
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
width: 32.0506%;
|
||||
text-align: center;
|
||||
padding: 7px;
|
||||
height: 22px;
|
||||
"
|
||||
>
|
||||
<strong>Line Remarks</strong>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="height: 21px; display: none;">
|
||||
<td style="height: 21px; display: none; width: 33.7389%;">
|
||||
<span>{{#each parts_orders_by_pk.parts_order_lines}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="height: 22px;">
|
||||
<td
|
||||
style="
|
||||
width: 33.7389%;
|
||||
text-align: center;
|
||||
padding: 7px;
|
||||
height: 22px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
"
|
||||
>
|
||||
<span>{{this.line_desc}}</span>
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
width: 22.0208%;
|
||||
text-align: center;
|
||||
padding: 7px;
|
||||
height: 22px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
"
|
||||
>
|
||||
<span>{{this.oem_partno}}</span>
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
width: 12.1897%;
|
||||
text-align: center;
|
||||
padding: 7px;
|
||||
height: 22px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
"
|
||||
>
|
||||
<span>${{this.act_price}}</span>
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
width: 32.0506%;
|
||||
text-align: center;
|
||||
padding: 7px;
|
||||
height: 22px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
"
|
||||
>
|
||||
<span>{{this.line_remarks}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="height: 21px; display: none;">
|
||||
<td style="height: 21px; width: 33.7389%;"><span>{{/each}}</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Order Placed by {{parts_orders_by_pk.user_email}}.</p>
|
||||
</div>
|
||||
@@ -1,26 +0,0 @@
|
||||
query REPORT_PAYMENT_RECEIPT($id: uuid!) {
|
||||
payments_by_pk(id: $id) {
|
||||
job {
|
||||
id
|
||||
vehicle {
|
||||
id
|
||||
v_model_desc
|
||||
v_make_desc
|
||||
v_model_yr
|
||||
v_vin
|
||||
}
|
||||
ro_number
|
||||
est_number
|
||||
clm_no
|
||||
clm_total
|
||||
ded_amt
|
||||
}
|
||||
id
|
||||
amount
|
||||
memo
|
||||
transactionid
|
||||
stripeid
|
||||
payer
|
||||
type
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<div style="font-family: Arial, Helvetica, sans-serif;">
|
||||
<p style="text-align: center;"><strong>RECEIPT OF PAYMENT</strong></p>
|
||||
<p style="text-align: left;"><strong>Amount</strong>: {{payments_by_pk.amount}}</p>
|
||||
<p style="text-align: left;"><strong>Memo</strong>: {{payments_by_pk.memo}}</p>
|
||||
<p style="text-align: left;"><strong>RO Number</strong>: {{payments_by_pk.job.ro_number}} / {{payments_by_pk.job.est_number}}</p>
|
||||
<p style="text-align: left;"><strong>Payer</strong>: {{payments_by_pk.payer}}</p>
|
||||
<p style="text-align: left;"><strong>StripeID</strong>: {{payments_by_pk.stripeid}}</p>
|
||||
<p style="text-align: left;"><strong>Amount</strong>: {{payments_by_pk.amount}}</p>
|
||||
<p style="text-align: left;"> </p>
|
||||
</div>
|
||||
@@ -1,25 +0,0 @@
|
||||
query REPORT_TIME_TICKETS_IN_RANGE($id: uuid!, $start: date!, $end: date!) {
|
||||
employees_by_pk(id: $id) {
|
||||
id
|
||||
first_name
|
||||
last_name
|
||||
employee_number
|
||||
timetickets(where: { date: { _gte: $start, _lte: $end } }) {
|
||||
actualhrs
|
||||
ciecacode
|
||||
clockoff
|
||||
clockon
|
||||
cost_center
|
||||
created_at
|
||||
date
|
||||
id
|
||||
rate
|
||||
productivehrs
|
||||
memo
|
||||
job {
|
||||
id
|
||||
ro_number
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
<div style="font-family: Arial, Helvetica, sans-serif;">
|
||||
<h1 style="text-align: center;"><span><strong>Employee Time Tickets</strong></span></h1>
|
||||
<table style="border-collapse: collapse; width: 100%;" border="1">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 33.3333%; vertical-align: top;"><strong>Employee:</strong> {{employees_by_pk.first_name}} {{employees_by_pk.last_name}}</td>
|
||||
<td style="width: 35.2832%; vertical-align: top;"> </td>
|
||||
<td style="width: 31.3834%; vertical-align: top;">
|
||||
<p> </p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h2 style="text-align: center;"><span>Time Tickets</span></h2>
|
||||
<table style="border-collapse: collapse; width: 100%; height: 88px;" border="1">
|
||||
<tbody>
|
||||
<tr style="height: 22px;">
|
||||
<td style="width: 14.2857%; text-align: center; height: 22px;"><strong>Date</strong></td>
|
||||
<td style="width: 14.2857%; text-align: center; height: 22px;"><strong>Cost Center</strong></td>
|
||||
<td style="width: 14.2857%; text-align: center; height: 22px;"><strong>Actual Hrs</strong></td>
|
||||
<td style="width: 14.2857%; text-align: center; height: 22px;"><strong>Productive Hrs</strong></td>
|
||||
<td style="width: 14.2857%; text-align: center; height: 22px;"><strong>Shift Clock On</strong></td>
|
||||
<td style="width: 7.14285%; text-align: center; height: 22px;"><strong>Shift Clock Off</strong></td>
|
||||
<td style="width: 7.14285%; text-align: center;"><strong>Shift Time</strong></td>
|
||||
</tr>
|
||||
<tr style="height: 22px; display: none;">
|
||||
<td style="width: 14.2857%; height: 22px;"><span>{{#each employees_by_pk.timetickets}}</span></td>
|
||||
<td style="width: 14.2857%;"> </td>
|
||||
<td style="width: 14.2857%;"> </td>
|
||||
<td style="width: 14.2857%;"> </td>
|
||||
<td style="width: 14.2857%;"> </td>
|
||||
<td style="width: 7.14285%;"> </td>
|
||||
<td style="width: 7.14285%;"> </td>
|
||||
</tr>
|
||||
<tr style="height: 22px;">
|
||||
<td style="width: 14.2857%; height: 22px; text-align: justify;">{{this.date}}</td>
|
||||
<td style="width: 14.2857%; height: 22px; text-align: justify;">{{this.cost_center}}</td>
|
||||
<td style="width: 14.2857%; height: 22px; text-align: justify;">{{this.actualhrs}}</td>
|
||||
<td style="width: 14.2857%; height: 22px; text-align: justify;">{{this.productivehrs}}</td>
|
||||
<td style="width: 14.2857%; height: 22px; text-align: justify;">{{moment this.clockon format="MM/DD/YYYY @ hh:mm:ss"}}</td>
|
||||
<td style="width: 7.14285%; height: 22px; text-align: justify;">{{moment this.clockoff format="MM/DD/YYYY @ hh:mm:ss"}}</td>
|
||||
<td style="width: 7.14285%; text-align: justify;">{{moment this.clockoff diff=this.clockon }}</td>
|
||||
</tr>
|
||||
<tr style="height: 22px; display: none;">
|
||||
<td style="width: 14.2857%; height: 22px;"><span>{{/each}}</span></td>
|
||||
<td style="width: 14.2857%;"> </td>
|
||||
<td style="width: 14.2857%;"> </td>
|
||||
<td style="width: 14.2857%;"> </td>
|
||||
<td style="width: 14.2857%;"> </td>
|
||||
<td style="width: 7.14285%;"> </td>
|
||||
<td style="width: 7.14285%;"> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."bodyshops" DROP COLUMN "imexshopid";
|
||||
type: run_sql
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."bodyshops" ADD COLUMN "imexshopid" text NULL UNIQUE;
|
||||
type: run_sql
|
||||
@@ -0,0 +1,74 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- accountingconfig
|
||||
- address1
|
||||
- address2
|
||||
- appt_alt_transport
|
||||
- appt_colors
|
||||
- appt_length
|
||||
- bill_tax_rates
|
||||
- city
|
||||
- country
|
||||
- created_at
|
||||
- deliverchecklist
|
||||
- email
|
||||
- enforce_class
|
||||
- federal_tax_id
|
||||
- id
|
||||
- inhousevendorid
|
||||
- insurance_vendor_id
|
||||
- intakechecklist
|
||||
- logo_img_path
|
||||
- md_categories
|
||||
- md_classes
|
||||
- md_ins_cos
|
||||
- md_labor_rates
|
||||
- md_messaging_presets
|
||||
- md_notes_presets
|
||||
- md_order_statuses
|
||||
- md_parts_locations
|
||||
- md_rbac
|
||||
- md_referral_sources
|
||||
- md_responsibility_centers
|
||||
- md_ro_statuses
|
||||
- messagingservicesid
|
||||
- phone
|
||||
- prodtargethrs
|
||||
- production_config
|
||||
- region_config
|
||||
- schedule_end_time
|
||||
- schedule_start_time
|
||||
- scoreboard_target
|
||||
- shopname
|
||||
- shoprates
|
||||
- speedprint
|
||||
- ssbuckets
|
||||
- state
|
||||
- state_tax_id
|
||||
- stripe_acct_id
|
||||
- target_touchtime
|
||||
- template_header
|
||||
- textid
|
||||
- updated_at
|
||||
- zip_post
|
||||
computed_fields: []
|
||||
filter:
|
||||
associations:
|
||||
bodyshop:
|
||||
associations:
|
||||
user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -0,0 +1,75 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- accountingconfig
|
||||
- address1
|
||||
- address2
|
||||
- appt_alt_transport
|
||||
- appt_colors
|
||||
- appt_length
|
||||
- bill_tax_rates
|
||||
- city
|
||||
- country
|
||||
- created_at
|
||||
- deliverchecklist
|
||||
- email
|
||||
- enforce_class
|
||||
- federal_tax_id
|
||||
- id
|
||||
- imexshopid
|
||||
- inhousevendorid
|
||||
- insurance_vendor_id
|
||||
- intakechecklist
|
||||
- logo_img_path
|
||||
- md_categories
|
||||
- md_classes
|
||||
- md_ins_cos
|
||||
- md_labor_rates
|
||||
- md_messaging_presets
|
||||
- md_notes_presets
|
||||
- md_order_statuses
|
||||
- md_parts_locations
|
||||
- md_rbac
|
||||
- md_referral_sources
|
||||
- md_responsibility_centers
|
||||
- md_ro_statuses
|
||||
- messagingservicesid
|
||||
- phone
|
||||
- prodtargethrs
|
||||
- production_config
|
||||
- region_config
|
||||
- schedule_end_time
|
||||
- schedule_start_time
|
||||
- scoreboard_target
|
||||
- shopname
|
||||
- shoprates
|
||||
- speedprint
|
||||
- ssbuckets
|
||||
- state
|
||||
- state_tax_id
|
||||
- stripe_acct_id
|
||||
- target_touchtime
|
||||
- template_header
|
||||
- textid
|
||||
- updated_at
|
||||
- zip_post
|
||||
computed_fields: []
|
||||
filter:
|
||||
associations:
|
||||
bodyshop:
|
||||
associations:
|
||||
user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -713,6 +713,7 @@ tables:
|
||||
- enforce_class
|
||||
- federal_tax_id
|
||||
- id
|
||||
- imexshopid
|
||||
- inhousevendorid
|
||||
- insurance_vendor_id
|
||||
- intakechecklist
|
||||
|
||||
17
sendemail.js
17
sendemail.js
@@ -6,16 +6,24 @@ require("dotenv").config({
|
||||
),
|
||||
});
|
||||
|
||||
const inlineCssTool = require("inline-css");
|
||||
|
||||
const mailjet = require("node-mailjet").connect(
|
||||
process.env.email_api,
|
||||
process.env.email_secret
|
||||
);
|
||||
|
||||
exports.sendEmail = (req, res) => {
|
||||
exports.sendEmail = async (req, res) => {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
console.log("[EMAIL] Incoming Message", req.body.from.name);
|
||||
}
|
||||
|
||||
// const inlinedCssHtml = await inlineCssTool(req.body.html, {
|
||||
// url: "https://imex.online",
|
||||
// });
|
||||
|
||||
// console.log("inlinedCssHtml", inlinedCssHtml);
|
||||
|
||||
const request = mailjet.post("send", { version: "v3.1" }).request({
|
||||
Messages: [
|
||||
{
|
||||
@@ -36,13 +44,6 @@ exports.sendEmail = (req, res) => {
|
||||
// "Dear passenger 1, welcome to Mailjet! May the delivery force be with you!",
|
||||
HTMLPart: req.body.html,
|
||||
Attachments: req.body.attachments || null,
|
||||
// Attachments: [
|
||||
// {
|
||||
// ContentType: "text/plain",
|
||||
// Filename: "test.txt",
|
||||
// Base64Content: "VGhpcyBpcyB5b3VyIGF0dGFjaGVkIGZpbGUhISEK",
|
||||
// },
|
||||
// ],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -19,5 +19,8 @@ exports.inlinecss = (req, res) => {
|
||||
.then((inlinedHtml) => {
|
||||
res.send(inlinedHtml);
|
||||
})
|
||||
.catch((error) => res.send(error));
|
||||
.catch((error) => {
|
||||
console.log("Error while inlining CSS", JSON.stringify(error));
|
||||
res.send(error);
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user