Compare commits

...

59 Commits

Author SHA1 Message Date
Patrick Fic
09f909142b Resolve sped print error. 2022-03-25 07:21:54 -06:00
Patrick Fic
fb380a5b31 IO-1799 Mark md_from_email 2022-03-24 15:40:25 -06:00
Patrick Fic
3896a0b03d IO-1799 Change from address for emails. 2022-03-24 14:54:36 -06:00
Patrick Fic
832674662d Autohouse add PVRT to salestax per request. 2022-03-24 11:55:48 -06:00
Patrick Fic
f2b2011900 Ignore custom margins on certain templates. 2022-03-22 13:41:06 -06:00
Patrick Fic
15c305317a Update header and footer margins. 2022-03-22 11:11:47 -06:00
Patrick Fic
d6924c2292 Add JSR margins to speed print. 2022-03-21 11:52:44 -06:00
Patrick Fic
7ccd356f0a Add margin to templates generation. 2022-03-21 10:51:44 -07:00
Patrick Fic
592b47b5d4 IO-1789 Incoming hours cap 2022-03-21 11:47:25 -06:00
Patrick Fic
d9beee7f2f Merged in release/2022-03-18 (pull request #427)
IO-1777 add check for paint materials threshold.
2022-03-18 22:19:43 +00:00
Patrick Fic
75743f44e7 IO-1777 add check for paint materials threshold. 2022-03-18 15:18:48 -07:00
Patrick Fic
3d68a7099b Merged in release/2022-03-18 (pull request #426)
Release/2022 03 18
2022-03-18 21:46:33 +00:00
Patrick Fic
71f161ec27 IO-1774 Remove scheduled completion date on void. 2022-03-17 15:47:40 -07:00
Patrick Fic
9b1f24926f Merged in hotfix/2022-03-16 (pull request #424)
Autohouse updates.
2022-03-16 23:42:16 +00:00
Patrick Fic
bcc153caa5 Autohouse updates. 2022-03-16 16:39:42 -07:00
Patrick Fic
f59911d5ab Autohouse Adjustment 2022-03-16 16:38:41 -07:00
Patrick Fic
417958e1e8 Additional Autohouse Updates. 2022-03-16 15:50:03 -07:00
Patrick Fic
ccf2f0ad47 IO-1780 Print off 1 job note. 2022-03-16 14:01:05 -07:00
Patrick Fic
bb993ab1fb IO-1783 Add production filtered by status. 2022-03-16 14:00:04 -07:00
Patrick Fic
9c39c8d59b Add header and footer margins. 2022-03-16 13:53:30 -07:00
Patrick Fic
3439f09d9a IO-1693 Allow 0 line supplements. 2022-03-16 11:47:32 -07:00
Patrick Fic
279e93f0c3 IO-1774 Cancel appointments when voiding. 2022-03-14 13:46:18 -07:00
Patrick Fic
92f14d6fa5 IO-1750 Add towin and rent resp date fields. 2022-03-14 13:25:13 -07:00
Patrick Fic
148c645f18 IO-177 Paint/Mat Thresholds 2022-03-14 10:41:52 -07:00
Patrick Fic
9a65b6a1ce Merged in release/2022-03-11 (pull request #420)
Release/2022 03 11
2022-03-11 18:47:44 +00:00
Patrick Fic
839c82abb9 IO-1776 Mechanical authorization added to print center. 2022-03-11 08:11:44 -08:00
Patrick Fic
471756d7ad IO-1769 Resolve error on part selection. 2022-03-10 11:00:13 -08:00
Patrick Fic
2e2a4920ca IO-1772 Add label for no vehicle info. 2022-03-10 10:55:44 -08:00
Patrick Fic
f775e09391 Merged in hotfix/2022-03-09 (pull request #417)
Remove autohouse log file creation.
2022-03-09 17:23:57 +00:00
Patrick Fic
c0b0bcd55e Merged in hotfix/2022-03-09 (pull request #416)
Remove autohouse log file creation.
2022-03-09 16:42:11 +00:00
Patrick Fic
1bc5493f3f Remove autohouse log file creation. 2022-03-09 08:41:36 -08:00
Patrick Fic
2579558090 IO-1606 IO-1607 IO-1752 Move towing rates. 2022-03-08 14:47:01 -08:00
Patrick Fic
bc9a3a21a8 Merged in release/2022-03-11 (pull request #415)
Remove special characters from Autohouse export.

Approved-by: Patrick Fic
2022-03-07 22:48:22 +00:00
Patrick Fic
f11eb6406d Remove special characters from Autohouse export. 2022-03-07 14:44:48 -08:00
Patrick Fic
3d6bad9e7d Merged in release/2022-03-04 (pull request #413)
release/2022-03-04

Approved-by: Patrick Fic
2022-03-04 18:15:38 +00:00
Patrick Fic
12a5f17351 Add create user. 2022-03-04 10:11:18 -08:00
Patrick Fic
a2032553d9 IO-1746 Smart Scheduling Dates to 10. 2022-03-03 12:39:18 -08:00
Patrick Fic
d22979dadc IO-1748 Remove show phone on vendor search select in report cente.r 2022-03-03 08:52:25 -08:00
Patrick Fic
c1068ec92b IO-1728 Set cc create form to be vertical. 2022-03-03 08:37:21 -08:00
Patrick Fic
a318f3e74b Merged in hotfix/2022-03-01 (pull request #410)
IO-1759 Replace offset with timezone.
2022-03-03 01:25:12 +00:00
Patrick Fic
e5a5cb4e85 IO-1759 Replace offset with timezone.
(cherry picked from commit 548d9255638839841e7ad7cbb24888c59dd398d3)
2022-03-02 17:23:10 -08:00
Patrick Fic
f8151e387e Resolve missing fields from time tickets query. 2022-03-02 16:04:49 -08:00
Patrick Fic
b98bfe566a IO-1760 2022-03-02 15:26:18 -08:00
Patrick Fic
c0220f0ca2 IO-1756 Include documents when sending job notes. 2022-03-02 14:36:19 -08:00
Patrick Fic
3e121a1a25 IO-1759 Replace offset with timezone. 2022-03-02 12:56:29 -08:00
Patrick Fic
a2a8868223 IO-1748 Add vendor # to parts order modal. 2022-03-02 11:18:10 -08:00
Patrick Fic
80d16b4651 IO-1747 Add CSR to Visual Board. 2022-03-02 10:54:10 -08:00
Patrick Fic
ce3fbab1dc Merged in hotfix/2022-03-01 (pull request #407)
Autohouse Extract Updates & Remove duedate on payables.

Approved-by: Patrick Fic
2022-03-02 02:24:43 +00:00
Patrick Fic
4c1a333514 Autohouse Extract Updates & Remove duedate on payables. 2022-03-01 18:23:52 -08:00
Patrick Fic
0660b79c01 Autohouse Extract Updates & Remove duedate on payables. 2022-03-01 17:27:04 -08:00
Patrick Fic
63ec578b6a IO-1749 Inactive employees removed from drop downs. 2022-03-01 15:17:33 -08:00
Patrick Fic
dcf388ff7c IO-1745 Resolve scheduling showing incorrect days. 2022-03-01 14:53:42 -08:00
Patrick Fic
0cd1b41ed9 IO-1728 Remove unsaved changes popup when creating CC 2022-03-01 13:24:07 -08:00
Patrick Fic
79124daa9a IO-1719 Add invoice number to job line 2022-03-01 13:14:03 -08:00
Patrick Fic
76ec55d709 Merged in release/2022-02-18 (pull request #405)
Updated task scheduler.

Approved-by: Patrick Fic
2022-02-18 20:59:57 +00:00
Patrick Fic
51c5d163a5 Merged in hotfix/2022-02-14 (pull request #395)
Job costing additional fix.
2022-02-15 20:17:19 +00:00
Patrick Fic
a6f2cfba0f Merged in hotfix/2022-02-14 (pull request #393)
Revert "Potential resolution to date time issues post 4pm."
2022-02-15 17:13:36 +00:00
Patrick Fic
da51aeb135 Merged in hotfix/2022-02-14 (pull request #392)
Potential resolution to date time issues post 4pm.
2022-02-15 16:27:59 +00:00
Patrick Fic
1ba904d082 Merged in hotfix/2022-02-14 (pull request #389)
Resolve bill update error.
2022-02-14 21:13:48 +00:00
58 changed files with 881 additions and 143 deletions

View File

@@ -1,4 +1,4 @@
<babeledit_project version="1.2" be_version="2.7.1">
<babeledit_project be_version="2.7.1" version="1.2">
<!--
BabelEdit project file
@@ -4388,6 +4388,48 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>logo_img_footer_margin</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>logo_img_header_margin</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>logo_img_path</name>
<definition_loaded>false</definition_loaded>
@@ -4556,6 +4598,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>md_from_emails</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>
<folder_node>
<name>md_hour_split</name>
<children>
@@ -7238,6 +7301,32 @@
</concept_node>
</children>
</folder_node>
<folder_node>
<name>ss_configuration</name>
<children>
<concept_node>
<name>dailyhrslimit</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>
</children>
</folder_node>
<folder_node>
<name>ssbuckets</name>
<children>
@@ -13163,6 +13252,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>from</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>subject</name>
<definition_loaded>false</definition_loaded>
@@ -20581,6 +20691,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>date_rentalresp</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>date_scheduled</name>
<definition_loaded>false</definition_loaded>
@@ -20602,6 +20733,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>date_towin</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>ded_amt</name>
<definition_loaded>false</definition_loaded>
@@ -27371,6 +27523,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>threshhold</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>total_cost</name>
<definition_loaded>false</definition_loaded>
@@ -34752,6 +34925,27 @@
</concept_node>
</children>
</folder_node>
<concept_node>
<name>mechanical_authorization</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>mpi_animal_checklist</name>
<definition_loaded>false</definition_loaded>
@@ -38427,6 +38621,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>production_by_repair_status_one</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>production_by_ro</name>
<definition_loaded>false</definition_loaded>
@@ -43353,6 +43568,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>novehinfo</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>relatedjobs</name>
<definition_loaded>false</definition_loaded>

View File

@@ -9,7 +9,7 @@ import { DateFormatter } from "../../utils/DateFormatter";
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
import CourtesyCarStatus from "../courtesy-car-status-select/courtesy-car-status-select.component";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
//import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
@@ -32,7 +32,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
}
/>
<FormFieldsChanged form={form} />
{/* <FormFieldsChanged form={form} /> */}
<LayoutFormRow header={t("courtesycars.labels.vehicle")}>
<Form.Item
label={t("courtesycars.fields.make")}

View File

@@ -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 (
<div>
<Form.Item
label={t("emails.fields.from")}
name="from"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select>
<Select.Option key={currentUser.email}>
{currentUser.email}
</Select.Option>
<Select.Option key={bodyshop.email}>{bodyshop.email}</Select.Option>
{bodyshop.md_from_emails &&
bodyshop.md_from_emails.map((e) => (
<Select.Option key={e}>{e}</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={
<Space>

View File

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

View File

@@ -64,9 +64,9 @@ export default function JobBillsTotalComponent({
})
);
const totalPartsSublet = Dinero(totals.parts.parts.total).add(
Dinero(totals.parts.sublets.total)
);
const totalPartsSublet = Dinero(totals.parts.parts.total)
.add(Dinero(totals.parts.sublets.total))
.add(Dinero(totals.additional.towing));
const discrepancy = totalPartsSublet.subtract(billTotals);

View File

@@ -462,7 +462,7 @@ export function JobLinesComponent({
};
}}
rowSelection={{
selectedRowKeys: selectedLines.map((item) => item.id),
selectedRowKeys: selectedLines.map((item) => item && item.id),
onSelectAll: (selected, selectedRows, changeRows) => {
setSelectedLines(selectedRows);
},

View File

@@ -55,15 +55,17 @@ export function JobEmployeeAssignments({
0
}
>
{bodyshop.employees.map((emp) => (
<Select.Option
value={emp.id}
key={emp.id}
name={`${emp.first_name} ${emp.last_name}`}
>
{`${emp.first_name} ${emp.last_name}`}
</Select.Option>
))}
{bodyshop.employees
.filter((emp) => emp.active)
.map((emp) => (
<Select.Option
value={emp.id}
key={emp.id}
name={`${emp.first_name} ${emp.last_name}`}
>
{`${emp.first_name} ${emp.last_name}`}
</Select.Option>
))}
</Select>
</Col>
<Col span={24}>

View File

@@ -10,7 +10,7 @@ export default function JobLinesBillRefernece({ jobline }) {
{subletRequired && <WarningFilled />}
{`${(billLine.actual_price * billLine.quantity).toFixed(2)} (${
billLine.bill.vendor.name
})`}
} #${billLine.bill.invoice_number})`}
</div>
);
}

View File

@@ -18,7 +18,11 @@ export default function JobReconciliationModalComponent({ job, bills }) {
.flat() || [];
const jobLineData = job.joblines.filter(
(j) => j.part_type !== null && j.part_type !== "PAE"
(j) =>
(j.part_type !== null && j.part_type !== "PAE") ||
(j.line_desc &&
j.line_desc.toLowerCase().includes("towing") &&
j.lbr_op === "OP13")
);
return (

View File

@@ -1,4 +1,4 @@
import { Table } from "antd";
import { Space, Table } from "antd";
import Dinero from "dinero.js";
import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -119,7 +119,18 @@ export default function JobTotalsTableLabor({ job }) {
</Table.Summary.Cell>
</Table.Summary.Row>
<Table.Summary.Row>
<Table.Summary.Cell>{t("jobs.labels.mapa")}</Table.Summary.Cell>
<Table.Summary.Cell>
<Space>
{t("jobs.labels.mapa")}
{job.materials &&
job.materials.mapa &&
job.materials.mapa.cal_maxdlr &&
job.materials.mapa.cal_maxdlr > 0 &&
t("jobs.labels.threshhold", {
amount: job.materials.mapa.cal_maxdlr,
})}
</Space>
</Table.Summary.Cell>
<Table.Summary.Cell align="right">
<CurrencyFormatter>
{job.job_totals.rates.mapa.rate}
@@ -133,7 +144,18 @@ export default function JobTotalsTableLabor({ job }) {
</Table.Summary.Cell>
</Table.Summary.Row>
<Table.Summary.Row>
<Table.Summary.Cell>{t("jobs.labels.mash")}</Table.Summary.Cell>
<Table.Summary.Cell>
<Space wrap>
{t("jobs.labels.mash")}
{job.materials &&
job.materials.mash &&
job.materials.mash.cal_maxdlr &&
job.materials.mash.cal_maxdlr > 0 &&
t("jobs.labels.threshhold", {
amount: job.materials.mash.cal_maxdlr,
})}
</Space>
</Table.Summary.Cell>
<Table.Summary.Cell align="right">
<CurrencyFormatter>
{job.job_totals.rates.mash.rate}

View File

@@ -58,6 +58,15 @@ export default function JobsAdminDatesChange({ job }) {
>
<FormDatePicker format="MM/DD/YYYY" />
</Form.Item>
<Form.Item label={t("jobs.fields.date_towin")} name="date_towin">
<DateTimePicker />
</Form.Item>
<Form.Item
label={t("jobs.fields.date_rentalresp")}
name="date_rentalresp"
>
<DateTimePicker />
</Form.Item>
<Form.Item label={t("jobs.fields.date_open")} name="date_open">
<DateTimePicker />
</Form.Item>

View File

@@ -37,7 +37,6 @@ export const GetSupplementDelta = async (client, jobId, newLines) => {
});
//Wahtever is left in the existing lines, are lines that should be removed.
const insertQueries = linesToInsert.reduce((acc, value, idx) => {
return acc + generateInsertQuery(value, idx, jobId);
}, "");
@@ -49,6 +48,13 @@ export const GetSupplementDelta = async (client, jobId, newLines) => {
const removeQueries = existingLines.reduce((acc, value, idx) => {
return acc + generateRemoveQuery(value, idx);
}, "");
console.log(insertQueries, updateQueries, removeQueries);
if ((insertQueries + updateQueries + removeQueries).trim() === "") {
return new Promise((resolve, reject) => {
resolve(null);
});
}
return new Promise((resolve, reject) => {
resolve(gql`

View File

@@ -220,12 +220,13 @@ export function JobsAvailableContainer({
);
delete supp.joblines;
await client.mutate({
mutation: gql`
${suppDelta}
`,
});
if (suppDelta !== null) {
await client.mutate({
mutation: gql`
${suppDelta}
`,
});
}
const updateResult = await updateJob({
variables: {
jobId: selectedJob,

View File

@@ -39,6 +39,12 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
<Form.Item label={t("jobs.fields.date_open")} name="date_open">
<DateTimePicker disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.date_towin")} name="date_towin">
<DateTimePicker disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.date_rentalresp")} name="date_rentalresp">
<DateTimePicker disabled={jobRO} />
</Form.Item>
</FormRow>
<FormRow header={t("jobs.forms.scheddates")}>

View File

@@ -401,6 +401,9 @@ export function JobsDetailHeaderActions({
job: {
status: bodyshop.md_ro_statuses.default_void,
voided: true,
scheduled_in: null,
scheduled_completion: null,
inproduction: false,
},
note: [
{

View File

@@ -60,6 +60,13 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
);
}, [job.status, bodyshop.md_ro_statuses.post_production_statuses]);
const vehicleTitle = `${job.v_model_yr || ""} ${job.v_color || ""}
${job.v_make_desc || ""}
${job.v_model_desc || ""}`.trim();
console.log(
"🚀 ~ file: jobs-detail-header.component.jsx ~ line 64 ~ vehicleTitle",
vehicleTitle.length
);
return (
<Row gutter={[16, 16]} style={{ alignItems: "stretch" }}>
<Col {...colSpan}>
@@ -188,9 +195,9 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
: job.vehicle && `/manage/vehicles/${job.vehicle.id}`
}
>
{`${job.v_model_yr || ""} ${job.v_color || ""}
${job.v_make_desc || ""}
${job.v_model_desc || ""}`}
{vehicleTitle.length > 0
? vehicleTitle
: t("vehicles.labels.novehinfo")}
</Link>
) : (
<span></span>

View File

@@ -115,7 +115,6 @@ export function JobNotesComponent({
<EditFilled />
</Button>
<PrintWrapperComponent
emailOnly
templateObject={{
name: Templates.individual_job_note.key,
@@ -124,7 +123,7 @@ export function JobNotesComponent({
messageObject={{
subject: Templates.individual_job_note.subject,
}}
id={record.id}
id={jobId}
/>
</Space>
),

View File

@@ -73,6 +73,7 @@ export function PartsOrderModalComponent({
options={vendorList}
disabled={isReturn}
preferredMake={preferredMake}
showPhone
/>
</Form.Item>
<Form.Item

View File

@@ -196,6 +196,11 @@ export function PartsOrderModalContainer({
(item) => item.id === values.vendorid
)[0];
let vendorEmails =
matchingVendor &&
matchingVendor.email &&
matchingVendor.email.split(RegExp("[;,]"));
GenerateDocument(
{
name: isReturn
@@ -206,7 +211,7 @@ export function PartsOrderModalContainer({
},
},
{
to: matchingVendor ? [matchingVendor.email] : null,
to: matchingVendor ? vendorEmails : null,
replyTo: bodyshop.email,
subject: isReturn
? Templates.parts_return_slip.subject

View File

@@ -22,7 +22,7 @@ export default function ProductionBoardCard(
) {
const { t } = useTranslation();
let employee_body, employee_prep, employee_refinish; //employee_csr;
let employee_body, employee_prep, employee_refinish, employee_csr;
if (card.employee_body) {
employee_body = bodyshop.employees.find((e) => e.id === card.employee_body);
}
@@ -34,6 +34,9 @@ export default function ProductionBoardCard(
(e) => e.id === card.employee_refinish
);
}
if (card.employee_csr) {
employee_csr = bodyshop.employees.find((e) => e.id === card.employee_csr);
}
// if (card.employee_csr) {
// employee_csr = bodyshop.employees.find((e) => e.id === card.employee_csr);
// }
@@ -131,11 +134,11 @@ export default function ProductionBoardCard(
)} ${employee_refinish.last_name.charAt(0)}`
: ""
} ${card.larhrs.aggregate.sum.mod_lb_hrs || "?"}h`}</Col>
{/* <Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`C: ${
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`C: ${
employee_csr
? `${employee_csr.first_name} ${employee_csr.last_name}`
: ""
}`}</Col> */}
}`}</Col>
</Row>
</Col>
)}

View File

@@ -66,6 +66,7 @@ export const createBoardData = (AllStatuses, Jobs, filter) => {
include ||
j.employee_body === employeeId ||
j.employee_prep === employeeId ||
j.employee_csr === employeeId ||
j.employee_refinish === employeeId;
}
@@ -76,9 +77,8 @@ export const createBoardData = (AllStatuses, Jobs, filter) => {
Object.keys(DataGroupedByStatus).map((statusGroupKey) => {
try {
boardLanes.columns.find(
(l) => l.id === statusGroupKey
).cards = sortByParentId(DataGroupedByStatus[statusGroupKey]);
boardLanes.columns.find((l) => l.id === statusGroupKey).cards =
sortByParentId(DataGroupedByStatus[statusGroupKey]);
} catch (error) {
console.log("Error while creating board card", error);
}

View File

@@ -116,15 +116,17 @@ export function ProductionListEmpAssignment({
0
}
>
{bodyshop.employees.map((emp) => (
<Select.Option
value={emp.id}
key={emp.id}
name={`${emp.first_name} ${emp.last_name}`}
>
{`${emp.first_name} ${emp.last_name}`}
</Select.Option>
))}
{bodyshop.employees
.filter((emp) => emp.active)
.map((emp) => (
<Select.Option
value={emp.id}
key={emp.id}
name={`${emp.first_name} ${emp.last_name}`}
>
{`${emp.first_name} ${emp.last_name}`}
</Select.Option>
))}
</Select>
</Col>
<Col span={24}>

View File

@@ -8,8 +8,11 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const ProdTemplates = TemplateList("production");
const { production_by_technician_one, production_by_category_one } =
TemplateList("special");
const {
production_by_technician_one,
production_by_category_one,
production_by_repair_status_one,
} = TemplateList("special");
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -95,6 +98,29 @@ export function ProductionListPrint({ bodyshop }) {
</Menu.Item>
))}
</Menu.SubMenu>
<Menu.SubMenu
title={t("reportcenter.templates.production_by_repair_status_one")}
>
{bodyshop.md_ro_statuses.production_statuses.map((e) => (
<Menu.Item
key={e}
onClick={async () => {
setLoading(true);
await GenerateDocument(
{
name: production_by_repair_status_one.key,
variables: { status: e },
},
{},
"p"
);
setLoading(false);
}}
>
{e}
</Menu.Item>
))}
</Menu.SubMenu>
</Menu>
}
>

View File

@@ -142,6 +142,18 @@ export default function ShopInfoGeneral({ form }) {
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.logo_img_header_margin")}
name={["logo_img_path", "headerMargin"]}
>
<InputNumber min={0} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.logo_img_footer_margin")}
name={["logo_img_path", "footerMargin"]}
>
<InputNumber min={0} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("bodyshop.labels.accountingsetup")}>
<Form.Item
@@ -495,6 +507,18 @@ export default function ShopInfoGeneral({ form }) {
>
<Switch />
</Form.Item>
<Form.Item
name={["md_from_emails"]}
label={t("bodyshop.fields.md_from_emails")}
// rules={[
// {
// //message: t("general.validation.required"),
// type: "array",
// },
// ]}
>
<Select mode="tags" />
</Form.Item>
<Form.Item
name={["md_email_cc", "parts_order"]}
label={t("bodyshop.fields.md_email_cc", { template: "parts_order" })}

View File

@@ -70,6 +70,12 @@ export default function ShopInfoSchedulingComponent({ form }) {
>
<Select mode="tags" />
</Form.Item>
<Form.Item
name={["ss_configuration", "dailyhrslimit"]}
label={t("bodyshop.fields.ss_configuration.dailyhrslimit")}
>
<InputNumber min={0} />
</Form.Item>
</LayoutFormRow>
<Divider orientation="left">{t("bodyshop.labels.workingdays")}</Divider>
<Space wrap size="large">

View File

@@ -1,12 +1,13 @@
import { HeartOutlined } from "@ant-design/icons";
import { Select, Tag } from "antd";
import { Select, Space, Tag } from "antd";
import React, { forwardRef, useEffect, useState } from "react";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
const { Option } = Select;
//To be used as a form element only.
const VendorSearchSelect = (
{ value, onChange, options, onSelect, disabled, preferredMake },
{ value, onChange, options, onSelect, disabled, preferredMake, showPhone },
ref
) => {
const [option, setOption] = useState(value);
@@ -35,6 +36,7 @@ const VendorSearchSelect = (
style={{
width: "100%",
}}
dropdownMatchSelectWidth={false}
onChange={setOption}
optionFilterProp="name"
onSelect={onSelect}
@@ -50,10 +52,15 @@ const VendorSearchSelect = (
>
<div className="imex-flex-row">
<div style={{ flex: 1 }}>{o.name}</div>
<HeartOutlined />
{o.discount && o.discount !== 0 ? (
<Tag color="green">{`${o.discount * 100}%`}</Tag>
) : null}
<Space style={{ marginLeft: "1rem" }}>
<HeartOutlined style={{ color: "red" }} />
{o.phone && showPhone && (
<PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>
)}
{o.discount && o.discount !== 0 ? (
<Tag color="green">{`${o.discount * 100}%`}</Tag>
) : null}
</Space>
</div>
</Option>
))
@@ -64,9 +71,14 @@ const VendorSearchSelect = (
<div className="imex-flex-row" style={{ width: "100%" }}>
<div style={{ flex: 1 }}>{o.name}</div>
{o.discount && o.discount !== 0 ? (
<Tag color="green">{`${o.discount * 100}%`}</Tag>
) : null}
<Space style={{ marginLeft: "1rem" }}>
{o.phone && showPhone && (
<PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>
)}
{o.discount && o.discount !== 0 ? (
<Tag color="green">{`${o.discount * 100}%`}</Tag>
) : null}
</Space>
</div>
</Option>
))

View File

@@ -109,12 +109,14 @@ export default function VendorsFormComponent({
<Form.Item
label={t("vendors.fields.email")}
rules={[
{
type: "email",
message: t("general.validation.invalidemail"),
},
]}
rules={
[
// {
// type: "email",
// message: t("general.validation.invalidemail"),
// },
]
}
name="email"
>
<FormItemEmail email={getFieldValue("email")} />

View File

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

View File

@@ -59,6 +59,8 @@ export const GET_LINE_TICKET_BY_PK = gql`
employeeid
memo
flat_rate
clockon
clockoff
employee {
id
first_name

View File

@@ -595,6 +595,7 @@ export const GET_JOB_BY_PK = gql`
ca_gst_registrant
ownerid
ded_note
materials
owner {
id
ownr_fn
@@ -649,6 +650,8 @@ export const GET_JOB_BY_PK = gql`
date_invoiced
date_last_contacted
date_next_contact
date_towin
date_rentalresp
date_exported
status
owner_owing
@@ -696,6 +699,7 @@ export const GET_JOB_BY_PK = gql`
joblineid
bill {
id
invoice_number
vendor {
id
name
@@ -1096,6 +1100,15 @@ export const VOID_JOB = gql`
insert_notes(objects: $note) {
affected_rows
}
update_appointments(
where: { jobid: { _eq: $jobId } }
_set: { canceled: true }
) {
returning {
id
canceled
}
}
}
`;

View File

@@ -87,6 +87,7 @@ export const QUERY_ALL_VENDORS_FOR_ORDER = gql`
discount
email
active
phone
}
jobs(where: { id: { _eq: $jobId } }) {
v_make_desc

View File

@@ -70,7 +70,12 @@ export function CourtesyCarCreateContainer({
return (
<RbacWrapper action="courtesycar:create">
<Form form={form} autoComplete="new-password" onFinish={handleFinish}>
<Form
form={form}
autoComplete="new-password"
onFinish={handleFinish}
layout="vertical"
>
<CourtesyCarFormComponent form={form} saveLoading={loading} />
</Form>
</RbacWrapper>

View File

@@ -275,6 +275,8 @@
"mash": "Job Costing - Shop Materials Hourly Cost Rate"
},
"lastnumberworkingdays": "Scoreboard - Last Number of Working Days",
"logo_img_footer_margin": "Footer Margin (px)",
"logo_img_header_margin": "Header Margin (px)",
"logo_img_path": "Shop Logo",
"logo_img_path_height": "Logo Image Height",
"logo_img_path_width": "Logo Image Width",
@@ -283,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"
@@ -453,6 +456,9 @@
"label": "Label",
"templates": "Templates"
},
"ss_configuration": {
"dailyhrslimit": "Daily Incoming Hours Limit"
},
"ssbuckets": {
"gte": "Greater Than/Equal to (hrs)",
"id": "ID",
@@ -825,6 +831,7 @@
},
"fields": {
"cc": "CC",
"from": "From",
"subject": "Subject",
"to": "To"
},
@@ -1254,7 +1261,9 @@
"date_last_contacted": "Last Contacted Date",
"date_next_contact": "Next Contact Date",
"date_open": "Open",
"date_rentalresp": "Shop Rental Responsibility Start",
"date_scheduled": "Scheduled",
"date_towin": "Towed In",
"ded_amt": "Deductible",
"ded_note": "Deductible Note",
"ded_status": "Deductible Status",
@@ -1602,6 +1611,7 @@
"supplementnote": "The job had a supplement imported.",
"suspended": "SUSPENDED",
"suspense": "Suspense",
"threshhold": "Max Threshold: ${{amount}}",
"total_cost": "Total Cost",
"total_cust_payable": "Total Customer Amount Payable",
"total_repairs": "Total Repairs",
@@ -2068,6 +2078,7 @@
"labels": "Labels",
"position": "Starting Position"
},
"mechanical_authorization": "Mechanical Authorization",
"mpi_animal_checklist": "MPI - Animal Checklist",
"mpi_eglass_auth": "MPI - eGlass Auth",
"mpi_final_acct_sheet": "MPI - Final Accounting Sheet",
@@ -2280,6 +2291,7 @@
"production_by_csr": "Production by CSR",
"production_by_last_name": "Production by Last Name",
"production_by_repair_status": "Production by Status",
"production_by_repair_status_one": "Production filtered by Status",
"production_by_ro": "Production by RO",
"production_by_target_date": "Production by Target Date",
"production_by_technician": "Production by Technician",
@@ -2578,6 +2590,7 @@
},
"labels": {
"fromvehicle": "Historical Vehicle Record",
"novehinfo": "No Vehicle Information",
"relatedjobs": "Related Jobs",
"updatevehicle": "Update Vehicle Information"
},

View File

@@ -275,6 +275,8 @@
"mash": ""
},
"lastnumberworkingdays": "",
"logo_img_footer_margin": "",
"logo_img_header_margin": "",
"logo_img_path": "",
"logo_img_path_height": "",
"logo_img_path_width": "",
@@ -283,6 +285,7 @@
"md_classes": "",
"md_ded_notes": "",
"md_email_cc": "",
"md_from_emails": "",
"md_hour_split": {
"paint": "",
"prep": ""
@@ -453,6 +456,9 @@
"label": "",
"templates": ""
},
"ss_configuration": {
"dailyhrslimit": ""
},
"ssbuckets": {
"gte": "",
"id": "",
@@ -825,6 +831,7 @@
},
"fields": {
"cc": "",
"from": "",
"subject": "",
"to": ""
},
@@ -1254,7 +1261,9 @@
"date_last_contacted": "",
"date_next_contact": "",
"date_open": "Abierto",
"date_rentalresp": "",
"date_scheduled": "Programado",
"date_towin": "",
"ded_amt": "Deducible",
"ded_note": "",
"ded_status": "Estado deducible",
@@ -1602,6 +1611,7 @@
"supplementnote": "",
"suspended": "",
"suspense": "",
"threshhold": "",
"total_cost": "",
"total_cust_payable": "",
"total_repairs": "",
@@ -2068,6 +2078,7 @@
"labels": "",
"position": ""
},
"mechanical_authorization": "",
"mpi_animal_checklist": "",
"mpi_eglass_auth": "",
"mpi_final_acct_sheet": "",
@@ -2280,6 +2291,7 @@
"production_by_csr": "",
"production_by_last_name": "",
"production_by_repair_status": "",
"production_by_repair_status_one": "",
"production_by_ro": "",
"production_by_target_date": "",
"production_by_technician": "",
@@ -2578,6 +2590,7 @@
},
"labels": {
"fromvehicle": "",
"novehinfo": "",
"relatedjobs": "",
"updatevehicle": ""
},

View File

@@ -275,6 +275,8 @@
"mash": ""
},
"lastnumberworkingdays": "",
"logo_img_footer_margin": "",
"logo_img_header_margin": "",
"logo_img_path": "",
"logo_img_path_height": "",
"logo_img_path_width": "",
@@ -283,6 +285,7 @@
"md_classes": "",
"md_ded_notes": "",
"md_email_cc": "",
"md_from_emails": "",
"md_hour_split": {
"paint": "",
"prep": ""
@@ -453,6 +456,9 @@
"label": "",
"templates": ""
},
"ss_configuration": {
"dailyhrslimit": ""
},
"ssbuckets": {
"gte": "",
"id": "",
@@ -825,6 +831,7 @@
},
"fields": {
"cc": "",
"from": "",
"subject": "",
"to": ""
},
@@ -1254,7 +1261,9 @@
"date_last_contacted": "",
"date_next_contact": "",
"date_open": "Ouvrir",
"date_rentalresp": "",
"date_scheduled": "Prévu",
"date_towin": "",
"ded_amt": "Déductible",
"ded_note": "",
"ded_status": "Statut de franchise",
@@ -1602,6 +1611,7 @@
"supplementnote": "",
"suspended": "",
"suspense": "",
"threshhold": "",
"total_cost": "",
"total_cust_payable": "",
"total_repairs": "",
@@ -2068,6 +2078,7 @@
"labels": "",
"position": ""
},
"mechanical_authorization": "",
"mpi_animal_checklist": "",
"mpi_eglass_auth": "",
"mpi_final_acct_sheet": "",
@@ -2280,6 +2291,7 @@
"production_by_csr": "",
"production_by_last_name": "",
"production_by_repair_status": "",
"production_by_repair_status_one": "",
"production_by_ro": "",
"production_by_target_date": "",
"production_by_technician": "",
@@ -2578,6 +2590,7 @@
},
"labels": {
"fromvehicle": "",
"novehinfo": "",
"relatedjobs": "",
"updatevehicle": ""
},

View File

@@ -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: {
@@ -39,7 +60,7 @@ export default async function RenderTemplate(
...templateObject.context,
headerpath: `/${bodyshop.imexshopid}/header.html`,
bodyshop: bodyshop,
offset: moment().utcOffset(),
offset: bodyshop.timezone, //moment().utcOffset(),
},
};
@@ -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: {

View File

@@ -37,6 +37,14 @@ export const TemplateList = (type, context) => {
disabled: false,
group: "authorization",
},
mechanical_authorization: {
title: i18n.t("printcenter.jobs.mechanical_authorization"),
description: "Diagnostic Authorization",
subject: i18n.t("printcenter.jobs.mechanical_authorization"),
key: "mechanical_authorization",
disabled: false,
group: "authorization",
},
appointment_reminder: {
title: i18n.t("printcenter.jobs.appointment_reminder"),
description: "All Jobs Notes",
@@ -363,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"),
@@ -371,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"),
@@ -1664,6 +1674,18 @@ export const TemplateList = (type, context) => {
//idtype: "vendor",
disabled: false,
},
production_by_repair_status_one: {
title: i18n.t(
"reportcenter.templates.production_by_repair_status_one"
),
description: "",
subject: i18n.t(
"reportcenter.templates.production_by_repair_status_one"
),
key: "production_by_repair_status_one",
//idtype: "vendor",
disabled: false,
},
}
: {}),
};

View File

@@ -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
@@ -2721,7 +2725,9 @@
- date_last_contacted
- date_next_contact
- date_open
- date_rentalresp
- date_scheduled
- date_towin
- ded_amt
- ded_note
- ded_status
@@ -2804,6 +2810,7 @@
- loss_desc
- loss_of_use
- loss_type
- materials
- other_amount_payable
- owner_owing
- ownerid
@@ -2978,7 +2985,9 @@
- date_last_contacted
- date_next_contact
- date_open
- date_rentalresp
- date_scheduled
- date_towin
- ded_amt
- ded_note
- ded_status
@@ -3061,6 +3070,7 @@
- loss_desc
- loss_of_use
- loss_type
- materials
- other_amount_payable
- owner_owing
- ownerid
@@ -3245,7 +3255,9 @@
- date_last_contacted
- date_next_contact
- date_open
- date_rentalresp
- date_scheduled
- date_towin
- ded_amt
- ded_note
- ded_status
@@ -3328,6 +3340,7 @@
- loss_desc
- loss_of_use
- loss_type
- materials
- other_amount_payable
- owner_owing
- ownerid

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."jobs" add column "materials" jsonb
-- not null default jsonb_build_object();

View File

@@ -0,0 +1,2 @@
alter table "public"."jobs" add column "materials" jsonb
not null default jsonb_build_object();

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."jobs" add column "towin_date" timestamptz
-- null;

View File

@@ -0,0 +1,2 @@
alter table "public"."jobs" add column "towin_date" timestamptz
null;

View File

@@ -0,0 +1 @@
alter table "public"."jobs" rename column "date_towin" to "towin_date";

View File

@@ -0,0 +1 @@
alter table "public"."jobs" rename column "towin_date" to "date_towin";

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."jobs" add column "date_rentalresp" timestamptz
-- null;

View File

@@ -0,0 +1,2 @@
alter table "public"."jobs" add column "date_rentalresp" timestamptz
null;

View File

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

View File

@@ -0,0 +1,2 @@
alter table "public"."bodyshops" add column "ss_configuration" jsonb
null default jsonb_build_object();

View File

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

View File

@@ -0,0 +1,2 @@
alter table "public"."bodyshops" add column "md_from_emails" jsonb
null default jsonb_build_array();

View File

@@ -149,6 +149,7 @@ app.post(
fb.unsubscribe
);
app.post("/adm/updateuser", fb.validateFirebaseIdToken, fb.updateUser);
app.post("/adm/createuser", fb.validateFirebaseIdToken, fb.createUser);
//Stripe Processing
var stripe = require("./server/stripe/payment");
@@ -180,7 +181,6 @@ app.post("/data/arms", data.arms);
var taskHandler = require("./server/tasks/tasks");
app.post("/taskHandler", taskHandler.taskHandler);
var ioevent = require("./server/ioevent/ioevent");
app.post("/ioevent", ioevent.default);
app.post("/newlog", (req, res) => {
@@ -188,7 +188,6 @@ app.post("/newlog", (req, res) => {
logger.log(message, type, user, record, object);
});
var cdkGetMake = require("./server/cdk/cdk-get-makes");
app.post("/cdk/getvehicles", fb.validateFirebaseIdToken, cdkGetMake.default);

View File

@@ -181,12 +181,13 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor) {
TxnDate: moment(bill.date)
//.tz(bill.job.bodyshop.timezone)
.format("YYYY-MM-DD"),
...(bill.vendor.due_date && {
DueDate: moment(bill.date)
//.tz(bill.job.bodyshop.timezone)
.add(bill.vendor.due_date, "days")
.format("YYYY-MM-DD"),
}),
...(!bill.is_credit_memo &&
bill.vendor.due_date && {
DueDate: moment(bill.date)
//.tz(bill.job.bodyshop.timezone)
.add(bill.vendor.due_date, "days")
.format("YYYY-MM-DD"),
}),
DocNumber: bill.invoice_number,
//...(bill.job.class ? { ClassRef: { Id: classes[bill.job.class] } } : {}),

View File

@@ -75,12 +75,13 @@ const generateBill = (bill) => {
TxnDate: moment(bill.date)
//.tz(bill.job.bodyshop.timezone)
.format("YYYY-MM-DD"),
...(bill.vendor.due_date && {
DueDate: moment(bill.date)
// .tz(bill.job.bodyshop.timezone)
.add(bill.vendor.due_date, "days")
.format("YYYY-MM-DD"),
}),
...(!bill.is_credit_memo &&
bill.vendor.due_date && {
DueDate: moment(bill.date)
// .tz(bill.job.bodyshop.timezone)
.add(bill.vendor.due_date, "days")
.format("YYYY-MM-DD"),
}),
RefNumber: bill.invoice_number,
Memo: `RO ${bill.job.ro_number || ""}`,
ExpenseLineAdd: bill.billlines.map((il) =>

View File

@@ -96,7 +96,7 @@ exports.default = async (req, res) => {
count: autoHouseObject.AutoHouseExport.RepairOrder.length,
xml: ret,
filename: `IM_${bodyshop.autohouseid}_${moment().format(
"DDMMYYYY_HHMMSS"
"DDMMYYYY_HHMMss"
)}.xml`,
});
@@ -214,7 +214,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
Street: job.ownr_addr1 || "",
City: job.ownr_city || "",
State: job.ownr_st || "",
Zip: job.ownr_zip || "",
Zip: (job.ownr_zip && job.ownr_zip.substring(0, 3)) || "",
Phone1: job.ownr_ph1 || "",
Phone2: null,
Phone2Extension: null,
@@ -328,8 +328,12 @@ const CreateRepairOrderTag = (job, errorCallback) => {
.format(AhDateFormat)) ||
"",
DeliveryAppointmentDate:
(job.scheduled_delivery &&
moment(job.scheduled_delivery)
// (job.scheduled_delivery &&
// moment(job.scheduled_delivery)
// .tz(job.bodyshop.timezone)
// .format(AhDateFormat)) ||
(job.scheduled_completion &&
moment(job.scheduled_completion)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
@@ -488,8 +492,8 @@ const CreateRepairOrderTag = (job, errorCallback) => {
PartsReconditionedCost:
repairCosts.PartsReconditionedCost.toFormat(AHDineroFormat),
PartsRecycled: Dinero(
job.job_totals.parts.parts.list.PAR &&
job.job_totals.parts.parts.list.PAR.total
job.job_totals.parts.parts.list.PAL &&
job.job_totals.parts.parts.list.PAL.total
).toFormat(AHDineroFormat),
PartsRecycledCost:
repairCosts.PartsRecycledCost.toFormat(AHDineroFormat),
@@ -538,7 +542,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
),
GlassLaborTotalCost:
repairCosts.GlassLaborTotalCost.toFormat(AHDineroFormat),
DetailLaborTotal: Dinero(job.job_totals.rates.lag.total).toFormat(
DetailLaborTotal: Dinero(job.job_totals.rates.lad.total).toFormat(
AHDineroFormat
),
DetailLaborTotalCost:
@@ -555,7 +559,9 @@ const CreateRepairOrderTag = (job, errorCallback) => {
AHDineroFormat
),
BMTotalCost: repairCosts.BMTotalCost.toFormat(AHDineroFormat),
MiscTotal: 0,
MiscTotal: Dinero(job.job_totals.additional.additionalCosts).toFormat(
AHDineroFormat
),
MiscTotalCost: 0,
TowingTotal: Dinero(job.job_totals.additional.towing).toFormat(
AHDineroFormat
@@ -570,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(
@@ -588,7 +595,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
AHDineroFormat
),
InsurancePay: Dinero(job.job_totals.totals.total_repairs)
.subtract(Dinero(job.job_totals.totals.custPayable))
.subtract(Dinero(job.job_totals.totals.custPayable.total))
.toFormat(AHDineroFormat),
Deposit: 0,
AmountDue: 0,
@@ -673,7 +680,7 @@ const CreateCosts = (job) => {
});
return bill_acc;
}, {});
const materialsHours = { mapaHrs: 0, mashHrs: 0 };
//If the hourly rates for job costing are set, add them in.
if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mapa) {
if (
@@ -694,9 +701,32 @@ const CreateCosts = (job) => {
(job.bodyshop.jc_hourly_rates &&
job.bodyshop.jc_hourly_rates.mapa * 100) ||
0,
}).multiply(materialsHours.mapaHrs)
}).multiply(job.job_totals.rates.mapa.hours)
);
}
if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mash) {
if (
!billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
]
)
billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
] = Dinero();
billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
] = billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
].add(
Dinero({
amount:
(job.bodyshop.jc_hourly_rates &&
job.bodyshop.jc_hourly_rates.mash * 100) ||
0,
}).multiply(job.job_totals.rates.mash.hours)
);
}
const ticketTotalsByCostCenter = job.timetickets.reduce(
(ticket_acc, ticket_val) => {
//At the invoice level.
@@ -797,7 +827,9 @@ const GenerateDetailLines = (job, line, statuses) => {
)) ||
0,
//Critical: null,
Description: line.line_desc || "",
Description: line.line_desc
? line.line_desc.replace(/[^\x00-\x7F]/g, "")
: "",
DiscountMarkup: line.prt_dsmk_m || 0,
InvoiceNumber: line.billlines[0] && line.billlines[0].bill.invoice_number,
IOUPart: 0,

View File

@@ -25,6 +25,43 @@ const adminEmail = [
"patrick@thinkimex.com",
];
exports.createUser = (req, res) => {
logger.log("admin-create-user", "WARN", req.user.email, null, {
request: req.body,
});
if (!adminEmail.includes(req.user.email)) {
logger.log(
"admin-create-user-unauthorized",
"ERROR",
req.user.email,
null,
{
request: req.body,
user: req.user,
}
);
res.sendStatus(404);
}
const { email, displayName, password } = req.body;
admin
.auth()
.createUser({ email, displayName, password })
.then((userRecord) => {
// See the UserRecord reference doc for the contents of userRecord.
logger.log("admin-update-user-success", "DEBUG", req.user.email, null, {
userRecord,
});
res.json(userRecord);
})
.catch((error) => {
logger.log("admin-update-user-error", "ERROR", req.user.email, null, {
error,
});
res.status(500).json(error);
});
};
exports.updateUser = (req, res) => {
logger.log("admin-update-user", "WARN", req.user.email, null, {
request: req.body,

View File

@@ -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
@@ -960,6 +961,7 @@ exports.GET_JOB_BY_PK = ` query GET_JOB_BY_PK($id: uuid!) {
voided
ca_bc_pvrt
ca_customer_gst
materials
joblines(where: { removed: { _eq: false } }){
id
line_no

View File

@@ -234,11 +234,30 @@ function CalculateRatesTotals(ratesList) {
if (!ret[property].total) {
ret[property].total = Dinero();
}
ret[property].total = ret[property].total.add(
Dinero({
amount: Math.round((ret[property].rate || 0) * 100),
}).multiply(ret[property].hours)
);
let threshold;
//Check if there is a max for this type.
if (ratesList.materials && ratesList.materials[property]) {
//
if (
ratesList.materials[property].cal_maxdlr &&
ratesList.materials[property].cal_maxdlr > 0
) {
//It has an upper threshhold.
threshold = Dinero({
amount: Math.round(ratesList.materials[property].cal_maxdlr * 100),
});
}
}
const total = Dinero({
amount: Math.round((ret[property].rate || 0) * 100),
}).multiply(ret[property].hours);
if (threshold && total.greaterThanOrEqual(threshold)) {
ret[property].total = ret[property].total.add(threshold);
} else {
ret[property].total = ret[property].total.add(total);
}
}
subtotal = subtotal.add(ret[property].total);
@@ -252,6 +271,7 @@ function CalculateRatesTotals(ratesList) {
return ret;
}
function CalculatePartsTotals(jobLines) {
const ret = jobLines
.filter((jl) => !jl.removed)
@@ -418,21 +438,27 @@ function CalculateAdditional(job) {
pvrt: null,
total: null,
};
ret.towing = Dinero({
amount: Math.round((job.towing_payable || 0) * 100),
});
ret.additionalCosts = job.joblines
.filter((jl) => !jl.removed && IsAdditionalCost(jl))
.reduce((acc, val) => {
const lineValue = Dinero({
amount: Math.round((val.act_price || 0) * 100),
}).multiply(val.part_qty || 1);
ret.additionalCostItems.push({ key: val.line_desc, total: lineValue });
return acc.add(lineValue);
if (val.line_desc.toLowerCase().includes("towing")) {
ret.towing = lineValue;
return acc;
} else {
ret.additionalCostItems.push({ key: val.line_desc, total: lineValue });
return acc.add(lineValue);
}
}, Dinero());
ret.adjustments = Dinero({
amount: Math.round((job.adjustment_bottom_line || 0) * 100),
});
ret.towing = Dinero({
amount: Math.round((job.towing_payable || 0) * 100),
});
ret.storage = Dinero({
amount: Math.round((job.storage_payable || 0) * 100),
});

View File

@@ -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.
@@ -111,10 +152,9 @@ exports.job = async (req, res) => {
}
if (
moment(item.actual_completion || item.scheduled_completion).tz(timezone).isBefore(
moment().tz(timezone),
"day"
)
moment(item.actual_completion || item.scheduled_completion)
.tz(timezone)
.isBefore(moment().tz(timezone), "day")
) {
console.log("Job should have already gone. Ignoring it.", item);
return;
@@ -128,7 +168,9 @@ exports.job = async (req, res) => {
} else {
const itemDate = moment(
item.actual_completion || item.scheduled_completion
).tz(timezone).format("yyyy-MM-DD");
)
.tz(timezone)
.format("yyyy-MM-DD");
if (!!load[itemDate]) {
load[itemDate].hoursOut =
(load[itemDate].hoursOut || 0) +
@@ -153,14 +195,20 @@ exports.job = async (req, res) => {
const end = moment.max([
...filteredArrJobs.map((a) => moment(a.scheduled_in).tz(timezone)),
...filteredCompJobs
.map((p) => moment(p.actual_completion || p.scheduled_completion).tz(timezone))
.map((p) =>
moment(p.actual_completion || p.scheduled_completion).tz(timezone)
)
.filter((p) => p.isValid() && p.isAfter(yesterday)),
moment().tz(timezone).add(15, "days"),
]);
const range = Math.round(moment.duration(end.diff(today)).asDays());
for (var day = 0; day < range; day++) {
const current = moment(today).tz(timezone).add(day, "days").format("yyyy-MM-DD");
const prev = moment(today).tz(timezone)
const current = moment(today)
.tz(timezone)
.add(day, "days")
.format("yyyy-MM-DD");
const prev = moment(today)
.tz(timezone)
.add(day - 1, "days")
.format("yyyy-MM-DD");
if (!!!load[current]) {
@@ -194,7 +242,7 @@ exports.job = async (req, res) => {
load[startIsoFormat] = { blocked: true };
}
});
// //Propose the first 5 dates where we are below target.
// //Propose the first 10 dates where we are below target.
const possibleDates = [];
delete load.productionTotal;
@@ -204,22 +252,36 @@ exports.job = async (req, res) => {
loadKeys.forEach((loadKey) => {
const isShopOpen =
(workingdays[dayOfWeekMapper(moment(loadKey).tz(timezone).day())] || false) &&
(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));
});
if (possibleDates.length < 6) {
if (possibleDates.length < 11) {
res.json(possibleDates);
} else {
res.json(possibleDates.slice(0, 5));
res.json(possibleDates.slice(0, 10));
}
} catch (error) {
logger.log("smart-scheduling-error", "ERROR", req.user.email, jobId, {