Merged in feature/2021-07-23 (pull request #148)

Feature/2021 07 23
This commit is contained in:
Patrick Fic
2021-07-22 00:23:07 +00:00
29 changed files with 958 additions and 102 deletions

View File

@@ -1 +1 @@
client_max_body_size 15M;
client_max_body_size 50M;

View File

@@ -3014,6 +3014,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>attach_pdf_to_email</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>bill_federal_tax_rate</name>
<definition_loaded>false</definition_loaded>
@@ -11679,6 +11700,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>pdfcopywillbeattached</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>preview</name>
<definition_loaded>false</definition_loaded>
@@ -29956,6 +29998,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>refnumber</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>sendtype</name>
<definition_loaded>false</definition_loaded>
@@ -32799,6 +32862,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>hours_sold_detail_closed_csr</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>hours_sold_detail_closed_ins_co</name>
<definition_loaded>false</definition_loaded>
@@ -32841,6 +32925,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>hours_sold_detail_open_csr</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>hours_sold_detail_open_ins_co</name>
<definition_loaded>false</definition_loaded>
@@ -32883,6 +32988,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>hours_sold_summary_closed_csr</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>hours_sold_summary_closed_ins_co</name>
<definition_loaded>false</definition_loaded>
@@ -32925,6 +33051,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>hours_sold_summary_open_csr</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>hours_sold_summary_open_ins_co</name>
<definition_loaded>false</definition_loaded>
@@ -32946,6 +33093,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>job_costing_ro_csr</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>job_costing_ro_date_detail</name>
<definition_loaded>false</definition_loaded>

View File

@@ -37,6 +37,8 @@ export default function EmailOverlayComponent({ form, selectedMediaState }) {
</Form.Item>
<Divider>{t("emails.labels.preview")}</Divider>
<strong>{t("emails.labels.pdfcopywillbeattached")}</strong>
<Form.Item shouldUpdate>
{() => {
return (

View File

@@ -43,6 +43,10 @@ export function EmailOverlayContainer({
const [loading, setLoading] = useState(false);
const [sending, setSending] = useState(false);
const [rawHtml, setRawHtml] = useState("");
const [pdfCopytoAttach, setPdfCopytoAttach] = useState({
filename: null,
pdf: null,
});
const [selectedMedia, setSelectedMedia] = useState([]);
const defaultEmailFrom = {
@@ -59,17 +63,17 @@ export function EmailOverlayContainer({
const handleFinish = async (values) => {
logImEXEvent("email_send_from_modal");
const attachments = [];
//const attachments = [];
if (values.fileList)
await asyncForEach(values.fileList, async (f) => {
const t = {
ContentType: f.type,
Filename: f.name,
Base64Content: (await toBase64(f.originFileObj)).split(",")[1],
};
attachments.push(t);
});
// if (values.fileList)
// await asyncForEach(values.fileList, async (f) => {
// const t = {
// ContentType: f.type,
// Filename: f.name,
// Base64Content: (await toBase64(f.originFileObj)).split(",")[1],
// };
// attachments.push(t);
// });
setSending(true);
try {
@@ -77,11 +81,28 @@ export function EmailOverlayContainer({
...defaultEmailFrom,
...values,
html: rawHtml,
attachments:
values.fileList &&
(await Promise.all(
values.fileList.map(async (f) => await toBase64(f.originFileObj))
)),
attachments: [
...(values.fileList
? await Promise.all(
values.fileList.map(async (f) => {
return {
filename: f.name,
path: await toBase64(f.originFileObj),
};
})
)
: []),
...(pdfCopytoAttach.pdf
? [
{
path: pdfCopytoAttach.pdf,
filename:
pdfCopytoAttach.filename &&
`${pdfCopytoAttach.filename}.pdf`,
},
]
: []),
],
media: selectedMedia.filter((m) => m.isSelected).map((m) => m.src),
//attachments,
});
@@ -99,13 +120,22 @@ export function EmailOverlayContainer({
const render = async () => {
logImEXEvent("email_render_template", { template: emailConfig.template });
setLoading(true);
let html = await RenderTemplate(emailConfig.template, bodyshop, true);
let { html, pdf, filename } = await RenderTemplate(
emailConfig.template,
bodyshop,
true
);
const response = await axios.post("/render/inlinecss", {
html: html,
url: `${window.location.protocol}://${window.location.host}/`,
});
setRawHtml(response.data);
if (pdf) {
setPdfCopytoAttach({ pdf, filename });
}
form.setFieldsValue({
...emailConfig.messageOptions,
cc:
@@ -166,8 +196,8 @@ const toBase64 = (file) =>
reader.onerror = (error) => reject(error);
});
const asyncForEach = async (array, callback) => {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
};
// const asyncForEach = async (array, callback) => {
// for (let index = 0; index < array.length; index++) {
// await callback(array[index], index, array);
// }
// };

View File

@@ -161,7 +161,7 @@ export function Jobd3RdPartyModal({ bodyshop, jobId }) {
<Input />
</Form.Item>
<Form.Item
label={t("printcenter.jobs.3rdpartyfields.ponumber")}
label={t("printcenter.jobs.3rdpartyfields.refnumber")}
name="ponumber"
>
<Input />

View File

@@ -1,5 +1,5 @@
import { useLazyQuery } from "@apollo/client";
import { Button, DatePicker, Form, Radio } from "antd";
import { Button, DatePicker, Form, Radio, Space } from "antd";
import moment from "moment";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
@@ -13,6 +13,7 @@ import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import "./report-center-modal.styles.scss";
const mapStateToProps = createStructuredSelector({
reportCenterModal: selectReportCenter,
@@ -86,6 +87,7 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
<Form.Item
name="key"
label={t("reportcenter.labels.key")}
// className="radio-group-columns"
rules={[
{
required: true,
@@ -93,12 +95,21 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
},
]}
>
<Radio.Group style={{ columns: "3 auto" }}>
{Object.keys(Templates).map((key) => (
<Radio key={key} value={key}>
{Templates[key].title}
</Radio>
))}
<Radio.Group>
<Space
direction="vertical"
wrap
size="small"
style={{
maxHeight: "50vh",
}}
>
{Object.keys(Templates).map((key) => (
<Radio key={key} value={key}>
{Templates[key].title}
</Radio>
))}
</Space>
</Radio.Group>
</Form.Item>
<Form.Item dependencies={["key"]}>

View File

@@ -31,7 +31,7 @@ export function ReportCenterModalContainer({
onCancel={() => toggleModalVisible()}
cancelButtonProps={{ style: { display: "none" } }}
destroyOnClose
width="60%"
width="80%"
>
<ReportCenterModalComponent />
</Modal>

View File

@@ -0,0 +1,11 @@
.radio-group-columns {
.ant-radio-group {
// display: block;
}
.ant-radio-wrapper {
display: block;
span {
word-wrap: break-word;
}
}
}

View File

@@ -436,6 +436,14 @@ export default function ShopInfoGeneral({ form }) {
>
<CurrencyInput />
</Form.Item>
<Form.Item
name={["attach_pdf_to_email"]}
label={t("bodyshop.fields.attach_pdf_to_email")}
valuePropName="checked"
>
<Switch />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow header={t("bodyshop.labels.messagingpresets")}>
<Form.List name={["md_messaging_presets"]}>

View File

@@ -91,6 +91,7 @@ export const QUERY_BODYSHOP = gql`
md_jobline_presets
cdk_dealerid
features
attach_pdf_to_email
employees {
id
active
@@ -178,6 +179,7 @@ export const UPDATE_SHOP = gql`
jc_hourly_rates
md_jobline_presets
cdk_dealerid
attach_pdf_to_email
employees {
id
first_name

View File

@@ -689,6 +689,8 @@ export const QUERY_JOB_CARD_DETAILS = gql`
v_make_desc
v_model_desc
v_color
v_vin
plate_st
plate_no
vehicle {
id

View File

@@ -199,6 +199,7 @@
"label": "Label"
},
"appt_length": "Default Appointment Length",
"attach_pdf_to_email": "Attach PDF copy to sent emails?",
"bill_federal_tax_rate": "Bills - Federal Tax Rate %",
"bill_local_tax_rate": "Bill - Provincial/State Tax Rate %",
"bill_state_tax_rate": "Bill - Provincial/State Tax Rate %",
@@ -742,6 +743,7 @@
"attachments": "Attachments",
"documents": "Documents",
"generatingemail": "Generating email...",
"pdfcopywillbeattached": "A PDF copy of this email will be attached when it is sent.",
"preview": "Email Preview"
},
"successes": {
@@ -1807,6 +1809,7 @@
"depreciation": "Depreciation",
"other": "Other",
"ponumber": "PO Number",
"refnumber": "Reference Number",
"sendtype": "Send by",
"state": "Province/State",
"zip": "Postal Code/Zip"
@@ -1969,19 +1972,24 @@
"gsr_by_delivery_date": "Gross Sales by Delivery Date",
"gsr_by_estimator": "Gross Sales by Estimator",
"gsr_by_exported_date": "Gross Sales by Export Date",
"gsr_by_ins_co": "Gross Sales by Insurance Company'",
"gsr_by_ins_co": "Gross Sales by Insurance Company",
"gsr_by_make": "Gross Sales by Vehicle Make",
"gsr_by_referral": "Gross Sales by Referral Source",
"gsr_by_ro": "Gross Sales by RO",
"gsr_labor_only": "Gross Sales - Labor Only",
"hours_sold_detail_closed": "Hours Sold Detail - Closed",
"hours_sold_detail_closed_csr": "Hours Sold Detail - Closed by CSR",
"hours_sold_detail_closed_ins_co": "Hours Sold Detail - Closed by Source",
"hours_sold_detail_open": "Hours Sold Detail - Open",
"hours_sold_detail_open_csr": "Hours Sold Detail - Open by CSR",
"hours_sold_detail_open_ins_co": "Hours Sold Detail - Open by Source",
"hours_sold_summary_closed": "Hours Sold Summary - Closed",
"hours_sold_summary_closed_csr": "Hours Sold Summary - Closed by CSR",
"hours_sold_summary_closed_ins_co": "Hours Sold Summary - Closed by Source",
"hours_sold_summary_open": "Hours Sold Summary - Open",
"hours_sold_summary_open_csr": "Hours Sold Summary - Open CSR",
"hours_sold_summary_open_ins_co": "Hours Sold Summary - Open by Source",
"job_costing_ro_csr": "Job Costing by CSR",
"job_costing_ro_date_detail": "Job Costing by RO - Detail",
"job_costing_ro_date_summary": "Job Costing by RO - Summary",
"job_costing_ro_estimator": "Job Costing by Estimator",

View File

@@ -199,6 +199,7 @@
"label": ""
},
"appt_length": "",
"attach_pdf_to_email": "",
"bill_federal_tax_rate": "",
"bill_local_tax_rate": "",
"bill_state_tax_rate": "",
@@ -742,6 +743,7 @@
"attachments": "",
"documents": "",
"generatingemail": "",
"pdfcopywillbeattached": "",
"preview": ""
},
"successes": {
@@ -1807,6 +1809,7 @@
"depreciation": "",
"other": "",
"ponumber": "",
"refnumber": "",
"sendtype": "",
"state": "",
"zip": ""
@@ -1975,13 +1978,18 @@
"gsr_by_ro": "",
"gsr_labor_only": "",
"hours_sold_detail_closed": "",
"hours_sold_detail_closed_csr": "",
"hours_sold_detail_closed_ins_co": "",
"hours_sold_detail_open": "",
"hours_sold_detail_open_csr": "",
"hours_sold_detail_open_ins_co": "",
"hours_sold_summary_closed": "",
"hours_sold_summary_closed_csr": "",
"hours_sold_summary_closed_ins_co": "",
"hours_sold_summary_open": "",
"hours_sold_summary_open_csr": "",
"hours_sold_summary_open_ins_co": "",
"job_costing_ro_csr": "",
"job_costing_ro_date_detail": "",
"job_costing_ro_date_summary": "",
"job_costing_ro_estimator": "",

View File

@@ -199,6 +199,7 @@
"label": ""
},
"appt_length": "",
"attach_pdf_to_email": "",
"bill_federal_tax_rate": "",
"bill_local_tax_rate": "",
"bill_state_tax_rate": "",
@@ -742,6 +743,7 @@
"attachments": "",
"documents": "",
"generatingemail": "",
"pdfcopywillbeattached": "",
"preview": ""
},
"successes": {
@@ -1807,6 +1809,7 @@
"depreciation": "",
"other": "",
"ponumber": "",
"refnumber": "",
"sendtype": "",
"state": "",
"zip": ""
@@ -1975,13 +1978,18 @@
"gsr_by_ro": "",
"gsr_labor_only": "",
"hours_sold_detail_closed": "",
"hours_sold_detail_closed_csr": "",
"hours_sold_detail_closed_ins_co": "",
"hours_sold_detail_open": "",
"hours_sold_detail_open_csr": "",
"hours_sold_detail_open_ins_co": "",
"hours_sold_summary_closed": "",
"hours_sold_summary_closed_csr": "",
"hours_sold_summary_closed_ins_co": "",
"hours_sold_summary_open": "",
"hours_sold_summary_open_csr": "",
"hours_sold_summary_open_ins_co": "",
"job_costing_ro_csr": "",
"job_costing_ro_date_detail": "",
"job_costing_ro_date_summary": "",
"job_costing_ro_estimator": "",

View File

@@ -8,6 +8,7 @@ import { setEmailOptions } from "../redux/email/email.actions";
import { store } from "../redux/store";
import client from "../utils/GraphQLClient";
import { TemplateList } from "./TemplateConstants";
import _ from "lodash";
const server = process.env.REACT_APP_REPORTS_SERVER_URL;
jsreport.serverUrl = server;
@@ -39,8 +40,10 @@ export default async function RenderTemplate(
offset: moment().utcOffset(),
},
};
try {
const render = await jsreport.renderAsync(reportRequest);
if (!renderAsHtml) {
render.download(
(Templates[templateObject.name] &&
@@ -48,8 +51,21 @@ export default async function RenderTemplate(
""
);
} else {
let pdf;
if (bodyshop.attach_pdf_to_email) {
const pdfRequest = _.cloneDeep(reportRequest);
pdfRequest.template.recipe = "chrome-pdf";
const pdfRender = await jsreport.renderAsync(pdfRequest);
pdf = pdfRender.toDataURI();
}
return new Promise((resolve, reject) => {
resolve(render.toString());
resolve({
pdf,
filename:
Templates[templateObject.name] &&
Templates[templateObject.name].title,
html: render.toString(),
});
});
}
} catch (error) {

View File

@@ -752,6 +752,67 @@ export const TemplateList = (type, context) => {
field: i18n.t("jobs.fields.date_open"),
},
},
hours_sold_detail_closed_csr: {
title: i18n.t(
"reportcenter.templates.hours_sold_detail_closed_csr"
),
description: "",
subject: i18n.t(
"reportcenter.templates.hours_sold_detail_closed_csr"
),
key: "hours_sold_detail_closed_csr",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_invoiced"),
},
},
hours_sold_detail_open_csr: {
title: i18n.t("reportcenter.templates.hours_sold_detail_open_csr"),
description: "",
subject: i18n.t(
"reportcenter.templates.hours_sold_detail_open_csr"
),
key: "hours_sold_detail_open_csr",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_open"),
},
},
hours_sold_summary_closed_csr: {
title: i18n.t(
"reportcenter.templates.hours_sold_summary_closed_csr"
),
description: "",
subject: i18n.t(
"reportcenter.templates.hours_sold_summary_closed_csr"
),
key: "hours_sold_summary_closed_csr",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_invoiced"),
},
},
hours_sold_summary_open_csr: {
title: i18n.t("reportcenter.templates.hours_sold_summary_open_csr"),
description: "",
subject: i18n.t(
"reportcenter.templates.hours_sold_summary_open_csr"
),
key: "hours_sold_summary_open_csr",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_invoiced"),
},
},
estimator_detail: {
title: i18n.t("reportcenter.templates.estimator_detail"),
description: "",
@@ -814,6 +875,18 @@ export const TemplateList = (type, context) => {
field: i18n.t("jobs.fields.date_invoiced"),
},
},
job_costing_ro_csr: {
title: i18n.t("reportcenter.templates.job_costing_ro_csr"),
description: "",
subject: i18n.t("reportcenter.templates.job_costing_ro_csr"),
key: "job_costing_ro_csr",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_open"),
},
},
job_costing_ro_ins_co: {
title: i18n.t("reportcenter.templates.job_costing_ro_ins_co"),
description: "",

View File

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

View File

@@ -0,0 +1,6 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."bodyshops" ADD COLUMN "attach_pdf_to_email" boolean
NOT NULL DEFAULT False;
type: run_sql

View File

@@ -0,0 +1,87 @@
- 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
- cdk_dealerid
- city
- country
- created_at
- default_adjustment_rate
- deliverchecklist
- email
- enforce_class
- enforce_referral
- features
- federal_tax_id
- id
- imexshopid
- inhousevendorid
- insurance_vendor_id
- intakechecklist
- jc_hourly_rates
- jobsizelimit
- logo_img_path
- md_categories
- md_ccc_rates
- md_classes
- md_hour_split
- md_ins_cos
- md_jobline_presets
- md_labor_rates
- md_messaging_presets
- md_notes_presets
- md_order_statuses
- md_parts_locations
- md_payment_types
- 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
- sub_status
- target_touchtime
- template_header
- textid
- updated_at
- use_fippa
- website
- workingdays
- zip_post
computed_fields: []
filter:
associations:
user:
authid:
_eq: X-Hasura-User-Id
role: user
table:
name: bodyshops
schema: public
type: create_select_permission

View File

@@ -0,0 +1,88 @@
- 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
- attach_pdf_to_email
- bill_tax_rates
- cdk_dealerid
- city
- country
- created_at
- default_adjustment_rate
- deliverchecklist
- email
- enforce_class
- enforce_referral
- features
- federal_tax_id
- id
- imexshopid
- inhousevendorid
- insurance_vendor_id
- intakechecklist
- jc_hourly_rates
- jobsizelimit
- logo_img_path
- md_categories
- md_ccc_rates
- md_classes
- md_hour_split
- md_ins_cos
- md_jobline_presets
- md_labor_rates
- md_messaging_presets
- md_notes_presets
- md_order_statuses
- md_parts_locations
- md_payment_types
- 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
- sub_status
- target_touchtime
- template_header
- textid
- updated_at
- use_fippa
- website
- workingdays
- zip_post
computed_fields: []
filter:
associations:
user:
authid:
_eq: X-Hasura-User-Id
role: user
table:
name: bodyshops
schema: public
type: create_select_permission

View File

@@ -0,0 +1,79 @@
- args:
role: user
table:
name: bodyshops
schema: public
type: drop_update_permission
- args:
permission:
columns:
- accountingconfig
- address1
- address2
- appt_alt_transport
- appt_colors
- appt_length
- bill_tax_rates
- city
- country
- created_at
- default_adjustment_rate
- deliverchecklist
- email
- enforce_class
- enforce_referral
- federal_tax_id
- id
- inhousevendorid
- insurance_vendor_id
- intakechecklist
- jc_hourly_rates
- logo_img_path
- md_categories
- md_ccc_rates
- md_classes
- md_hour_split
- md_ins_cos
- md_jobline_presets
- md_labor_rates
- md_messaging_presets
- md_notes_presets
- md_order_statuses
- md_parts_locations
- md_payment_types
- md_rbac
- md_referral_sources
- md_responsibility_centers
- md_ro_statuses
- phone
- prodtargethrs
- production_config
- schedule_end_time
- schedule_start_time
- scoreboard_target
- shopname
- shoprates
- speedprint
- ssbuckets
- state
- state_tax_id
- target_touchtime
- updated_at
- use_fippa
- website
- workingdays
- zip_post
filter:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
set: {}
role: user
table:
name: bodyshops
schema: public
type: create_update_permission

View File

@@ -0,0 +1,80 @@
- args:
role: user
table:
name: bodyshops
schema: public
type: drop_update_permission
- args:
permission:
columns:
- accountingconfig
- address1
- address2
- appt_alt_transport
- appt_colors
- appt_length
- attach_pdf_to_email
- bill_tax_rates
- city
- country
- created_at
- default_adjustment_rate
- deliverchecklist
- email
- enforce_class
- enforce_referral
- federal_tax_id
- id
- inhousevendorid
- insurance_vendor_id
- intakechecklist
- jc_hourly_rates
- logo_img_path
- md_categories
- md_ccc_rates
- md_classes
- md_hour_split
- md_ins_cos
- md_jobline_presets
- md_labor_rates
- md_messaging_presets
- md_notes_presets
- md_order_statuses
- md_parts_locations
- md_payment_types
- md_rbac
- md_referral_sources
- md_responsibility_centers
- md_ro_statuses
- phone
- prodtargethrs
- production_config
- schedule_end_time
- schedule_start_time
- scoreboard_target
- shopname
- shoprates
- speedprint
- ssbuckets
- state
- state_tax_id
- target_touchtime
- updated_at
- use_fippa
- website
- workingdays
- zip_post
filter:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
set: {}
role: user
table:
name: bodyshops
schema: public
type: create_update_permission

View File

@@ -756,6 +756,7 @@ tables:
- appt_alt_transport
- appt_colors
- appt_length
- attach_pdf_to_email
- bill_tax_rates
- cdk_dealerid
- city
@@ -831,6 +832,7 @@ tables:
- appt_alt_transport
- appt_colors
- appt_length
- attach_pdf_to_email
- bill_tax_rates
- city
- country

View File

@@ -39,6 +39,7 @@
"phone": "^2.4.20",
"soap": "^0.39.0",
"socket.io": "^4.1.2",
"ssh2-sftp-client": "^7.0.0",
"stripe": "^8.148.0",
"twilio": "^3.62.0",
"xmlbuilder2": "^2.4.1"

View File

@@ -139,7 +139,7 @@ app.post("/qbo/authorize", qbo.authorize);
app.get("/qbo/callback", qbo.callback);
var data = require("./server/data/data");
app.get("/data/ah", data.autohouse);
app.post("/data/ah", data.autohouse);
var ioevent = require("./server/ioevent/ioevent");
app.post("/ioevent", ioevent.default);

View File

@@ -11,42 +11,124 @@ require("dotenv").config({
`.env.${process.env.NODE_ENV || "development"}`
),
});
let Client = require("ssh2-sftp-client");
const client = require("../graphql-client/graphql-client").client;
const AHDineroFormat = "0.00";
const AhDateFormat = "MMDDYYYY";
const repairOpCodes = ["OP4", "OP9", "OP10"];
const replaceOpCodes = ["OP2", "OP5", "OP11", "OP12"];
const ftpSetup = {
host: process.env.AUTOHOUSE_HOST,
port: process.env.AUTOHOUSE_PORT,
username: process.env.AUTOHOUSE_USER,
password: process.env.AUTOHOUSE_PASSWORD,
//debug: console.log,
};
exports.default = async (req, res) => {
//Get Client Dataset.
const { jobs } = await client.request(queries.AUTOHOUSE_QUERY);
//Query for the List of Bodyshop Clients.
console.log("Starting Autohouse datapump request.");
const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS);
const erroredJobs = [];
const allxmlsToUpload = [];
const allErrors = [];
try {
for (const bodyshop of bodyshops) {
console.log("Starting extract for ", bodyshop.shopname);
const erroredJobs = [];
try {
const { jobs } = await client.request(queries.AUTOHOUSE_QUERY, {
bodyshopid: bodyshop.id,
});
const autoHouseObject = {
AutoHouseExport: {
RepairOrder: jobs.map((j) =>
CreateRepairOrderTag(j, (job, error) => {
erroredJobs.push({ job, error });
})
),
},
};
const autoHouseObject = {
AutoHouseExport: {
RepairOrder: jobs.map((j) =>
CreateRepairOrderTag(
{ ...j, bodyshop },
function ({ job, error }) {
erroredJobs.push({ job: job, error: error.toString() });
}
)
),
},
};
console.log(
"***Number of Failed jobs***: ",
erroredJobs.length,
JSON.stringify(erroredJobs.map((x) => x.error))
);
var ret = builder
.create(autoHouseObject, {
version: "1.0",
encoding: "UTF-8",
})
.end({ pretty: true, allowEmptyTags: true });
console.log(
bodyshop.shopname,
"***Number of Failed jobs***: ",
erroredJobs.length,
JSON.stringify(erroredJobs.map((j) => j.job.ro_number))
);
//***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml
res.type("application/xml");
//res.sendFile(ret);
res.send(ret);
var ret = builder
.create(autoHouseObject, {
version: "1.0",
encoding: "UTF-8",
})
.end({ pretty: true, allowEmptyTags: true });
allxmlsToUpload.push({
xml: ret,
filename: `IM_${bodyshop.imexshopid}_${moment().format(
"DDMMYYYY_HHMMSS"
)}.xml`,
});
console.log("Finished extract for shop ", bodyshop.shopname);
} catch (error) {
//Error at the shop level.
console.log("Error at shop level", bodyshop.shopname, error);
allErrors.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
fatal: true,
errors: [error.toString()],
});
} finally {
allErrors.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
errors: erroredJobs,
});
}
}
let sftp = new Client();
sftp.on("error", (errors) =>
console.log("Error in FTP client", JSON.stringify(errors))
);
try {
//Connect to the FTP and upload all.
await sftp.connect(ftpSetup);
for (const xmlObj of allxmlsToUpload) {
console.log("Uploading", xmlObj.filename);
const uploadResult = await sftp.put(
Buffer.from(xmlObj.xml),
`/${xmlObj.filename}`
);
console.log(
"🚀 ~ file: autohouse.js ~ line 94 ~ uploadResult",
uploadResult
);
}
//***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml
} catch (error) {
console.log("Error when connecting to FTP", error);
} finally {
sftp.end();
}
res.sendStatus(200);
} catch (error) {
res.JSON(error).sendStatus(500);
}
};
const CreateRepairOrderTag = (job, errorCallback) => {
@@ -58,9 +140,9 @@ const CreateRepairOrderTag = (job, errorCallback) => {
const ret = {
RepairOrderInformation: {
ShopInternalName: job.bodyshop.autohouseid,
ID: job.id,
ID: parseInt(job.ro_number.match(/\d/g).join(""), 10),
RO: job.ro_number,
Est: job.id, //We no longer use estimate id.
Est: parseInt(job.ro_number.match(/\d/g).join(""), 10), //We no longer use estimate id.
GUID: job.id,
TransType: StatusMapping(job.status, job.bodyshop.md_ro_statuses),
ShopName: job.bodyshop.shopname,
@@ -69,8 +151,12 @@ const CreateRepairOrderTag = (job, errorCallback) => {
ShopState: job.bodyshop.state,
ShopZip: job.bodyshop.zip_post,
ShopPhone: job.bodyshop.phone,
EstimatorID: `${job.est_ct_fn || ""} ${job.est_ct_ln || ""}`,
EstimatorName: `${job.est_ct_fn || ""} ${job.est_ct_ln || ""}`,
EstimatorID: `${job.est_ct_ln ? job.est_ct_ln : ""}${
job.est_ct_ln ? ", " : ""
}${job.est_ct_fn ? job.est_ct_fn : ""}`,
EstimatorName: `${job.est_ct_ln ? job.est_ct_ln : ""}${
job.est_ct_ln ? ", " : ""
}${job.est_ct_fn ? job.est_ct_fn : ""}`,
},
CustomerInformation: {
FirstName: job.ownr_fn,
@@ -410,7 +496,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
return ret;
} catch (error) {
console.log("Error calculating job", error);
errorCallback(job, error);
errorCallback({ job, error });
}
};
@@ -611,6 +697,3 @@ const generateNullDetailLine = () => {
EstimateAmount: null,
};
};
const repairOpCodes = ["OP4", "OP9", "OP10"];
const replaceOpCodes = ["OP2", "OP5", "OP11", "OP12"];

View File

@@ -48,7 +48,8 @@ exports.sendEmail = async (req, res) => {
...((req.body.attachments &&
req.body.attachments.map((a) => {
return {
path: a,
filename: a.filename,
path: a.path,
};
})) ||
[]),

View File

@@ -357,8 +357,8 @@ exports.QUERY_EMPLOYEE_PIN = `query QUERY_EMPLOYEE_PIN($shopId: uuid!, $employee
}
}`;
exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz) {
jobs(where: {_and: [{updated_at: {_gt: $start}}, {bodyshop: {autohouseid: {_is_null: false}}}]}) {
exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshopid: uuid!) {
jobs(where: {_and: [{converted :{_eq: true}},{updated_at: {_gt: $start}}, {shopid: {_eq: $bodyshopid}}]}) {
id
ro_number
status
@@ -433,10 +433,10 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz) {
md_ro_statuses
md_order_statuses
autohouseid
md_responsibility_centers
jc_hourly_rates
md_responsibility_centers
jc_hourly_rates
}
joblines (where:{removed: {_eq:false}}){
joblines(where: {removed: {_eq: false}}) {
id
line_no
status
@@ -452,40 +452,40 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz) {
part_type
oem_partno
lbr_op
profitcenter_part
profitcenter_labor
billlines (order_by:{bill:{date:desc_nulls_last}}) {
profitcenter_part
profitcenter_labor
billlines(order_by: {bill: {date: desc_nulls_last}}) {
actual_cost
actual_price
quantity
bill {
vendor{
vendor {
name
}
invoice_number
}
}
} bills {
id
federal_tax_rate
local_tax_rate
state_tax_rate
is_credit_memo
billlines {
actual_cost
cost_center
id
quantity
}
}
timetickets {
id
rate
}
bills {
id
federal_tax_rate
local_tax_rate
state_tax_rate
is_credit_memo
billlines {
actual_cost
cost_center
actualhrs
productivehrs
id
quantity
}
}
timetickets {
id
rate
cost_center
actualhrs
productivehrs
}
area_of_damage
employee_prep_rel {
first_name
@@ -507,6 +507,7 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz) {
}
}
}
`;
exports.UPDATE_JOB = `
@@ -906,3 +907,24 @@ exports.INSERT_IOEVENT = ` mutation INSERT_IOEVENT($event: ioevents_insert_input
}
}
`;
exports.GET_AUTOHOUSE_SHOPS = `query GET_AUTOHOUSE_SHOPS {
bodyshops(where: {autohouseid: {_is_null: false}}){
id
shopname
address1
city
state
zip_post
country
phone
md_ro_statuses
md_order_statuses
autohouseid
md_responsibility_centers
jc_hourly_rates
imexshopid
}
}
`;

View File

@@ -529,7 +529,7 @@ asap@^2.0.0:
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
asn1@~0.2.3:
asn1@^0.2.4, asn1@~0.2.3:
version "0.2.4"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
@@ -644,7 +644,7 @@ batch@^0.6.1:
resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=
bcrypt-pbkdf@^1.0.0:
bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
@@ -915,6 +915,16 @@ concat-stream@^1.4.7:
readable-stream "^2.2.2"
typedarray "^0.0.6"
concat-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1"
integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==
dependencies:
buffer-from "^1.0.0"
inherits "^2.0.3"
readable-stream "^3.0.2"
typedarray "^0.0.6"
concurrently@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-6.0.2.tgz#4ecdfc78a72a6f626a3a5d3c2a7a81962f3663e3"
@@ -999,6 +1009,13 @@ cors@2.8.5, cors@~2.8.5:
object-assign "^4"
vary "^1"
cpu-features@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.2.tgz#9f636156f1155fd04bdbaa028bb3c2fbef3cea7a"
integrity sha512-/2yieBqvMcRj8McNzkycjW2v3OIUOibBfd2dLEJ0nWts8NobAxwiyw9phVNS6oDL8x8tz9F7uNVFEVpJncQpeA==
dependencies:
nan "^2.14.1"
cross-fetch@^3.0.6:
version "3.1.4"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39"
@@ -1345,6 +1362,11 @@ entities@^2.0.0:
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
err-code@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9"
integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==
error-ex@^1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
@@ -2839,6 +2861,11 @@ ms@^2.1.1:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
nan@^2.14.1, nan@^2.14.2:
version "2.14.2"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
@@ -3136,6 +3163,14 @@ progress@^2.0.0:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
promise-retry@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22"
integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==
dependencies:
err-code "^2.0.2"
retry "^0.12.0"
protobufjs@^6.10.2, protobufjs@^6.8.6:
version "6.10.2"
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.10.2.tgz#b9cb6bd8ec8f87514592ba3fdfd28e93f33a469b"
@@ -3347,7 +3382,7 @@ readable-stream@2, readable-stream@^2.2.2, readable-stream@^2.3.5, readable-stre
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
@@ -3431,7 +3466,7 @@ retry-request@^4.0.0, retry-request@^4.1.1:
dependencies:
debug "^4.1.1"
retry@0.12.0:
retry@0.12.0, retry@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=
@@ -3766,6 +3801,26 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
ssh2-sftp-client@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/ssh2-sftp-client/-/ssh2-sftp-client-7.0.0.tgz#38c3420319156d030a80ac9db1df7459d2ba42a0"
integrity sha512-o++ryEeSbAQ6GzjuXs6BHnST6zsoWUZYt9cLy6XQ4+WdL6jNuU6UjyQzvg1J3IgN4LpCSAI+9EyTeKeIb0AfSQ==
dependencies:
concat-stream "^2.0.0"
promise-retry "^2.0.1"
ssh2 "^1.1.0"
ssh2@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.1.0.tgz#43dd24930e15e317687f519d6b40270d9cd00d00"
integrity sha512-CidQLG2ZacoT0Z7O6dOyisj4JdrOrLVJ4KbHjVNz9yI1vO08FAYQPcnkXY9BP8zeYo+J/nBgY6Gg4R7w4WFWtg==
dependencies:
asn1 "^0.2.4"
bcrypt-pbkdf "^1.0.2"
optionalDependencies:
cpu-features "0.0.2"
nan "^2.14.2"
sshpk@^1.7.0:
version "1.16.1"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"