Introduced JS report and refactored some doc generation. IO-585

This commit is contained in:
Patrick Fic
2021-01-06 17:56:46 -08:00
parent 1c967ece2e
commit 2d260dceb8
33 changed files with 311 additions and 676 deletions

View 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

View File

@@ -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",

View File

@@ -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",

View File

@@ -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; */
}

View File

@@ -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);

View File

@@ -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>;

View File

@@ -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,

View File

@@ -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)

View File

@@ -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

View File

@@ -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) => {

View File

@@ -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",
},
};
};

View File

@@ -1,11 +0,0 @@
query EMAIL_APPOINTMENT_CONFIRMATION($id: uuid!) {
appointments_by_pk(id: $id) {
start
title
job {
ownr_fn
ownr_ln
ownr_ea
}
}
}

View File

@@ -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.&nbsp;
</p>
</div>

View File

@@ -1,11 +0,0 @@
query EMAIL_APPOINTMENT_CONFIRMATION($id: uuid!) {
appointments_by_pk(id: $id) {
start
title
job {
ownr_fn
ownr_ln
ownr_ea
}
}
}

View File

@@ -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.&nbsp;
</p>
</div>

View File

@@ -1,6 +0,0 @@
query ($id: uuid!){
csi_by_pk(id: $id){
id
relateddata
}
}

View File

@@ -1,45 +0,0 @@
<div>Hi {{csi_by_pk.relateddata.job.ownr_fn}},&nbsp;</div>
<div>&nbsp;</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.&nbsp;
</div>
<div>&nbsp;</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 &rarr;</a
>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<div>&nbsp;</div>

View File

@@ -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
}
}
}

View File

@@ -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:&lt;/strong &gt; {{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>

View File

@@ -1,3 +0,0 @@
Table Styles
<link rel='stylesheet' href='http://localhost:3000/render-styles.css'/>

View File

@@ -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
}
}

View File

@@ -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>

View File

@@ -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
}
}

View File

@@ -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;">&nbsp;</p>
</div>

View File

@@ -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
}
}
}
}

View File

@@ -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;">&nbsp;</td>
<td style="width: 31.3834%; vertical-align: top;">
<p>&nbsp;</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%;">&nbsp;</td>
<td style="width: 14.2857%;">&nbsp;</td>
<td style="width: 14.2857%;">&nbsp;</td>
<td style="width: 14.2857%;">&nbsp;</td>
<td style="width: 7.14285%;">&nbsp;</td>
<td style="width: 7.14285%;">&nbsp;</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%;">&nbsp;</td>
<td style="width: 14.2857%;">&nbsp;</td>
<td style="width: 14.2857%;">&nbsp;</td>
<td style="width: 14.2857%;">&nbsp;</td>
<td style="width: 7.14285%;">&nbsp;</td>
<td style="width: 7.14285%;">&nbsp;</td>
</tr>
</tbody>
</table>
</div>

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."bodyshops" DROP COLUMN "imexshopid";
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."bodyshops" ADD COLUMN "imexshopid" text NULL UNIQUE;
type: run_sql

View File

@@ -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

View File

@@ -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

View File

@@ -713,6 +713,7 @@ tables:
- enforce_class
- federal_tax_id
- id
- imexshopid
- inhousevendorid
- insurance_vendor_id
- intakechecklist

View File

@@ -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",
// },
// ],
},
],
});

View File

@@ -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);
});
};