Compare commits

...

38 Commits

Author SHA1 Message Date
Patrick Fic
098754125b IO-594 Add AH Settings 2021-07-21 13:11:51 -07:00
Patrick Fic
ae4a864533 IO-1264 IO-1271 Report Center additions & format. 2021-07-21 11:19:23 -07:00
Patrick Fic
27d9322ced IO-594 Include AH Requested changes 2021-07-21 08:50:29 -07:00
Patrick Fic
f5003080db Removed unnecessary import. 2021-07-20 16:37:19 -07:00
Patrick Fic
3b992edc21 IO-594 Create schedulable AH export. 2021-07-20 16:25:08 -07:00
Patrick Fic
3e1663bf18 IO-1267 missing query info in detail cards. 2021-07-20 10:36:49 -07:00
Patrick Fic
9a60149d75 IO-1266 attach pdf copy of email. 2021-07-20 10:34:03 -07:00
Patrick Fic
54b483333f Revert "IO-1257 Accept special characters"
This reverts commit a8ad65000d.
2021-07-16 14:42:12 -07:00
Patrick Fic
990ec1a553 IO-1189 IO-1190 IO-1259 Added gen doc keys. 2021-07-16 10:13:50 -07:00
Patrick Fic
6bd49a461e IO-1264 Add CSR reports 2021-07-16 09:25:03 -07:00
Patrick Fic
0d30fc0e32 IO-1219 Improve display of job status. 2021-07-15 13:59:23 -07:00
Patrick Fic
7ce4264309 IO-1260 Remove VIN Unique Key 2021-07-15 11:40:09 -07:00
Patrick Fic
5385e6918b IO-1219 Add status to job search select. 2021-07-14 15:48:27 -07:00
Patrick Fic
a8ad65000d IO-1257 Accept special characters 2021-07-14 14:51:19 -07:00
Patrick Fic
ae9ca0ac3b IO-1258 Update reconciliation on posting bill 2021-07-13 12:30:12 -07:00
Patrick Fic
9fd23e9181 IO-1253 Disable receiving inhouse bills. 2021-07-13 12:14:28 -07:00
Patrick Fic
a288a1a2a4 IO-1250 Mark job as PST Exempt 2021-07-13 11:49:27 -07:00
Patrick Fic
33e2201524 IO-1256 Payments on job missing field & edit from list. 2021-07-13 11:09:15 -07:00
Patrick Fic
34422dfef7 IO-1255 Resolve parts return and posting for non-defined lines 2021-07-13 11:02:54 -07:00
Patrick Fic
c6635845f5 Merged in feature/2021-07-09 (pull request #134)
Removed uneeded imports.

Approved-by: Patrick Fic
2021-07-08 18:46:03 +00:00
Patrick Fic
e770232e1d Removed uneeded imports. 2021-07-08 11:45:43 -07:00
Patrick Fic
51dcf3a7c6 Merged in feature/2021-07-09 (pull request #133)
feature/2021-07-09

Approved-by: Patrick Fic
2021-07-08 18:38:34 +00:00
Patrick Fic
afd745917d IO-1230 remove sort on export logs for ro num 2021-07-08 11:34:39 -07:00
Patrick Fic
aa8e12ef58 IO-1248 IO-1247 Resolve nulls in system & payment search update. 2021-07-08 11:33:02 -07:00
Patrick Fic
41bbda7bcf IO-12424 Delete line to mark as removed. 2021-07-08 11:17:51 -07:00
Patrick Fic
f19289362d IO-1249 Adjust reconciliation window sizing. 2021-07-08 11:12:58 -07:00
Patrick Fic
2d546d92b5 Merged in feature/2021-07-09 (pull request #131)
IO-1245 Change Address 1

Approved-by: Patrick Fic
2021-07-07 23:30:51 +00:00
Patrick Fic
1360a73028 IO-1245 Change Address 1 2021-07-07 16:30:05 -07:00
Patrick Fic
7de224831f Merged in feature/2021-07-09 (pull request #130)
feature/2021-07-09

Approved-by: Patrick Fic
2021-07-07 22:14:01 +00:00
Patrick Fic
e9cda93898 IO-1245 Resolve QB Consistency Issue 2021-07-07 15:04:56 -07:00
Patrick Fic
2c1f5a9f34 IO-1241 Supplement Merge with discarded changes. 2021-07-06 09:45:02 -07:00
Patrick Fic
d88d7ebebd Merged in feature/2021-07-09 (pull request #129)
IO-1239 Resolve extra lines on glass claim export
2021-07-05 17:37:52 +00:00
Patrick Fic
17dcc2efd8 IO-1239 Resolve extra lines on glass claim export 2021-07-05 10:35:45 -07:00
Patrick Fic
3391d7d3f4 Merged in hotfix/2021-07-02 (pull request #127)
IO-1231 Resolve dates not saving on job close.

Approved-by: Patrick Fic
2021-07-02 20:13:40 +00:00
Patrick Fic
bccb5e353b IO-1231 Resolve dates not saving on job close. 2021-07-02 13:13:19 -07:00
Patrick Fic
8e05105917 Merged in hotfix/2021-06-30 (pull request #125)
Hotfix/2021 06 30
2021-06-30 20:46:41 +00:00
Patrick Fic
81babca775 Landing page update. 2021-06-30 13:44:46 -07:00
Patrick Fic
fe8dd2a920 Landing page updates. 2021-06-30 13:44:26 -07:00
60 changed files with 1584 additions and 280 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>
@@ -16516,6 +16558,48 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>markpstexempt</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>markpstexemptconfirm</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>postbills</name>
<definition_loaded>false</definition_loaded>
@@ -29914,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>
@@ -30420,6 +30525,48 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>key_tag</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>paint_grid</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>parts_label_single</name>
<definition_loaded>false</definition_loaded>
@@ -30714,6 +30861,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>stolen_recovery_checklist</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>supplement_request</name>
<definition_loaded>false</definition_loaded>
@@ -32484,6 +32652,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>gsr_by_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>gsr_by_delivery_date</name>
<definition_loaded>false</definition_loaded>
@@ -32673,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>
@@ -32715,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>
@@ -32757,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>
@@ -32799,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>
@@ -32820,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>
@@ -32925,6 +33219,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>open_orders_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>open_orders_estimator</name>
<definition_loaded>false</definition_loaded>

View File

@@ -81,6 +81,7 @@ function BillEnterModalContainer({
},
],
},
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"],
});
console.log("adjustmentsToInsert", adjustmentsToInsert);
const adjKeys = Object.keys(adjustmentsToInsert);

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

@@ -45,7 +45,7 @@ export default function GlobalSearch() {
<span>{`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${
job.v_model_desc || ""
}`}</span>
<span>{`${job.clm_no}`}</span>
<span>{`${job.clm_no || ""}`}</span>
</Space>
</Link>
),
@@ -91,8 +91,8 @@ export default function GlobalSearch() {
vehicle.v_make_desc || ""
} ${vehicle.v_model_desc || ""}`}
</span>
<span>{vehicle.plate_no}</span>
<span> {vehicle.v_vin}</span>
<span>{vehicle.plate_no || ""}</span>
<span> {vehicle.v_vin || ""}</span>
</Space>
</Link>
),
@@ -108,10 +108,11 @@ export default function GlobalSearch() {
label: (
<Link to={`/manage/jobs/${payment.job.id}`}>
<Space size="small" split={<Divider type="vertical" />}>
<span>{payment.paymentnum}</span>
<span>{payment.job.ro_number}</span>
<span>{payment.job.memo}</span>
<span>{payment.job.amount}</span>
<span>{payment.job.transactionid}</span>
<span>{payment.memo || ""}</span>
<span>{payment.amount || ""}</span>
<span>{payment.transactionid || ""}</span>
</Space>
</Link>
),

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

@@ -295,18 +295,18 @@ export function JobLinesComponent({
onClick={async () => {
await deleteJobLine({
variables: { joblineId: record.id },
update(cache) {
cache.modify({
id: cache.identify(job),
fields: {
joblines(existingJobLines, { readField }) {
return existingJobLines.filter(
(jlRef) => record.id !== readField("id", jlRef)
);
},
},
});
},
// update(cache) {
// cache.modify({
// id: cache.identify(job),
// fields: {
// joblines(existingJobLines, { readField }) {
// return existingJobLines.filter(
// (jlRef) => record.id !== readField("id", jlRef)
// );
// },
// },
// });
// },
});
await axios.post("/job/totalsssu", {
id: job.id,

View File

@@ -1,4 +1,5 @@
import { Button, Card, Space, Table } from "antd";
import { EditFilled } from "@ant-design/icons";
import Dinero from "dinero.js";
import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -115,16 +116,29 @@ export function JobPayments({
dataIndex: "actions",
key: "actions",
render: (text, record) => (
<PrintWrapperComponent
templateObject={{
name: TemplateList("payment").payment_receipt.key,
variables: { id: record.id },
}}
messageObject={{
to: job.ownr_ea,
}}
id={job.id}
/>
<Space wrap>
<Button
disabled={record.exportedat}
onClick={() => {
setPaymentContext({
actions: { refetch: refetch },
context: record,
});
}}
>
<EditFilled />
</Button>
<PrintWrapperComponent
templateObject={{
name: TemplateList("payment").payment_receipt.key,
variables: { id: record.id },
}}
messageObject={{
to: job.ownr_ea,
}}
id={job.id}
/>
</Space>
),
},
];

View File

@@ -1,4 +1,4 @@
import { Checkbox, PageHeader, Table } from "antd";
import { Checkbox, Table, Typography } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
@@ -21,6 +21,7 @@ export default function JobReconciliationBillsTable({
title: t("billlines.fields.line_desc"),
dataIndex: "line_desc",
key: "line_desc",
width: "35%",
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
sortOrder:
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
@@ -29,6 +30,8 @@ export default function JobReconciliationBillsTable({
title: t("billlines.labels.from"),
dataIndex: "from",
key: "from",
width: "20%",
ellipsis: true,
render: (text, record) =>
`${record.bill.vendor && record.bill.vendor.name} / ${
record.bill.invoice_number
@@ -57,7 +60,7 @@ export default function JobReconciliationBillsTable({
),
},
{
title: t("billlines.fields.quantity"),
title: t("joblines.fields.part_qty"),
dataIndex: "quantity",
key: "quantity",
sorter: (a, b) => a.quantity - b.quantity,
@@ -86,10 +89,12 @@ export default function JobReconciliationBillsTable({
};
return (
<PageHeader title={t("bills.labels.bills")}>
<div>
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
<Table
pagination={false}
scroll={{ y: "40vh", x: true }}
size="small"
scroll={{ y: "80vh", x: true }}
columns={columns}
rowKey="id"
dataSource={invoiceLineData}
@@ -99,6 +104,6 @@ export default function JobReconciliationBillsTable({
selectedRowKeys: selectedLines,
}}
/>
</PageHeader>
</div>
);
}

View File

@@ -22,21 +22,23 @@ export default function JobReconciliationModalComponent({ job, bills }) {
);
return (
<div>
<Row gutter={[16, 16]}>
<Col span={12}>
<JobReconciliationPartsTable
jobLineData={jobLineData}
jobLineState={jobLineState}
/>
</Col>
<Col span={12}>
<JobReconciliationBillsTable
invoiceLineData={invoiceLineData}
billLineState={billLineState}
/>
</Col>
</Row>
<div style={{ flex: 1, display: "flex", flexDirection: "column" }}>
<div style={{ flex: 1 }}>
<Row gutter={8}>
<Col span={12}>
<JobReconciliationPartsTable
jobLineData={jobLineData}
jobLineState={jobLineState}
/>
</Col>
<Col span={12}>
<JobReconciliationBillsTable
invoiceLineData={invoiceLineData}
billLineState={billLineState}
/>
</Col>
</Row>
</div>
<Row>
<JobReconciliationTotals
jobLines={jobLineData}

View File

@@ -0,0 +1,12 @@
.imex-reconciliation-modal {
top: 20px;
.ant-modal-content {
height: 95vh;
display: flex;
flex-direction: column;
.ant-modal-body {
display: flex;
flex: 1;
}
}
}

View File

@@ -10,6 +10,7 @@ import { selectReconciliation } from "../../redux/modals/modals.selectors";
import JobReconciliationModalComponent from "./job-reconciliation-modal.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import AlertComponent from "../alert/alert.component";
import "./job-reconciliation-modal.styles.scss";
const mapStateToProps = createStructuredSelector({
reconciliationModal: selectReconciliation,
@@ -38,23 +39,23 @@ function JobReconciliationModalContainer({
return (
<Modal
title={t("jobs.labels.reconciliationheader")}
width={"90%"}
width={"95%"}
visible={visible}
okText={t("general.actions.close")}
onOk={handleCancel}
onCancel={handleCancel}
cancelButtonProps={{ display: "none" }}
destroyOnClose
className="imex-reconciliation-modal"
>
<LoadingSpinner loading={loading}>
{error && <AlertComponent message={error.message} type="error" />}
{data && (
<JobReconciliationModalComponent
job={data && data.jobs_by_pk}
bills={data && data.bills}
/>
)}
</LoadingSpinner>
{loading && <LoadingSpinner loading={loading} />}
{error && <AlertComponent message={error.message} type="error" />}
{data && (
<JobReconciliationModalComponent
job={data && data.jobs_by_pk}
bills={data && data.bills}
/>
)}
</Modal>
);
}

View File

@@ -1,4 +1,4 @@
import { PageHeader, Table } from "antd";
import { Table, Typography } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
@@ -102,11 +102,13 @@ export default function JobReconcilitionPartsTable({
};
return (
<PageHeader title={t("jobs.labels.lines")}>
<div>
<Typography.Title level={4}>{t("jobs.labels.lines")}</Typography.Title>
<Table
pagination={false}
columns={columns}
scroll={{ y: "40vh", x: true }}
size="small"
scroll={{ y: "80vh", x: true }}
rowKey="id"
dataSource={jobLineData}
onChange={handleTableChange}
@@ -122,6 +124,6 @@ export default function JobReconcilitionPartsTable({
<div style={{ fontStyle: "italic", margin: "4px" }}>
{t("jobs.labels.reconciliation.removedpartsstrikethrough")}
</div>
</PageHeader>
</div>
);
}

View File

@@ -1,6 +1,6 @@
import { LoadingOutlined } from "@ant-design/icons";
import { useLazyQuery } from "@apollo/client";
import { Empty, Select } from "antd";
import { Empty, Select, Space, Tag } from "antd";
import _ from "lodash";
import React, { forwardRef, useEffect } from "react";
import { useTranslation } from "react-i18next";
@@ -80,13 +80,20 @@ const JobSearchSelect = (
{theOptions
? theOptions.map((o) => (
<Option key={o.id} value={o.id} status={o.status}>
{`${clm_no && o.clm_no ? `${o.clm_no} | ` : ""}${
o.ro_number || t("general.labels.na")
} | ${o.ownr_ln || ""} ${o.ownr_fn || ""} ${
o.ownr_co_nm ? ` ${o.ownr_co_num}` : ""
}| ${o.v_model_yr || ""} ${o.v_make_desc || ""} ${
o.v_model_desc || ""
}`}
<Space align="center">
<span>
{`${clm_no && o.clm_no ? `${o.clm_no} | ` : ""}${
o.ro_number || t("general.labels.na")
} | ${o.ownr_ln || ""} ${o.ownr_fn || ""} ${
o.ownr_co_nm ? ` ${o.ownr_co_num}` : ""
}| ${o.v_model_yr || ""} ${o.v_make_desc || ""} ${
o.v_model_desc || ""
}`}
</span>
<Tag>
<strong>{o.status}</strong>
</Tag>
</Space>
</Option>
))
: null}

View File

@@ -0,0 +1,57 @@
import { DownCircleFilled } from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { Button, Dropdown, Menu, notification } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { UPDATE_JOB_STATUS } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminStatus);
export function JobsAdminStatus({ bodyshop, job }) {
const { t } = useTranslation();
const [mutationUpdateJobstatus] = useMutation(UPDATE_JOB_STATUS);
const updateJobStatus = (status) => {
mutationUpdateJobstatus({
variables: { jobId: job.id, status: status },
})
.then((r) => {
notification["success"]({ message: t("jobs.successes.save") });
// refetch();
})
.catch((error) => {
notification["error"]({ message: t("jobs.errors.saving") });
});
};
const statusmenu = (
<Menu
onClick={(e) => {
updateJobStatus(e.key);
}}
>
{bodyshop.md_ro_statuses.statuses.map((item) => (
<Menu.Item key={item}>{item}</Menu.Item>
))}
</Menu>
);
return (
<Dropdown overlay={statusmenu} trigger={["click"]} key="changestatus">
<Button shape="round">
<span>{job.status}</span>
<DownCircleFilled />
</Button>
</Dropdown>
);
}

View File

@@ -11,6 +11,7 @@ export const GetSupplementDelta = async (client, jobId, newLines) => {
query: GET_ALL_JOBLINES_BY_PK,
variables: { id: jobId },
});
const existingLines = _.cloneDeep(existingLinesFromDb);
const linesToInsert = [];
const linesToUpdate = [];
@@ -19,11 +20,14 @@ export const GetSupplementDelta = async (client, jobId, newLines) => {
const matchingIndex = existingLines.findIndex(
(eL) => eL.unq_seq === newLine.unq_seq
);
//Should do a check to make sure there is only 1 matching unq sequence number.
if (matchingIndex >= 0) {
//Found a relevant matching line. Add it to lines to update.
linesToUpdate.push({
id: existingLines[matchingIndex].id,
newData: newLine,
newData: { ...newLine, removed: false },
});
//Splice out item we found for performance.

View File

@@ -11,7 +11,8 @@ import FormItemPhone, {
PhoneItemFormatterValidation,
} from "../form-items-formatted/phone-form-item.component";
import JobsDetailRatesChangeButton from "../jobs-detail-rates-change-button/jobs-detail-rates-change-button.component";
import { JobsDetailRatesParts } from "../jobs-detail-rates/jobs-detail-rates.parts.component";
import JobsDetailRatesParts from "../jobs-detail-rates/jobs-detail-rates.parts.component";
import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -187,7 +188,7 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
header={t("menus.jobsdetail.financials")}
>
<JobsDetailRatesChangeButton form={form} />
<JobsMarkPstExempt form={form} />
<LayoutFormRow>
<Form.Item label={t("jobs.fields.ded_amt")} name="ded_amt">
<CurrencyInput />

View File

@@ -1,4 +1,13 @@
import { Form, Input, InputNumber, Select, Space, Switch, Tooltip } from "antd";
import {
Divider,
Form,
Input,
InputNumber,
Select,
Space,
Switch,
Tooltip,
} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -7,6 +16,7 @@ import { selectJobReadOnly } from "../../redux/application/application.selectors
import CABCpvrtCalculator from "../ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import JobsDetailRatesChangeButton from "../jobs-detail-rates-change-button/jobs-detail-rates-change-button.component";
import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
import FormRow from "../layout-form-row/layout-form-row.component";
import JobsDetailRatesParts from "./jobs-detail-rates.parts.component";
@@ -103,8 +113,19 @@ export function JobsDetailRates({ jobRO, form, job }) {
<Switch disabled={jobRO} />
</Form.Item>
</FormRow>
<JobsDetailRatesChangeButton form={form} disabled={jobRO} />
<FormRow header={t("jobs.forms.laborrates")}>
<Divider
orientation="left"
type="horizontal"
style={{ marginTop: ".8rem", float: "right" }}
>
{t("jobs.forms.laborrates")}
</Divider>
<Space>
<div></div>
<JobsDetailRatesChangeButton form={form} disabled={jobRO} />
<JobsMarkPstExempt form={form} />
</Space>
<FormRow noDivider>
<Form.Item
label={t("jobs.fields.labor_rate_desc")}
name="labor_rate_desc"
@@ -180,7 +201,6 @@ export function JobsDetailRates({ jobRO, form, job }) {
<CurrencyInput disabled={jobRO} />
</Form.Item>
</FormRow>
<JobsDetailRatesParts form={form} />
</div>
);

View File

@@ -19,7 +19,11 @@ export function JobsDetailRatesParts({
return (
<Collapse defaultActiveKey={expanded && "rates"}>
<Collapse.Panel header={t("jobs.labels.parts_tax_rates")} key="rates">
<Collapse.Panel
forceRender
header={t("jobs.labels.parts_tax_rates")}
key="rates"
>
<LayoutFormRow header={t("joblines.fields.part_types.PAA")}>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")}

View File

@@ -0,0 +1,55 @@
import { Popconfirm, Button } from "antd";
import React from "react";
import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import _ from "lodash";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly,
});
export function JobsMarkPstExempt({ jobRO, form }) {
const { t } = useTranslation();
const handleConfirm = () => {
const newPartRates = _.cloneDeep(form.getFieldValue("parts_tax_rates"));
Object.keys(newPartRates).forEach((key) => {
newPartRates[key] = {
...newPartRates[key],
prt_tax_in: false,
prt_tax_rt: 0,
};
});
form.setFieldsValue({
state_tax_rate: 0,
tax_lbr_rt: 0,
tax_levies_rt: 0,
tax_sub_rt: 0,
tax_shop_mat_rt: 0,
tax_paint_mat_rt: 0,
tax_str_rt: 0,
tax_tow_rt: 0,
parts_tax_rates: newPartRates,
});
};
return (
<Popconfirm
onConfirm={handleConfirm}
disabled={jobRO}
okText={t("general.labels.yes")}
cancelText={t("general.labels.no")}
title={t("jobs.actions.markpstexemptconfirm")}
>
<Button type="link" disabled={jobRO}>
{t("jobs.actions.markpstexempt")}
</Button>
</Popconfirm>
);
}
export default connect(mapStateToProps, null)(JobsMarkPstExempt);

View File

@@ -31,11 +31,14 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
title: t("jobs.fields.vehicle"),
dataIndex: "vehicleid",
key: "vehicleid",
render: (text, record) => (
<Link to={`/manage/vehicles/${record.vehicleid}`}>
{`${record.v_model_yr} ${record.v_make_desc} ${record.v_model_desc}`}
</Link>
),
render: (text, record) =>
record.vehicleid ? (
<Link to={`/manage/vehicles/${record.vehicleid}`}>
{`${record.v_model_yr} ${record.v_make_desc} ${record.v_model_desc}`}
</Link>
) : (
t("jobs.errors.novehicle")
),
},
{
title: t("jobs.fields.clm_no"),

View File

@@ -90,7 +90,11 @@ export function PartsOrderListTableComponent({
</Button>
)}
<Button
disabled={jobRO || record.return}
disabled={
jobRO ||
record.return ||
record.vendor.id === bodyshop.inhousevendorid
}
onClick={() => {
logImEXEvent("parts_order_receive_bill");
setPartsReceiveContext({
@@ -139,7 +143,10 @@ export function PartsOrderListTableComponent({
</Button>
</Popconfirm>
<Button
disabled={jobRO ? !record.return : jobRO}
disabled={
(jobRO ? !record.return : jobRO) ||
record.vendor.id === bodyshop.inhousevendorid
}
onClick={() => {
logImEXEvent("parts_order_receive_bill");
@@ -157,7 +164,7 @@ export function PartsOrderListTableComponent({
quantity: pol.quantity,
actual_price: pol.act_price,
cost_center: pol.jobline.part_type
cost_center: pol.jobline?.part_type
? responsibilityCenters.defaults.costs[
pol.jobline.part_type
] || null

View File

@@ -101,7 +101,9 @@ export function PartsOrderModalContainer({
const jobLinesResult = await updateJobLines({
variables: {
ids: values.parts_order_lines.data.map((item) => item.job_line_id),
ids: values.parts_order_lines.data
.filter((item) => item.job_line_id)
.map((item) => item.job_line_id),
status: isReturn
? bodyshop.md_order_statuses.default_returned || "Returned*"
: bodyshop.md_order_statuses.default_ordered || "Ordered*",

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

@@ -23,19 +23,6 @@ export const GET_ALL_JOBLINES_BY_PK = gql`
notes
location
tax_part
parts_order_lines {
id
parts_order {
id
order_number
order_date
user_email
vendor {
id
name
}
}
}
}
}
`;
@@ -228,7 +215,11 @@ export const generateJobLinesUpdatesForInvoicing = (joblines) => {
export const DELETE_JOB_LINE_BY_PK = gql`
mutation DELETE_JOB_LINE_BY_PK($joblineId: uuid!) {
delete_joblines_by_pk(id: $joblineId) {
update_joblines_by_pk(
pk_columns: { id: $joblineId }
_set: { removed: true }
) {
removed
id
}
}

View File

@@ -559,6 +559,7 @@ export const GET_JOB_BY_PK = gql`
}
payments {
id
jobid
amount
payer
created_at
@@ -566,6 +567,8 @@ export const GET_JOB_BY_PK = gql`
transactionid
memo
date
type
exportedat
}
cccontracts {
id
@@ -686,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
@@ -1040,6 +1045,7 @@ export const SEARCH_JOBS_FOR_AUTOCOMPLETE = gql`
v_make_desc
v_model_desc
v_model_yr
status
}
}
`;

View File

@@ -36,6 +36,7 @@ export const GLOBAL_SEARCH_QUERY = gql`
search_payments(args: { search: $search }) {
id
amount
paymentnum
job {
ro_number
id

View File

@@ -81,48 +81,52 @@ export default class Home extends React.Component {
dataSource={Banner00DataSource}
isMobile={this.state.isMobile}
/>,
// <Content4
// id="Content4_0"
// key="Content4_0"
// dataSource={Content40DataSource}
// isMobile={this.state.isMobile}
// />,
<Content1
id="Content1_0"
key="Content1_0"
dataSource={Content10DataSource}
isMobile={this.state.isMobile}
/>,
<Content0
id="Content0_0"
key="Content0_0"
dataSource={Content00DataSource}
isMobile={this.state.isMobile}
/>,
<Pricing2
id="Pricing2_0"
key="Pricing2_0"
dataSource={Pricing20DataSource}
isMobile={this.state.isMobile}
/>,
// <Pricing1
// id="Pricing1_1"
// key="Pricing1_1"
// dataSource={Pricing11DataSource}
// isMobile={this.state.isMobile}
// />,
// <Content3
// id="Content3_0"
// key="Content3_0"
// dataSource={Content30DataSource}
// isMobile={this.state.isMobile}
// />,
// <Content12
// id="Content12_0"
// key="Content12_0"
// dataSource={Content120DataSource}
// isMobile={this.state.isMobile}
// />,
...(process.env.NODE_ENV !== "production"
? [
// <Content4
// id="Content4_0"
// key="Content4_0"
// dataSource={Content40DataSource}
// isMobile={this.state.isMobile}
// />,
<Content1
id="Content1_0"
key="Content1_0"
dataSource={Content10DataSource}
isMobile={this.state.isMobile}
/>,
<Content0
id="Content0_0"
key="Content0_0"
dataSource={Content00DataSource}
isMobile={this.state.isMobile}
/>,
<Pricing2
id="Pricing2_0"
key="Pricing2_0"
dataSource={Pricing20DataSource}
isMobile={this.state.isMobile}
/>,
// <Pricing1
// id="Pricing1_1"
// key="Pricing1_1"
// dataSource={Pricing11DataSource}
// isMobile={this.state.isMobile}
// />,
// <Content3
// id="Content3_0"
// key="Content3_0"
// dataSource={Content30DataSource}
// isMobile={this.state.isMobile}
// />,
// <Content12
// id="Content12_0"
// key="Content12_0"
// dataSource={Content120DataSource}
// isMobile={this.state.isMobile}
// />,
]
: []),
<Footer1
id="Footer1_0"
key="Footer1_0"

View File

@@ -12,7 +12,6 @@ import AlertComponent from "../../components/alert/alert.component";
import { QUERY_EXPORT_LOG_PAGINATED } from "../../graphql/accounting.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -79,12 +78,10 @@ export function ExportLogsPageComponent({ bodyshop }) {
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder: sortcolumn === "ro_number" && sortorder,
render: (text, record) =>
record.job && (
<Link to={"/manage/jobs/" + record.job && record.job.id}>
<Link to={`/manage/jobs/${record.job.id}`}>
{(record.job && record.job.ro_number) || t("general.labels.na")}
</Link>
),

View File

@@ -15,6 +15,8 @@ import JobAdminOwnerReassociate from "../../components/jobs-admin-owner-reassoci
import JobsAdminUnvoid from "../../components/jobs-admin-unvoid/jobs-admin-unvoid.component";
import JobAdminVehicleReassociate from "../../components/jobs-admin-vehicle-reassociate/jobs-admin-vehicle-reassociate.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import JobsAdminStatus from "../../components/jobs-admin-change-status/jobs-admin-change.status.component";
import NotFound from "../../components/not-found/not-found.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { GET_JOB_BY_PK } from "../../graphql/jobs.queries";
@@ -96,6 +98,7 @@ export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader }) {
<JobsAdminDeleteIntake job={data ? data.jobs_by_pk : {}} />
<JobsAdminMarkReexport job={data ? data.jobs_by_pk : {}} />
<JobsAdminUnvoid job={data ? data.jobs_by_pk : {}} />
<JobsAdminStatus job={data ? data.jobs_by_pk : {}} />
</Space>
</Card>
</Col>

View File

@@ -42,9 +42,26 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
setLoading(true);
const result = await client.mutate({
mutation: generateJobLinesUpdatesForInvoicing(values.joblines),
});
if (result.errors) {
return; // Abandon the rest of the close.
}
const closeResult = await closeJob({
variables: {
jobId: job.id,
job: {
status: bodyshop.md_ro_statuses.default_invoiced || "",
date_invoiced: new Date(),
actual_in: values.actual_in,
actual_completion: values.actual_completion,
actual_delivery: values.actual_delivery,
},
},
refetchQueries: ["QUERY_JOB_CLOSE_DETAILS"],
awaitRefetchQueries: true,
});
if (!result.errors) {
notification["success"]({ message: t("jobs.successes.save") });
// form.resetFields();
@@ -56,18 +73,6 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
});
return; // Abandon the rest of the close.
}
form.resetFields();
form.resetFields();
const closeResult = await closeJob({
variables: {
jobId: job.id,
job: {
status: bodyshop.md_ro_statuses.default_invoiced || "",
date_invoiced: new Date(),
},
},
});
if (!closeResult.errors) {
setLoading(false);
@@ -84,6 +89,8 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
}),
});
}
form.resetFields();
form.resetFields();
setLoading(false);
};

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": {
@@ -1034,6 +1036,8 @@
"intake": "Intake",
"manualnew": "Create New Job Manually",
"mark": "Mark",
"markpstexempt": "Mark Job PST Exempt",
"markpstexemptconfirm": "Are you sure you want to do this? To undo this, you must manually update all PST rates.",
"postbills": "Post Bills",
"printCenter": "Print Center",
"recalculate": "Recalculate",
@@ -1441,11 +1445,11 @@
"name": "ImEX Online",
"status": "System Status"
},
"slogan": "The future of shop management systems. "
"slogan": "A whole new kind of shop management system."
},
"hero": {
"button": "Learn More",
"title": "Bringing the future to the collision repair process."
"button": "Coming Soon",
"title": "A whole new kind of shop management system."
},
"labels": {
"features": "Features",
@@ -1805,6 +1809,7 @@
"depreciation": "Depreciation",
"other": "Other",
"ponumber": "PO Number",
"refnumber": "Reference Number",
"sendtype": "Send by",
"state": "Province/State",
"zip": "Postal Code/Zip"
@@ -1830,6 +1835,8 @@
"invoice_total_payable": "Invoice (Total Payable)",
"job_costing_ro": "Job Costing",
"job_notes": "Job Notes",
"key_tag": "Key Tag",
"paint_grid": "Paint Grid",
"parts_label_single": "Parts Label - Single",
"parts_list": "Parts List",
"parts_order": "Parts Order Confirmation",
@@ -1844,6 +1851,7 @@
"qc_sheet": "Quality Control Sheet",
"ro_totals": "RO Totals",
"ro_with_description": "RO Summary with Descriptions",
"stolen_recovery_checklist": "Stolen Recovery Checklist",
"supplement_request": "Supplement Request",
"thank_you_ro": "Thank You Letter",
"thirdpartypayer": "Third Party Payer",
@@ -1960,27 +1968,34 @@
"export_payables": "Export Log - Payables",
"export_payments": "Export Log - Payments",
"export_receivables": "Export Log - Receivables",
"gsr_by_csr": "Gross Sales by CSR",
"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",
"job_costing_ro_ins_co": "Job Costing by RO Source",
"open_orders": "Open Orders by Date",
"open_orders_csr": "Open Orders by CSR",
"open_orders_estimator": "Open Orders by Estimator",
"open_orders_ins_co": "Open Orders by Insurance Company",
"parts_backorder": "Backordered Parts",
@@ -2282,7 +2297,7 @@
"city": "City",
"cost_center": "Cost Center",
"country": "Country",
"discount": "Discount %",
"discount": "Discount % (as decimal)",
"display_name": "Display Name",
"due_date": "Payment Due Date",
"email": "Contact Email",

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": {
@@ -1034,6 +1036,8 @@
"intake": "",
"manualnew": "",
"mark": "",
"markpstexempt": "",
"markpstexemptconfirm": "",
"postbills": "Contabilizar facturas",
"printCenter": "Centro de impresión",
"recalculate": "",
@@ -1805,6 +1809,7 @@
"depreciation": "",
"other": "",
"ponumber": "",
"refnumber": "",
"sendtype": "",
"state": "",
"zip": ""
@@ -1830,6 +1835,8 @@
"invoice_total_payable": "",
"job_costing_ro": "",
"job_notes": "",
"key_tag": "",
"paint_grid": "",
"parts_label_single": "",
"parts_list": "",
"parts_order": "",
@@ -1844,6 +1851,7 @@
"qc_sheet": "",
"ro_totals": "",
"ro_with_description": "",
"stolen_recovery_checklist": "",
"supplement_request": "",
"thank_you_ro": "",
"thirdpartypayer": "",
@@ -1960,6 +1968,7 @@
"export_payables": "",
"export_payments": "",
"export_receivables": "",
"gsr_by_csr": "",
"gsr_by_delivery_date": "",
"gsr_by_estimator": "",
"gsr_by_exported_date": "",
@@ -1969,18 +1978,24 @@
"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": "",
"job_costing_ro_ins_co": "",
"open_orders": "",
"open_orders_csr": "",
"open_orders_estimator": "",
"open_orders_ins_co": "",
"parts_backorder": "",

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": {
@@ -1034,6 +1036,8 @@
"intake": "",
"manualnew": "",
"mark": "",
"markpstexempt": "",
"markpstexemptconfirm": "",
"postbills": "Poster des factures",
"printCenter": "Centre d'impression",
"recalculate": "",
@@ -1805,6 +1809,7 @@
"depreciation": "",
"other": "",
"ponumber": "",
"refnumber": "",
"sendtype": "",
"state": "",
"zip": ""
@@ -1830,6 +1835,8 @@
"invoice_total_payable": "",
"job_costing_ro": "",
"job_notes": "",
"key_tag": "",
"paint_grid": "",
"parts_label_single": "",
"parts_list": "",
"parts_order": "",
@@ -1844,6 +1851,7 @@
"qc_sheet": "",
"ro_totals": "",
"ro_with_description": "",
"stolen_recovery_checklist": "",
"supplement_request": "",
"thank_you_ro": "",
"thirdpartypayer": "",
@@ -1960,6 +1968,7 @@
"export_payables": "",
"export_payments": "",
"export_receivables": "",
"gsr_by_csr": "",
"gsr_by_delivery_date": "",
"gsr_by_estimator": "",
"gsr_by_exported_date": "",
@@ -1969,18 +1978,24 @@
"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": "",
"job_costing_ro_ins_co": "",
"open_orders": "",
"open_orders_csr": "",
"open_orders_estimator": "",
"open_orders_ins_co": "",
"parts_backorder": "",

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

@@ -69,6 +69,14 @@ export const TemplateList = (type, context) => {
disabled: false,
group: "pre",
},
stolen_recovery_checklist: {
title: i18n.t("printcenter.jobs.stolen_recovery_checklist"),
description: "All Jobs Notes",
subject: i18n.t("printcenter.jobs.stolen_recovery_checklist"),
key: "stolen_recovery_checklist",
disabled: false,
group: "pre",
},
vehicle_check_in: {
title: i18n.t("printcenter.jobs.vehicle_check_in"),
description: "All Jobs Notes",
@@ -150,6 +158,22 @@ export const TemplateList = (type, context) => {
disabled: false,
group: "ro",
},
key_tag: {
title: i18n.t("printcenter.jobs.key_tag"),
description: "All Jobs Notes",
subject: i18n.t("printcenter.jobs.key_tag"),
key: "key_tag",
disabled: false,
group: "ro",
},
paint_grid: {
title: i18n.t("printcenter.jobs.paint_grid"),
description: "All Jobs Notes",
subject: i18n.t("printcenter.jobs.paint_grid"),
key: "paint_grid",
disabled: false,
group: "ro",
},
worksheet_by_line_number: {
title: i18n.t("printcenter.jobs.worksheet_by_line_number"),
description: "All Jobs Notes",
@@ -728,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: "",
@@ -790,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: "",
@@ -840,6 +937,18 @@ export const TemplateList = (type, context) => {
field: i18n.t("jobs.fields.date_open"),
},
},
gsr_by_csr: {
title: i18n.t("reportcenter.templates.gsr_by_csr"),
description: "",
subject: i18n.t("reportcenter.templates.gsr_by_csr"),
key: "gsr_by_csr",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_invoiced"),
},
},
gsr_by_make: {
title: i18n.t("reportcenter.templates.gsr_by_make"),
description: "",
@@ -949,6 +1058,18 @@ export const TemplateList = (type, context) => {
field: i18n.t("jobs.fields.date_open"),
},
},
open_orders_csr: {
title: i18n.t("reportcenter.templates.open_orders_csr"),
description: "",
subject: i18n.t("reportcenter.templates.open_orders_csr"),
key: "open_orders_csr",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_open"),
},
},
open_orders_estimator: {
title: i18n.t("reportcenter.templates.open_orders_estimator"),
description: "",

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,11 @@
- args:
cascade: false
read_only: false
sql: "CREATE OR REPLACE FUNCTION public.search_payments(search text)\n RETURNS
SETOF payments\n LANGUAGE plpgsql\n STABLE\nAS $function$\n\nBEGIN\n if search
= '' then\n return query select * from payments ;\n else \n return query
SELECT\n p.*\nFROM\n payments p, jobs j\nWHERE\np.jobid = j.id AND\n(\nsearch
<% p.paymentnum OR\nsearch <% j.ownr_fn OR\nsearch <% j.ownr_ln OR\nsearch <%
j.ownr_co_nm OR\nsearch <% j.ro_number OR\n search <% (p.payer) OR\n search
<% (p.transactionid) OR\n search <% (p.memo));\n end if;\n\n\tEND\n$function$;"
type: run_sql

View File

@@ -0,0 +1,6 @@
- args:
cascade: false
read_only: false
sql: alter table "public"."vehicles" add constraint "vehicles_v_vin_shopid_key"
unique ("v_vin", "shopid");
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: alter table "public"."vehicles" drop constraint "vehicles_v_vin_shopid_key";
type: run_sql

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

@@ -288,7 +288,7 @@ const generateInvoiceQbxml = (
});
// console.log("Done creating hash", JSON.stringify(invoiceLineHash));
if (!hasMapaLine) {
if (!hasMapaLine && jobs_by_pk.job_totals.rates.mapa.total.amount > 0) {
console.log("Adding MAPA Line Manually.");
const mapaAccountName = responsibilityCenters.defaults.profits.MAPA;
@@ -313,7 +313,7 @@ const generateInvoiceQbxml = (
}
}
if (!hasMashLine) {
if (!hasMashLine && jobs_by_pk.job_totals.rates.mash.total.amount > 0) {
console.log("Adding MASH Line Manually.");
const mashAccountName = responsibilityCenters.defaults.profits.MASH;
@@ -424,9 +424,11 @@ const generateInvoiceQbxml = (
TxnDate: moment(jobs_by_pk.date_invoiced).format("YYYY-MM-DD"),
RefNumber: jobs_by_pk.ro_number,
ShipAddress: {
Addr1: `${jobs_by_pk.ownr_fn || ""} ${jobs_by_pk.ownr_ln || ""} ${
jobs_by_pk.ownr_co_nm || ""
}`,
Addr1: jobs_by_pk.ownr_co_nm
? jobs_by_pk.ownr_co_nm.substring(0, 30)
: `${`${jobs_by_pk.ownr_ln || ""} ${
jobs_by_pk.ownr_fn || ""
}`.substring(0, 30)}`,
Addr2: jobs_by_pk.ownr_addr1,
Addr3: jobs_by_pk.ownr_addr2,
City: jobs_by_pk.ownr_city,

View File

@@ -17,12 +17,13 @@ exports.generateOwnerTier = (jobs_by_pk, isThreeTier, twotierpref) => {
if (isThreeTier) {
//It's always gonna be the owner now. Same as 2 tier by name
return jobs_by_pk.ownr_co_nm
? `${jobs_by_pk.ownr_co_nm} - ${jobs_by_pk.ownr_ln || ""} ${
jobs_by_pk.ownr_fn || ""
} #${jobs_by_pk.owner.accountingid || ""}`
: `${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""} #${
? `${jobs_by_pk.ownr_co_nm.substring(0, 30)} #${
jobs_by_pk.owner.accountingid || ""
}`;
}`
: `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}`.substring(
0,
30
)} #${jobs_by_pk.owner.accountingid || ""}`;
} else {
//What's the 2 tier pref?
if (twotierpref === "source") {
@@ -31,12 +32,12 @@ exports.generateOwnerTier = (jobs_by_pk, isThreeTier, twotierpref) => {
} else {
//Same as 3 tier
return jobs_by_pk.ownr_co_nm
? `${jobs_by_pk.ownr_co_nm} - ${jobs_by_pk.ownr_ln || ""} ${
jobs_by_pk.ownr_fn || ""
} #${jobs_by_pk.owner.accountingid || ""}`
: `${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""} #${
? `${jobs_by_pk.ownr_co_nm.substring(0, 30)} #${
jobs_by_pk.owner.accountingid || ""
}`;
}`
: `${`${jobs_by_pk.ownr_ln || ""} ${
jobs_by_pk.ownr_fn || ""
}`.substring(0, 30)} #${jobs_by_pk.owner.accountingid || ""}`;
}
}
};

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

@@ -57,6 +57,7 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
ownerid
ownr_ln
ownr_fn
ownr_co_nm
ownr_addr1
ownr_addr2
ownr_zip
@@ -356,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
@@ -432,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
@@ -451,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
@@ -506,6 +507,7 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz) {
}
}
}
`;
exports.UPDATE_JOB = `
@@ -905,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"