diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel
index d11e1ed2d..fe1e7a2a0 100644
--- a/bodyshop_translations.babel
+++ b/bodyshop_translations.babel
@@ -4598,6 +4598,27 @@
+
+ md_from_emails
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
md_hour_split
@@ -7280,6 +7301,32 @@
+
+ ss_configuration
+
+
+ dailyhrslimit
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+
ssbuckets
@@ -13205,6 +13252,27 @@
+
+ from
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
subject
false
diff --git a/client/src/components/email-overlay/email-overlay.component.jsx b/client/src/components/email-overlay/email-overlay.component.jsx
index 6826eba76..baf52d376 100644
--- a/client/src/components/email-overlay/email-overlay.component.jsx
+++ b/client/src/components/email-overlay/email-overlay.component.jsx
@@ -16,9 +16,14 @@ import EmailDocumentsComponent from "../email-documents/email-documents.componen
import _ from "lodash";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
-import { selectBodyshop } from "../../redux/user/user.selectors";
+import {
+ selectBodyshop,
+ selectCurrentUser,
+} from "../../redux/user/user.selectors";
+
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
+ currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
@@ -28,7 +33,12 @@ export default connect(
mapDispatchToProps
)(EmailOverlayComponent);
-export function EmailOverlayComponent({ form, selectedMediaState, bodyshop }) {
+export function EmailOverlayComponent({
+ form,
+ selectedMediaState,
+ bodyshop,
+ currentUser,
+}) {
const { t } = useTranslation();
const handleClick = ({ item, key, keyPath }) => {
const email = item.props.value;
@@ -51,6 +61,27 @@ export function EmailOverlayComponent({ form, selectedMediaState, bodyshop }) {
return (
+
+
+
diff --git a/client/src/components/email-overlay/email-overlay.container.jsx b/client/src/components/email-overlay/email-overlay.container.jsx
index c2dc7d261..9276f35ee 100644
--- a/client/src/components/email-overlay/email-overlay.container.jsx
+++ b/client/src/components/email-overlay/email-overlay.container.jsx
@@ -56,13 +56,9 @@ export function EmailOverlayContainer({
: bodyshop.shopname,
address: EmailSettings.fromAddress,
},
- ReplyTo: {
- Email: currentUser.validemail ? currentUser.email : bodyshop.email,
- Name: currentUser.displayName,
- },
};
- const handleFinish = async (values) => {
+ const handleFinish = async (allValues) => {
logImEXEvent("email_send_from_modal");
//const attachments = [];
@@ -77,10 +73,15 @@ export function EmailOverlayContainer({
// attachments.push(t);
// });
+ const { from, ...values } = allValues;
setSending(true);
try {
await axios.post("/sendemail", {
...defaultEmailFrom,
+ ReplyTo: {
+ Email: from,
+ Name: currentUser.displayName,
+ },
...values,
html: rawHtml,
attachments: [
@@ -138,6 +139,7 @@ export function EmailOverlayContainer({
}
form.setFieldsValue({
+ from: currentUser.validemail ? currentUser.email : bodyshop.email,
...emailConfig.messageOptions,
cc:
emailConfig.messageOptions.cc &&
diff --git a/client/src/components/shop-info/shop-info.general.component.jsx b/client/src/components/shop-info/shop-info.general.component.jsx
index 29288b9ee..3b2b748a0 100644
--- a/client/src/components/shop-info/shop-info.general.component.jsx
+++ b/client/src/components/shop-info/shop-info.general.component.jsx
@@ -507,6 +507,18 @@ export default function ShopInfoGeneral({ form }) {
>
+
+
+
+
+
+
{t("bodyshop.labels.workingdays")}
diff --git a/client/src/graphql/bodyshop.queries.js b/client/src/graphql/bodyshop.queries.js
index f0bd54a21..0ce955c15 100644
--- a/client/src/graphql/bodyshop.queries.js
+++ b/client/src/graphql/bodyshop.queries.js
@@ -101,6 +101,8 @@ export const QUERY_BODYSHOP = gql`
md_filehandlers
md_email_cc
timezone
+ ss_configuration
+ md_from_emails
employees {
user_email
id
@@ -199,6 +201,8 @@ export const UPDATE_SHOP = gql`
md_filehandlers
md_email_cc
timezone
+ ss_configuration
+ md_from_emails
employees {
id
first_name
@@ -266,7 +270,6 @@ export const QUERY_DELIVER_CHECKLIST = gql`
ro_number
actual_completion
actual_delivery
-
}
}
`;
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index 255a81a03..0c456db00 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -285,6 +285,7 @@
"md_classes": "Classes",
"md_ded_notes": "Deductible Notes",
"md_email_cc": "Auto Email CC: $t(printcenter.subjects.jobs.{{template}})",
+ "md_from_emails": "Additional From Emails",
"md_hour_split": {
"paint": "Paint Hour Split",
"prep": "Prep Hour Split"
@@ -455,6 +456,9 @@
"label": "Label",
"templates": "Templates"
},
+ "ss_configuration": {
+ "dailyhrslimit": "Daily Incoming Hours Limit"
+ },
"ssbuckets": {
"gte": "Greater Than/Equal to (hrs)",
"id": "ID",
@@ -827,6 +831,7 @@
},
"fields": {
"cc": "CC",
+ "from": "From",
"subject": "Subject",
"to": "To"
},
diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json
index 336ba07d4..ea698ce2a 100644
--- a/client/src/translations/es/common.json
+++ b/client/src/translations/es/common.json
@@ -285,6 +285,7 @@
"md_classes": "",
"md_ded_notes": "",
"md_email_cc": "",
+ "md_from_emails": "",
"md_hour_split": {
"paint": "",
"prep": ""
@@ -455,6 +456,9 @@
"label": "",
"templates": ""
},
+ "ss_configuration": {
+ "dailyhrslimit": ""
+ },
"ssbuckets": {
"gte": "",
"id": "",
@@ -827,6 +831,7 @@
},
"fields": {
"cc": "",
+ "from": "",
"subject": "",
"to": ""
},
diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json
index 67435a1fc..70f0141b2 100644
--- a/client/src/translations/fr/common.json
+++ b/client/src/translations/fr/common.json
@@ -285,6 +285,7 @@
"md_classes": "",
"md_ded_notes": "",
"md_email_cc": "",
+ "md_from_emails": "",
"md_hour_split": {
"paint": "",
"prep": ""
@@ -455,6 +456,9 @@
"label": "",
"templates": ""
},
+ "ss_configuration": {
+ "dailyhrslimit": ""
+ },
"ssbuckets": {
"gte": "",
"id": "",
@@ -827,6 +831,7 @@
},
"fields": {
"cc": "",
+ "from": "",
"subject": "",
"to": ""
},
diff --git a/client/src/utils/RenderTemplate.js b/client/src/utils/RenderTemplate.js
index 7aab1139a..716322937 100644
--- a/client/src/utils/RenderTemplate.js
+++ b/client/src/utils/RenderTemplate.js
@@ -24,13 +24,34 @@ export default async function RenderTemplate(
let { contextData, useShopSpecificTemplate } = await fetchContextData(
templateObject
);
+ const { ignoreCustomMargins } = Templates[templateObject.name];
let reportRequest = {
template: {
name: useShopSpecificTemplate
? `/${bodyshop.imexshopid}/${templateObject.name}`
: `/${templateObject.name}`,
- ...(renderAsHtml ? {} : { recipe: "chrome-pdf" }),
+ ...(renderAsHtml
+ ? {}
+ : {
+ recipe: "chrome-pdf",
+ ...(!ignoreCustomMargins && {
+ chrome: {
+ marginTop:
+ bodyshop.logo_img_path &&
+ bodyshop.logo_img_path.headerMargin &&
+ bodyshop.logo_img_path.headerMargin > 36
+ ? bodyshop.logo_img_path.headerMargin
+ : "36px",
+ marginBottom:
+ bodyshop.logo_img_path &&
+ bodyshop.logo_img_path.footerMargin &&
+ bodyshop.logo_img_path.footerMargin > 36
+ ? bodyshop.logo_img_path.footerMargin
+ : "36px",
+ },
+ }),
+ }),
...(renderAsExcel ? { recipe: "html-to-xlsx" } : {}),
},
data: {
@@ -121,7 +142,25 @@ export async function RenderTemplates(
name: rootTemplate.useShopSpecificTemplate
? `/${bodyshop.imexshopid}/${rootTemplate.templateObject.name}`
: `/${rootTemplate.templateObject.name}`,
- ...(renderAsHtml ? {} : { recipe: "chrome-pdf" }),
+ ...(renderAsHtml
+ ? {}
+ : {
+ recipe: "chrome-pdf",
+ chrome: {
+ marginTop:
+ bodyshop.logo_img_path &&
+ bodyshop.logo_img_path.headerMargin &&
+ bodyshop.logo_img_path.headerMargin > 36
+ ? bodyshop.logo_img_path.headerMargin
+ : "36px",
+ marginBottom:
+ bodyshop.logo_img_path &&
+ bodyshop.logo_img_path.footerMargin &&
+ bodyshop.logo_img_path.footerMargin > 36
+ ? bodyshop.logo_img_path.footerMargin
+ : "36px",
+ },
+ }),
pdfOperations: templateAndData.map((template) => {
return {
template: {
@@ -151,7 +190,7 @@ export async function RenderTemplates(
};
try {
- const render = await jsreport.renderAsync(reportRequest);
+ const render = await jsreport.renderAsync({ reportRequest });
if (!renderAsHtml) {
render.download("Speed Print");
} else {
diff --git a/client/src/utils/TemplateConstants.js b/client/src/utils/TemplateConstants.js
index 6201afada..06f624a4a 100644
--- a/client/src/utils/TemplateConstants.js
+++ b/client/src/utils/TemplateConstants.js
@@ -371,6 +371,7 @@ export const TemplateList = (type, context) => {
subject: i18n.t("printcenter.jobs.parts_label_single"),
disabled: false,
group: "ro",
+ ignoreCustomMargins: true,
},
envelope_return_address: {
title: i18n.t("printcenter.jobs.envelope_return_address"),
@@ -379,6 +380,7 @@ export const TemplateList = (type, context) => {
key: "envelope_return_address",
disabled: false,
group: "ro",
+ ignoreCustomMargins: true,
},
sgi_certificate_of_repairs: {
title: i18n.t("printcenter.jobs.sgi_certificate_of_repairs"),
diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml
index c91b048fc..52650b30f 100644
--- a/hasura/metadata/tables.yaml
+++ b/hasura/metadata/tables.yaml
@@ -834,6 +834,7 @@
- md_email_cc
- md_estimators
- md_filehandlers
+ - md_from_emails
- md_hour_split
- md_ins_cos
- md_jobline_presets
@@ -860,6 +861,7 @@
- shopname
- shoprates
- speedprint
+ - ss_configuration
- ssbuckets
- state
- state_tax_id
@@ -915,6 +917,7 @@
- md_email_cc
- md_estimators
- md_filehandlers
+ - md_from_emails
- md_hour_split
- md_ins_cos
- md_jobline_presets
@@ -938,6 +941,7 @@
- shopname
- shoprates
- speedprint
+ - ss_configuration
- ssbuckets
- state
- state_tax_id
diff --git a/hasura/migrations/1647876980174_alter_table_public_bodyshops_add_column_ss_configuration/down.sql b/hasura/migrations/1647876980174_alter_table_public_bodyshops_add_column_ss_configuration/down.sql
new file mode 100644
index 000000000..fbb2eef6c
--- /dev/null
+++ b/hasura/migrations/1647876980174_alter_table_public_bodyshops_add_column_ss_configuration/down.sql
@@ -0,0 +1,4 @@
+-- Could not auto-generate a down migration.
+-- Please write an appropriate down migration for the SQL below:
+-- alter table "public"."bodyshops" add column "ss_configuration" jsonb
+-- null default jsonb_build_object();
diff --git a/hasura/migrations/1647876980174_alter_table_public_bodyshops_add_column_ss_configuration/up.sql b/hasura/migrations/1647876980174_alter_table_public_bodyshops_add_column_ss_configuration/up.sql
new file mode 100644
index 000000000..c693c6d4e
--- /dev/null
+++ b/hasura/migrations/1647876980174_alter_table_public_bodyshops_add_column_ss_configuration/up.sql
@@ -0,0 +1,2 @@
+alter table "public"."bodyshops" add column "ss_configuration" jsonb
+ null default jsonb_build_object();
diff --git a/hasura/migrations/1648154167416_alter_table_public_bodyshops_add_column_md_from_emails/down.sql b/hasura/migrations/1648154167416_alter_table_public_bodyshops_add_column_md_from_emails/down.sql
new file mode 100644
index 000000000..00a90c958
--- /dev/null
+++ b/hasura/migrations/1648154167416_alter_table_public_bodyshops_add_column_md_from_emails/down.sql
@@ -0,0 +1,4 @@
+-- Could not auto-generate a down migration.
+-- Please write an appropriate down migration for the SQL below:
+-- alter table "public"."bodyshops" add column "md_from_emails" jsonb
+-- null default jsonb_build_array();
diff --git a/hasura/migrations/1648154167416_alter_table_public_bodyshops_add_column_md_from_emails/up.sql b/hasura/migrations/1648154167416_alter_table_public_bodyshops_add_column_md_from_emails/up.sql
new file mode 100644
index 000000000..108bbd7e8
--- /dev/null
+++ b/hasura/migrations/1648154167416_alter_table_public_bodyshops_add_column_md_from_emails/up.sql
@@ -0,0 +1,2 @@
+alter table "public"."bodyshops" add column "md_from_emails" jsonb
+ null default jsonb_build_array();
diff --git a/server/data/autohouse.js b/server/data/autohouse.js
index 8e0befe51..76030b5af 100644
--- a/server/data/autohouse.js
+++ b/server/data/autohouse.js
@@ -576,6 +576,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
SalesTaxTotal: Dinero(job.job_totals.totals.local_tax)
.add(Dinero(job.job_totals.totals.state_tax))
.add(Dinero(job.job_totals.totals.federal_tax))
+ .add(Dinero(job.job_totals.additional.pvrt))
.toFormat(AHDineroFormat),
SalesTaxTotalCost: 0,
GrossTotal: Dinero(job.job_totals.totals.total_repairs).toFormat(
diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js
index 306cb06cb..641adbd82 100644
--- a/server/graphql-client/queries.js
+++ b/server/graphql-client/queries.js
@@ -464,6 +464,7 @@ exports.QUERY_UPCOMING_APPOINTMENTS = `query QUERY_UPCOMING_APPOINTMENTS($now: t
jobs_by_pk(id: $jobId) {
bodyshop {
ssbuckets
+ ss_configuration
target_touchtime
workingdays
timezone
diff --git a/server/scheduling/scheduling-job.js b/server/scheduling/scheduling-job.js
index a314022c9..316bc7974 100644
--- a/server/scheduling/scheduling-job.js
+++ b/server/scheduling/scheduling-job.js
@@ -5,6 +5,7 @@ const Dinero = require("dinero.js");
const moment = require("moment-timezone");
const logger = require("../utils/logger");
const _ = require("lodash");
+const { filter } = require("lodash");
require("dotenv").config({
path: path.resolve(
process.cwd(),
@@ -32,7 +33,8 @@ exports.job = async (req, res) => {
});
const { jobs_by_pk, blockedDays, prodJobs, arrJobs, compJobs } = result;
- const { ssbuckets, workingdays, timezone } = result.jobs_by_pk.bodyshop;
+ const { ssbuckets, workingdays, timezone, ss_configuration } =
+ result.jobs_by_pk.bodyshop;
const jobHrs = result.jobs_by_pk.jobhrs.aggregate.sum.mod_lb_hrs;
const JobBucket = ssbuckets.filter(
@@ -63,29 +65,68 @@ exports.job = async (req, res) => {
}
});
- const filteredArrJobs = arrJobs.filter(
- (j) => JobBucket.id === CheckJobBucket(ssbuckets, j)
- );
+ // const filteredArrJobs = arrJobs.filter(
+ // (j) => JobBucket.id === CheckJobBucket(ssbuckets, j)
+ // );
+ const filteredArrJobs = [];
+
+ // filteredArrJobs.forEach((item) => {
+ // const itemDate = moment(item.scheduled_in)
+ // .tz(timezone)
+ // .format("yyyy-MM-DD");
+ // if (!!load[itemDate]) {
+ // load[itemDate].hoursIn =
+ // (load[itemDate].hoursIn || 0) +
+ // item.labhrs.aggregate.sum.mod_lb_hrs +
+ // item.larhrs.aggregate.sum.mod_lb_hrs;
+ // load[itemDate].jobsIn.push(item);
+ // } else {
+ // load[itemDate] = {
+ // jobsIn: [item],
+ // jobsOut: [],
+ // hoursIn:
+ // item.labhrs.aggregate.sum.mod_lb_hrs +
+ // item.larhrs.aggregate.sum.mod_lb_hrs,
+ // };
+ // }
+ // });
+
+ arrJobs.forEach((item) => {
+ let isSameBucket = false;
+ if (JobBucket.id === CheckJobBucket(ssbuckets, item)) {
+ filteredArrJobs.push(item);
+ isSameBucket = true;
+ }
+
+ let jobHours =
+ item.labhrs.aggregate.sum.mod_lb_hrs +
+ item.larhrs.aggregate.sum.mod_lb_hrs;
- filteredArrJobs.forEach((item) => {
const itemDate = moment(item.scheduled_in)
.tz(timezone)
.format("yyyy-MM-DD");
- if (!!load[itemDate]) {
- load[itemDate].hoursIn =
- (load[itemDate].hoursIn || 0) +
- item.labhrs.aggregate.sum.mod_lb_hrs +
- item.larhrs.aggregate.sum.mod_lb_hrs;
- load[itemDate].jobsIn.push(item);
- } else {
+ if (isSameBucket) {
+ if (!!load[itemDate]) {
+ load[itemDate].hoursIn = (load[itemDate].hoursIn || 0) + jobHours;
+ load[itemDate].jobsIn.push(item);
+ } else {
+ load[itemDate] = {
+ jobsIn: [item],
+ jobsOut: [],
+ hoursIn: jobHours,
+ };
+ }
+ }
+ if (!load[itemDate]) {
load[itemDate] = {
- jobsIn: [item],
+ jobsIn: [],
jobsOut: [],
- hoursIn:
- item.labhrs.aggregate.sum.mod_lb_hrs +
- item.larhrs.aggregate.sum.mod_lb_hrs,
+ hoursIn: 0,
+ hoursInTotal: 0,
};
}
+ load[itemDate].hoursInTotal =
+ (load[itemDate].hoursInTotal || 0) + jobHours;
});
//Get the completing jobs.
@@ -214,11 +255,25 @@ exports.job = async (req, res) => {
(workingdays[dayOfWeekMapper(moment(loadKey).day())] || false) &&
!load[loadKey].blocked;
+ let isUnderDailyTotalLimit = true;
+
+ if (
+ ss_configuration &&
+ ss_configuration.dailyhrslimit &&
+ ss_configuration.dailyhrslimit > 0 &&
+ load[loadKey] &&
+ load[loadKey].hoursInTotal &&
+ load[loadKey].hoursInTotal > ss_configuration.dailyhrslimit
+ ) {
+ isUnderDailyTotalLimit = false;
+ }
+
if (
load[loadKey].expectedLoad &&
load[loadKey].expectedLoad[JobBucket.id] &&
JobBucket.target > load[loadKey].expectedLoad[JobBucket.id].count &&
- isShopOpen
+ isShopOpen &&
+ isUnderDailyTotalLimit
)
possibleDates.push(new Date(loadKey).toISOString().substr(0, 10));
});