Compare commits
111 Commits
release/20
...
release/20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09f909142b | ||
|
|
fb380a5b31 | ||
|
|
3896a0b03d | ||
|
|
832674662d | ||
|
|
f2b2011900 | ||
|
|
15c305317a | ||
|
|
d6924c2292 | ||
|
|
7ccd356f0a | ||
|
|
592b47b5d4 | ||
|
|
d9beee7f2f | ||
|
|
75743f44e7 | ||
|
|
3d68a7099b | ||
|
|
71f161ec27 | ||
|
|
9b1f24926f | ||
|
|
bcc153caa5 | ||
|
|
f59911d5ab | ||
|
|
417958e1e8 | ||
|
|
ccf2f0ad47 | ||
|
|
bb993ab1fb | ||
|
|
9c39c8d59b | ||
|
|
3439f09d9a | ||
|
|
279e93f0c3 | ||
|
|
92f14d6fa5 | ||
|
|
148c645f18 | ||
|
|
9a65b6a1ce | ||
|
|
839c82abb9 | ||
|
|
471756d7ad | ||
|
|
2e2a4920ca | ||
|
|
f775e09391 | ||
|
|
c0b0bcd55e | ||
|
|
1bc5493f3f | ||
|
|
2579558090 | ||
|
|
bc9a3a21a8 | ||
|
|
f11eb6406d | ||
|
|
3d6bad9e7d | ||
|
|
12a5f17351 | ||
|
|
a2032553d9 | ||
|
|
d22979dadc | ||
|
|
c1068ec92b | ||
|
|
a318f3e74b | ||
|
|
e5a5cb4e85 | ||
|
|
f8151e387e | ||
|
|
b98bfe566a | ||
|
|
c0220f0ca2 | ||
|
|
3e121a1a25 | ||
|
|
a2a8868223 | ||
|
|
80d16b4651 | ||
|
|
ce3fbab1dc | ||
|
|
4c1a333514 | ||
|
|
0660b79c01 | ||
|
|
63ec578b6a | ||
|
|
dcf388ff7c | ||
|
|
0cd1b41ed9 | ||
|
|
79124daa9a | ||
|
|
76ec55d709 | ||
|
|
375b8ba050 | ||
|
|
2192cb1e7c | ||
|
|
65b505035a | ||
|
|
191f3f96a2 | ||
|
|
14d873f795 | ||
|
|
55ad75df8a | ||
|
|
5f112e797d | ||
|
|
b842cee076 | ||
|
|
2c1c11828d | ||
|
|
e7c380d780 | ||
|
|
0958ea5ba6 | ||
|
|
8031f2b2ed | ||
|
|
996863fcb7 | ||
|
|
066f395a40 | ||
|
|
51c5d163a5 | ||
|
|
d8ec6dd997 | ||
|
|
cc636bdfe2 | ||
|
|
a3e8f56728 | ||
|
|
a6f2cfba0f | ||
|
|
2e7d8df781 | ||
|
|
da51aeb135 | ||
|
|
680dd98f6d | ||
|
|
e79b9f9084 | ||
|
|
22e6d596e6 | ||
|
|
1ba904d082 | ||
|
|
62be2b0a0a | ||
|
|
dc77930950 | ||
|
|
520b61706f | ||
|
|
09a87dd2a7 | ||
|
|
f884d2e23f | ||
|
|
b84935efdc | ||
|
|
b3aeee4f45 | ||
|
|
06f266a292 | ||
|
|
a2d54d5dd5 | ||
|
|
b34694f3c4 | ||
|
|
0dbb3a446a | ||
|
|
2409042450 | ||
|
|
f509ea07c0 | ||
|
|
e263c32d83 | ||
|
|
4a023faf67 | ||
|
|
90f0232ff0 | ||
|
|
e0e2183d86 | ||
|
|
bc504d2a78 | ||
|
|
7c66e5cb90 | ||
|
|
06f725ebb1 | ||
|
|
8745ffd08f | ||
|
|
b5386be6af | ||
|
|
07b5c5e93c | ||
|
|
dcb9c32336 | ||
|
|
6a59092d6a | ||
|
|
9d770a4cd5 | ||
|
|
a20e005583 | ||
|
|
b81d3369af | ||
|
|
9ab08fbdd0 | ||
|
|
1a53e7c2f7 | ||
|
|
36a7b8346e |
@@ -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>
|
||||
@@ -25204,6 +25356,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>cost_sublet</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>costs</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -27119,6 +27292,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>sale_sublet</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>sales</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -27329,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>
|
||||
@@ -34710,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>
|
||||
@@ -35691,6 +35927,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>exported_payroll</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>
|
||||
@@ -38364,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>
|
||||
@@ -43290,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>
|
||||
|
||||
@@ -108,7 +108,7 @@ export function BillDetailEditcontainer({
|
||||
);
|
||||
|
||||
billlines.forEach((billline) => {
|
||||
const { deductedfromlbr, ...il } = billline;
|
||||
const { deductedfromlbr, jobline, ...il } = billline;
|
||||
delete il.__typename;
|
||||
|
||||
if (il.id) {
|
||||
|
||||
@@ -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")}
|
||||
|
||||
@@ -97,7 +97,9 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
render: (text, record) =>
|
||||
record.cccontracts.length === 1 ? (
|
||||
<Link to={`/manage/jobs/${record.cccontracts[0].job.id}`}>
|
||||
{record.cccontracts[0].job.ro_number}
|
||||
{`${record.cccontracts[0].job.ro_number} - ${
|
||||
record.cccontracts[0].job.ownr_fn || ""
|
||||
} ${record.cccontracts[0].job.ownr_ln || ""} ${record.cccontracts[0].job.ownr_co_nm || ""}`}
|
||||
</Link>
|
||||
) : null,
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -23,20 +23,21 @@ export function FormDatePicker({
|
||||
onChange,
|
||||
onBlur,
|
||||
onlyFuture,
|
||||
isDateOnly = true,
|
||||
...restProps
|
||||
}) {
|
||||
const ref = useRef();
|
||||
|
||||
const handleChange = (newDate) => {
|
||||
if (value !== newDate && onChange) {
|
||||
onChange(newDate);
|
||||
onChange(isDateOnly ? newDate && newDate.format("YYYY-MM-DD") : newDate);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key.toLowerCase() === "t") {
|
||||
if (onChange) {
|
||||
onChange(moment());
|
||||
onChange(isDateOnly ? moment().format("YYYY-MM-DD") : moment());
|
||||
// if (ref.current && ref.current.blur) ref.current.blur();
|
||||
}
|
||||
} else if (e.key.toLowerCase() === "enter") {
|
||||
@@ -64,7 +65,8 @@ export function FormDatePicker({
|
||||
});
|
||||
}
|
||||
|
||||
if (_a.isValid() && onChange) onChange(_a);
|
||||
if (_a.isValid() && onChange)
|
||||
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -26,19 +26,20 @@ const DateTimePicker = (
|
||||
value={value}
|
||||
onBlur={onBlur}
|
||||
onChange={onChange}
|
||||
isDateOnly={false}
|
||||
/>
|
||||
|
||||
<TimePicker
|
||||
{...restProps}
|
||||
value={value ? moment(value) : null}
|
||||
{...(onlyFuture && {
|
||||
disabledDate: (d) => moment().isAfter(d),
|
||||
})}
|
||||
onChange={onChange}
|
||||
showSecond={false}
|
||||
minuteStep={15}
|
||||
onBlur={onBlur}
|
||||
format="hh:mm a"
|
||||
value={value ? moment(value) : null}
|
||||
{...(onlyFuture && {
|
||||
disabledDate: (d) => moment().isAfter(d),
|
||||
})}
|
||||
onChange={onChange}
|
||||
showSecond={false}
|
||||
minuteStep={15}
|
||||
onBlur={onBlur}
|
||||
format="hh:mm a"
|
||||
{...restProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@ export default function JobCostingStatistics({ summaryData }) {
|
||||
value={Dinero(summaryData.totalPartsSales).toFormat()}
|
||||
title={t("jobs.labels.sale_parts")}
|
||||
/>
|
||||
<Statistic
|
||||
value={Dinero(summaryData.totalSubletSales).toFormat()}
|
||||
title={t("jobs.labels.sale_sublet")}
|
||||
/>
|
||||
<Statistic
|
||||
value={Dinero(summaryData.totalAdditionalSales).toFormat()}
|
||||
title={t("jobs.labels.sale_additional")}
|
||||
@@ -32,6 +36,10 @@ export default function JobCostingStatistics({ summaryData }) {
|
||||
value={Dinero(summaryData.totalPartsCost).toFormat()}
|
||||
title={t("jobs.labels.cost_parts")}
|
||||
/>
|
||||
<Statistic
|
||||
value={Dinero(summaryData.totalSubletCost).toFormat()}
|
||||
title={t("jobs.labels.cost_sublet")}
|
||||
/>
|
||||
<Statistic
|
||||
value={Dinero(summaryData.totalAdditionalCost).toFormat()}
|
||||
title={t("jobs.labels.cost_Additional")}
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -33,10 +33,10 @@ export default function JobTotalsTableOther({ job }) {
|
||||
key: t("jobs.fields.storage_payable"),
|
||||
total: job.job_totals.additional.storage,
|
||||
},
|
||||
{
|
||||
key: t("jobs.fields.ca_bc_pvrt"),
|
||||
total: job.job_totals.additional.pvrt,
|
||||
},
|
||||
// {
|
||||
// key: t("jobs.fields.ca_bc_pvrt"),
|
||||
// total: job.job_totals.additional.pvrt,
|
||||
// },
|
||||
];
|
||||
}, [job.job_totals, t]);
|
||||
|
||||
|
||||
@@ -3,7 +3,22 @@ import Dinero from "dinero.js";
|
||||
import React, { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function JobTotalsTableTotals({ job }) {
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(JobTotalsTableTotals);
|
||||
|
||||
export function JobTotalsTableTotals({ bodyshop, job }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const data = useMemo(() => {
|
||||
@@ -21,6 +36,14 @@ export default function JobTotalsTableTotals({ job }) {
|
||||
key: t("jobs.labels.state_tax_amt"),
|
||||
total: job.job_totals.totals.state_tax,
|
||||
},
|
||||
...(bodyshop.region_config === "CA_BC"
|
||||
? [
|
||||
{
|
||||
key: t("jobs.fields.ca_bc_pvrt"),
|
||||
total: job.job_totals.additional.pvrt,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: t("jobs.labels.federal_tax_amt"),
|
||||
total: job.job_totals.totals.federal_tax,
|
||||
@@ -58,7 +81,7 @@ export default function JobTotalsTableTotals({ job }) {
|
||||
bold: true,
|
||||
},
|
||||
];
|
||||
}, [job.job_totals, t]);
|
||||
}, [job.job_totals, t, bodyshop.region_config]);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")}>
|
||||
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
),
|
||||
|
||||
@@ -73,6 +73,7 @@ export function PartsOrderModalComponent({
|
||||
options={vendorList}
|
||||
disabled={isReturn}
|
||||
preferredMake={preferredMake}
|
||||
showPhone
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "actual_in" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<ProductionListDate record={record} field="actual_in" />
|
||||
<ProductionListDate record={record} field="actual_in" time/>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -114,6 +114,7 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
|
||||
record={record}
|
||||
field="scheduled_completion"
|
||||
pastIndicator
|
||||
time
|
||||
/>
|
||||
),
|
||||
},
|
||||
@@ -165,6 +166,7 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
|
||||
record={record}
|
||||
field="scheduled_delivery"
|
||||
pastIndicator
|
||||
time
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -20,9 +20,9 @@ export default function ProductionListDate({
|
||||
|
||||
const handleChange = (date) => {
|
||||
logImEXEvent("product_toggle_date", { field });
|
||||
if (date.isSame(record[field] && moment(record[field]))) {
|
||||
return;
|
||||
}
|
||||
// if (date.isSame(record[field] && moment(record[field]))) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
//e.stopPropagation();
|
||||
updateAlert({
|
||||
@@ -67,12 +67,14 @@ export default function ProductionListDate({
|
||||
value={(record[field] && moment(record[field])) || null}
|
||||
onChange={handleChange}
|
||||
format="MM/DD/YYYY"
|
||||
isDateOnly={!time}
|
||||
/>
|
||||
{time && (
|
||||
<TimePicker
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
value={(record[field] && moment(record[field])) || null}
|
||||
onChange={handleChange}
|
||||
minuteStep={15}
|
||||
format="hh:mm a"
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Descriptions, Drawer, Space } from "antd";
|
||||
import { Descriptions, Drawer, Space, PageHeader, Button } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -16,8 +16,25 @@ import JobEmployeeAssignments from "../job-employee-assignments/job-employee-ass
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
import ProductionRemoveButton from "../production-remove-button/production-remove-button.component";
|
||||
import JobAtChange from "../job-at-change/job-at-change.component";
|
||||
import { PrinterFilled } from "@ant-design/icons";
|
||||
|
||||
export default function ProductionListDetail({ jobs }) {
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setPrintCenterContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "printCenter" })),
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ProductionListDetail);
|
||||
|
||||
export function ProductionListDetail({ jobs, setPrintCenterContext }) {
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const history = useHistory();
|
||||
const { selected } = search;
|
||||
@@ -39,11 +56,29 @@ export default function ProductionListDetail({ jobs }) {
|
||||
return (
|
||||
<Drawer
|
||||
title={
|
||||
<Space>
|
||||
<span>{t("production.labels.jobdetail")}</span>
|
||||
<span>{theJob.ro_number}</span>
|
||||
<ProductionRemoveButton jobId={theJob.id} />
|
||||
</Space>
|
||||
<PageHeader
|
||||
title={theJob.ro_number}
|
||||
extra={
|
||||
<Space>
|
||||
<ProductionRemoveButton jobId={theJob.id} />{" "}
|
||||
<Button
|
||||
onClick={() => {
|
||||
setPrintCenterContext({
|
||||
actions: { refetch: refetch },
|
||||
context: {
|
||||
id: theJob.id,
|
||||
job: theJob,
|
||||
type: "job",
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<PrinterFilled />
|
||||
{t("jobs.actions.printCenter")}
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
}
|
||||
placement="right"
|
||||
width={"33%"}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -101,7 +101,7 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.dailyactual")}
|
||||
value={values.todayBody}
|
||||
value={values.todayBody.toFixed(1)}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
@@ -116,7 +116,7 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.weeklyactual")}
|
||||
value={values.weeklyBody}
|
||||
value={values.weeklyBody.toFixed(1)}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
@@ -140,7 +140,7 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.todateactual")}
|
||||
value={values.toDateBody}
|
||||
value={values.toDateBody.toFixed(1)}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -152,7 +152,7 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic value={values.todayPaint} />
|
||||
<Statistic value={values.todayPaint.toFixed(1)} />
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
@@ -163,7 +163,7 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic value={values.weeklyPaint} />
|
||||
<Statistic value={values.weeklyPaint.toFixed(1)} />
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
@@ -182,7 +182,7 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic value={values.toDatePaint} />
|
||||
<Statistic value={values.toDatePaint.toFixed(1)} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
|
||||
@@ -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" })}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -76,6 +76,21 @@ export function TimeTicketList({
|
||||
state.sortedInfo.columnKey === "employee" && state.sortedInfo.order,
|
||||
render: (text, record) =>
|
||||
`${record.employee.first_name} ${record.employee.last_name}`,
|
||||
filters:
|
||||
timetickets
|
||||
.map((l) => l.employeeid)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: (() => {
|
||||
const emp = bodyshop.employees.find((e) => e.id === s);
|
||||
|
||||
return `${emp.first_name} ${emp.last_name}`;
|
||||
})(), //
|
||||
value: [s],
|
||||
};
|
||||
}) || [],
|
||||
onFilter: (value, record) => value.includes(record.employeeid),
|
||||
},
|
||||
{
|
||||
title: t("timetickets.fields.cost_center"),
|
||||
|
||||
@@ -159,8 +159,10 @@ export function TimeTicketModalComponent({
|
||||
name="flat_rate"
|
||||
label={t("timetickets.fields.flat_rate")}
|
||||
valuePropName="checked"
|
||||
noStyle
|
||||
style={{ display: "none" }}
|
||||
>
|
||||
<Switch />
|
||||
<Switch style={{ display: "none" }} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
|
||||
@@ -212,6 +214,7 @@ export function TimeTicketModalComponent({
|
||||
<>
|
||||
<Form.Item label={t("timetickets.fields.clockon")} name="clockon">
|
||||
<FormDateTimePicker
|
||||
minuteStep={5}
|
||||
disabled={
|
||||
!HasRbacAccess({
|
||||
bodyshop,
|
||||
@@ -245,6 +248,7 @@ export function TimeTicketModalComponent({
|
||||
]}
|
||||
>
|
||||
<FormDateTimePicker
|
||||
minuteStep={5}
|
||||
disabled={
|
||||
!HasRbacAccess({
|
||||
bodyshop,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Button } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React, { useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import moment from "moment";
|
||||
const PayrollTemplate = TemplateList("special").exported_payroll;
|
||||
export default function TimeTicketsPayrollTable() {
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const { start, end } = searchParams;
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleClick = async () => {
|
||||
setLoading(true);
|
||||
|
||||
await GenerateDocument(
|
||||
{
|
||||
name: PayrollTemplate.key,
|
||||
variables: {
|
||||
start: start
|
||||
? start
|
||||
: moment().startOf("week").subtract(7, "days").format("YYYY-MM-DD"),
|
||||
end: end ? end : moment().endOf("week").format("YYYY-MM-DD"),
|
||||
},
|
||||
},
|
||||
{},
|
||||
"x"
|
||||
);
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Button loading={loading} onClick={handleClick}>
|
||||
{t("printcenter.payments.exported_payroll")}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
))
|
||||
|
||||
@@ -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")} />
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -89,6 +89,9 @@ export const QUERY_ALL_CC = gql`
|
||||
job {
|
||||
id
|
||||
ro_number
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,8 @@ export const GET_LINE_TICKET_BY_PK = gql`
|
||||
employeeid
|
||||
memo
|
||||
flat_rate
|
||||
clockon
|
||||
clockoff
|
||||
employee {
|
||||
id
|
||||
first_name
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ export const QUERY_ALL_VENDORS_FOR_ORDER = gql`
|
||||
discount
|
||||
email
|
||||
active
|
||||
phone
|
||||
}
|
||||
jobs(where: { id: { _eq: $jobId } }) {
|
||||
v_make_desc
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Col, Row } from "antd";
|
||||
import { Col, Row, Space } from "antd";
|
||||
import moment from "moment";
|
||||
import queryString from "query-string";
|
||||
import React, { useEffect } from "react";
|
||||
@@ -11,6 +11,7 @@ import AlertComponent from "../../components/alert/alert.component";
|
||||
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||
import TimeTicketsDatesSelector from "../../components/ticket-tickets-dates-selector/time-tickets-dates-selector.component";
|
||||
import TimeTicketList from "../../components/time-ticket-list/time-ticket-list.component";
|
||||
import TimeTicketsPayrollTable from "../../components/time-tickets-payroll-table/time-tickets-payroll-table.component";
|
||||
import TimeTicketsSummaryEmployees from "../../components/time-tickets-summary-employees/time-tickets-summary-employees.component";
|
||||
import { QUERY_TIME_TICKETS_IN_RANGE } from "../../graphql/timetickets.queries";
|
||||
import {
|
||||
@@ -68,7 +69,12 @@ export function TimeTicketsContainer({
|
||||
<TimeTicketList
|
||||
loading={loading}
|
||||
timetickets={data ? data.timetickets : []}
|
||||
extra={<TimeTicketsDatesSelector />}
|
||||
extra={
|
||||
<Space wrap>
|
||||
<TimeTicketsPayrollTable />
|
||||
<TimeTicketsDatesSelector />
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"actions": {
|
||||
"block": "Block Day",
|
||||
"calculate": "Calculate SMART Dates",
|
||||
"cancel": "Cancel",
|
||||
"cancel": "Cancel Appointment",
|
||||
"intake": "Intake",
|
||||
"new": "New Appointment",
|
||||
"preview": "Preview",
|
||||
@@ -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",
|
||||
@@ -1490,6 +1499,7 @@
|
||||
"cost_Additional": "Cost - Additional",
|
||||
"cost_labor": "Cost - Labor",
|
||||
"cost_parts": "Cost - Parts",
|
||||
"cost_sublet": "Cost - Sublet",
|
||||
"costs": "Costs",
|
||||
"create": {
|
||||
"jobinfo": "Job Info",
|
||||
@@ -1589,7 +1599,8 @@
|
||||
"rosaletotal": "RO Parts Total",
|
||||
"sale_additional": "Sales - Additional",
|
||||
"sale_labor": "Sales - Labor",
|
||||
"sale_parts": "Sales - Parts & Sublet",
|
||||
"sale_parts": "Sales - Parts",
|
||||
"sale_sublet": "Sales - Sublet",
|
||||
"sales": "Sales",
|
||||
"savebeforeconversion": "You have unsaved changes on the job. Please save them before converting it. ",
|
||||
"scheduledinchange": "The scheduled in is based off the latest appointment. To change this date, please schedule or reschedule the job. ",
|
||||
@@ -1600,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",
|
||||
@@ -2066,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",
|
||||
@@ -2117,7 +2130,8 @@
|
||||
"title": "Print Center"
|
||||
},
|
||||
"payments": {
|
||||
"ca_bc_etf_table": "ICBC ETF Table"
|
||||
"ca_bc_etf_table": "ICBC ETF Table",
|
||||
"exported_payroll": "Payroll Table"
|
||||
},
|
||||
"subjects": {
|
||||
"jobs": {
|
||||
@@ -2277,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",
|
||||
@@ -2575,6 +2590,7 @@
|
||||
},
|
||||
"labels": {
|
||||
"fromvehicle": "Historical Vehicle Record",
|
||||
"novehinfo": "No Vehicle Information",
|
||||
"relatedjobs": "Related Jobs",
|
||||
"updatevehicle": "Update Vehicle Information"
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
@@ -1490,6 +1499,7 @@
|
||||
"cost_Additional": "",
|
||||
"cost_labor": "",
|
||||
"cost_parts": "",
|
||||
"cost_sublet": "",
|
||||
"costs": "",
|
||||
"create": {
|
||||
"jobinfo": "",
|
||||
@@ -1590,6 +1600,7 @@
|
||||
"sale_additional": "",
|
||||
"sale_labor": "",
|
||||
"sale_parts": "",
|
||||
"sale_sublet": "",
|
||||
"sales": "",
|
||||
"savebeforeconversion": "",
|
||||
"scheduledinchange": "",
|
||||
@@ -1600,6 +1611,7 @@
|
||||
"supplementnote": "",
|
||||
"suspended": "",
|
||||
"suspense": "",
|
||||
"threshhold": "",
|
||||
"total_cost": "",
|
||||
"total_cust_payable": "",
|
||||
"total_repairs": "",
|
||||
@@ -2066,6 +2078,7 @@
|
||||
"labels": "",
|
||||
"position": ""
|
||||
},
|
||||
"mechanical_authorization": "",
|
||||
"mpi_animal_checklist": "",
|
||||
"mpi_eglass_auth": "",
|
||||
"mpi_final_acct_sheet": "",
|
||||
@@ -2117,7 +2130,8 @@
|
||||
"title": ""
|
||||
},
|
||||
"payments": {
|
||||
"ca_bc_etf_table": ""
|
||||
"ca_bc_etf_table": "",
|
||||
"exported_payroll": ""
|
||||
},
|
||||
"subjects": {
|
||||
"jobs": {
|
||||
@@ -2277,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": "",
|
||||
@@ -2575,6 +2590,7 @@
|
||||
},
|
||||
"labels": {
|
||||
"fromvehicle": "",
|
||||
"novehinfo": "",
|
||||
"relatedjobs": "",
|
||||
"updatevehicle": ""
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
@@ -1490,6 +1499,7 @@
|
||||
"cost_Additional": "",
|
||||
"cost_labor": "",
|
||||
"cost_parts": "",
|
||||
"cost_sublet": "",
|
||||
"costs": "",
|
||||
"create": {
|
||||
"jobinfo": "",
|
||||
@@ -1590,6 +1600,7 @@
|
||||
"sale_additional": "",
|
||||
"sale_labor": "",
|
||||
"sale_parts": "",
|
||||
"sale_sublet": "",
|
||||
"sales": "",
|
||||
"savebeforeconversion": "",
|
||||
"scheduledinchange": "",
|
||||
@@ -1600,6 +1611,7 @@
|
||||
"supplementnote": "",
|
||||
"suspended": "",
|
||||
"suspense": "",
|
||||
"threshhold": "",
|
||||
"total_cost": "",
|
||||
"total_cust_payable": "",
|
||||
"total_repairs": "",
|
||||
@@ -2066,6 +2078,7 @@
|
||||
"labels": "",
|
||||
"position": ""
|
||||
},
|
||||
"mechanical_authorization": "",
|
||||
"mpi_animal_checklist": "",
|
||||
"mpi_eglass_auth": "",
|
||||
"mpi_final_acct_sheet": "",
|
||||
@@ -2117,7 +2130,8 @@
|
||||
"title": ""
|
||||
},
|
||||
"payments": {
|
||||
"ca_bc_etf_table": ""
|
||||
"ca_bc_etf_table": "",
|
||||
"exported_payroll": ""
|
||||
},
|
||||
"subjects": {
|
||||
"jobs": {
|
||||
@@ -2277,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": "",
|
||||
@@ -2575,6 +2590,7 @@
|
||||
},
|
||||
"labels": {
|
||||
"fromvehicle": "",
|
||||
"novehinfo": "",
|
||||
"relatedjobs": "",
|
||||
"updatevehicle": ""
|
||||
},
|
||||
|
||||
@@ -17,19 +17,42 @@ const Templates = TemplateList();
|
||||
export default async function RenderTemplate(
|
||||
templateObject,
|
||||
bodyshop,
|
||||
renderAsHtml = false
|
||||
renderAsHtml = false,
|
||||
renderAsExcel = false
|
||||
) {
|
||||
//Query assets that match the template name. Must be in format <<templateName>>.query
|
||||
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: {
|
||||
...contextData,
|
||||
@@ -37,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(),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -119,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: {
|
||||
@@ -182,6 +223,9 @@ export const GenerateDocument = async (
|
||||
template,
|
||||
})
|
||||
);
|
||||
} else if (sendType === "x") {
|
||||
console.log("excel");
|
||||
await RenderTemplate(template, bodyshop, false, true);
|
||||
} else {
|
||||
await RenderTemplate(template, bodyshop);
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
@@ -1635,6 +1645,13 @@ export const TemplateList = (type, context) => {
|
||||
key: "ca_bc_etf_table",
|
||||
disabled: false,
|
||||
},
|
||||
exported_payroll: {
|
||||
title: i18n.t("printcenter.payments.exported_payroll"),
|
||||
description: "Est Detail",
|
||||
subject: i18n.t("printcenter.payments.exported_payroll"),
|
||||
key: "exported_payroll",
|
||||
disabled: false,
|
||||
},
|
||||
production_by_technician_one: {
|
||||
title: i18n.t(
|
||||
"reportcenter.templates.production_by_technician_one"
|
||||
@@ -1657,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,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."jobs" add column "materials" jsonb
|
||||
not null default jsonb_build_object();
|
||||
@@ -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;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."jobs" add column "towin_date" timestamptz
|
||||
null;
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."jobs" rename column "date_towin" to "towin_date";
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."jobs" rename column "towin_date" to "date_towin";
|
||||
@@ -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;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."jobs" add column "date_rentalresp" timestamptz
|
||||
null;
|
||||
@@ -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();
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."bodyshops" add column "ss_configuration" jsonb
|
||||
null default jsonb_build_object();
|
||||
@@ -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();
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."bodyshops" add column "md_from_emails" jsonb
|
||||
null default jsonb_build_array();
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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] } } : {}),
|
||||
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -31,8 +31,8 @@ async function JobCosting(req, res) {
|
||||
res.status(200).json(ret);
|
||||
} catch (error) {
|
||||
logger.log("job-costing-error", "ERROR", req.user.email, jobid, {
|
||||
jobid,
|
||||
error,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
});
|
||||
|
||||
res.status(400).send(JSON.stringify(error));
|
||||
@@ -63,10 +63,12 @@ async function JobCostingMulti(req, res) {
|
||||
totalLaborSales: Dinero({ amount: 0 }),
|
||||
totalPartsSales: Dinero({ amount: 0 }),
|
||||
totalAdditionalSales: Dinero({ amount: 0 }),
|
||||
totalSubletSales: Dinero({ amount: 0 }),
|
||||
totalSales: Dinero({ amount: 0 }),
|
||||
totalLaborCost: Dinero({ amount: 0 }),
|
||||
totalPartsCost: Dinero({ amount: 0 }),
|
||||
totalAdditionalCost: Dinero({ amount: 0 }),
|
||||
totalSubletCost: Dinero({ amount: 0 }),
|
||||
totalCost: Dinero({ amount: 0 }),
|
||||
gpdollars: Dinero({ amount: 0 }),
|
||||
gppercent: null,
|
||||
@@ -74,12 +76,15 @@ async function JobCostingMulti(req, res) {
|
||||
totalLaborGp: Dinero({ amount: 0 }),
|
||||
totalPartsGp: Dinero({ amount: 0 }),
|
||||
totalAdditionalGp: Dinero({ amount: 0 }),
|
||||
totalSubletGp: Dinero({ amount: 0 }),
|
||||
totalLaborGppercent: null,
|
||||
totalLaborGppercentFormatted: null,
|
||||
totalPartsGppercent: null,
|
||||
totalPartsGppercentFormatted: null,
|
||||
totalAdditionalGppercent: null,
|
||||
totalAdditionalGppercentFormatted: null,
|
||||
totalSubletGppercent: null,
|
||||
totalSubletGppercentFormatted: null,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -110,6 +115,9 @@ async function JobCostingMulti(req, res) {
|
||||
sale_additional_dinero: multiSummary.costCenterData[
|
||||
CostCenterIndex
|
||||
].sale_additional_dinero.add(c.sale_additional_dinero),
|
||||
sale_sublet_dinero: multiSummary.costCenterData[
|
||||
CostCenterIndex
|
||||
].sale_sublet_dinero.add(c.sale_sublet_dinero),
|
||||
cost_labor_dinero: multiSummary.costCenterData[
|
||||
CostCenterIndex
|
||||
].cost_labor_dinero.add(c.cost_labor_dinero),
|
||||
@@ -119,6 +127,9 @@ async function JobCostingMulti(req, res) {
|
||||
cost_additional_dinero: multiSummary.costCenterData[
|
||||
CostCenterIndex
|
||||
].cost_additional_dinero.add(c.cost_additional_dinero),
|
||||
cost_sublet_dinero: multiSummary.costCenterData[
|
||||
CostCenterIndex
|
||||
].cost_sublet_dinero.add(c.cost_sublet_dinero),
|
||||
gpdollars_dinero: multiSummary.costCenterData[
|
||||
CostCenterIndex
|
||||
].gpdollars_dinero.add(c.gpdollars_dinero),
|
||||
@@ -144,6 +155,10 @@ async function JobCostingMulti(req, res) {
|
||||
multiSummary.summaryData.totalAdditionalSales.add(
|
||||
costingData.summaryData.totalAdditionalSales
|
||||
);
|
||||
multiSummary.summaryData.totalSubletSales =
|
||||
multiSummary.summaryData.totalSubletSales.add(
|
||||
costingData.summaryData.totalSubletSales
|
||||
);
|
||||
multiSummary.summaryData.totalSales =
|
||||
multiSummary.summaryData.totalSales.add(
|
||||
costingData.summaryData.totalSales
|
||||
@@ -164,6 +179,10 @@ async function JobCostingMulti(req, res) {
|
||||
multiSummary.summaryData.totalAdditionalCost.add(
|
||||
costingData.summaryData.totalAdditionalCost
|
||||
);
|
||||
multiSummary.summaryData.totalSubletCost =
|
||||
multiSummary.summaryData.totalSubletCost.add(
|
||||
costingData.summaryData.totalSubletCost
|
||||
);
|
||||
multiSummary.summaryData.totalCost =
|
||||
multiSummary.summaryData.totalCost.add(
|
||||
costingData.summaryData.totalCost
|
||||
@@ -185,6 +204,10 @@ async function JobCostingMulti(req, res) {
|
||||
multiSummary.summaryData.totalAdditionalGp.add(
|
||||
costingData.summaryData.totalAdditionalGp
|
||||
);
|
||||
multiSummary.summaryData.totalSubletGp =
|
||||
multiSummary.summaryData.totalSubletGp.add(
|
||||
costingData.summaryData.totalSubletGp
|
||||
);
|
||||
|
||||
//Take the summary data & add it to total summary data.
|
||||
});
|
||||
@@ -219,6 +242,16 @@ async function JobCostingMulti(req, res) {
|
||||
multiSummary.summaryData.totalAdditionalGppercentFormatted =
|
||||
formatGpPercent(multiSummary.summaryData.totalAdditionalGppercent);
|
||||
|
||||
multiSummary.summaryData.totalSubletGppercent = (
|
||||
(multiSummary.summaryData.totalSubletGp.getAmount() /
|
||||
multiSummary.summaryData.totalSubletSales.getAmount()) *
|
||||
100
|
||||
).toFixed(2);
|
||||
|
||||
multiSummary.summaryData.totalSubletGppercentFormatted = formatGpPercent(
|
||||
multiSummary.summaryData.totalSubletGppercent
|
||||
);
|
||||
|
||||
multiSummary.summaryData.gppercent = (
|
||||
(multiSummary.summaryData.gpdollars.getAmount() /
|
||||
multiSummary.summaryData.totalSales.getAmount()) *
|
||||
@@ -236,11 +269,13 @@ async function JobCostingMulti(req, res) {
|
||||
sale_parts: c.sale_parts_dinero && c.sale_parts_dinero.toFormat(),
|
||||
sale_additional:
|
||||
c.sale_additional_dinero && c.sale_additional_dinero.toFormat(),
|
||||
sale_sublet: c.sale_sublet_dinero && c.sale_sublet_dinero.toFormat(),
|
||||
sales: c.sales_dinero.toFormat(),
|
||||
cost_parts: c.cost_parts_dinero && c.cost_parts_dinero.toFormat(),
|
||||
cost_labor: c.cost_labor_dinero && c.cost_labor_dinero.toFormat(),
|
||||
cost_additional:
|
||||
c.cost_additional_dinero && c.cost_additional_dinero.toFormat(),
|
||||
cost_sublet: c.cost_sublet_dinero && c.cost_sublet_dinero.toFormat(),
|
||||
costs: c.costs_dinero.toFormat(),
|
||||
gpdollars: c.gpdollars_dinero.toFormat(),
|
||||
gppercent: formatGpPercent(
|
||||
@@ -261,10 +296,10 @@ async function JobCostingMulti(req, res) {
|
||||
});
|
||||
} catch (error) {
|
||||
logger.log("job-costing-multi-error", "ERROR", req.user.email, [jobids], {
|
||||
jobids,
|
||||
error,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
});
|
||||
res.status(400).send(JSON.stringify(error));
|
||||
res.status(400).send(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,7 +352,12 @@ function GenerateCostingData(job) {
|
||||
}
|
||||
}
|
||||
|
||||
if (val.part_type && val.part_type !== "PAE") {
|
||||
if (
|
||||
val.part_type &&
|
||||
val.part_type !== "PAE" &&
|
||||
val.part_type !== "PAS" &&
|
||||
val.part_type !== "PASL"
|
||||
) {
|
||||
const partsProfitCenter =
|
||||
val.profitcenter_part || defaultProfits[val.part_type] || "?";
|
||||
|
||||
@@ -349,6 +389,42 @@ function GenerateCostingData(job) {
|
||||
acc.parts[partsProfitCenter] =
|
||||
acc.parts[partsProfitCenter].add(partsAmount);
|
||||
}
|
||||
if (
|
||||
val.part_type &&
|
||||
val.part_type !== "PAE" &&
|
||||
(val.part_type === "PAS" || val.part_type === "PASL")
|
||||
) {
|
||||
const partsProfitCenter =
|
||||
val.profitcenter_part || defaultProfits[val.part_type] || "?";
|
||||
|
||||
if (partsProfitCenter === "?")
|
||||
console.log("Unknown type", val.line_desc, val.part_type);
|
||||
|
||||
if (!partsProfitCenter)
|
||||
console.log(
|
||||
"Unknown cost/profit center mapping for sublet.",
|
||||
val.line_desc,
|
||||
val.part_type
|
||||
);
|
||||
const partsAmount = Dinero({
|
||||
amount: Math.round((val.act_price || 0) * 100),
|
||||
})
|
||||
.multiply(val.part_qty || 1)
|
||||
.add(
|
||||
val.prt_dsmk_m && val.prt_dsmk_m !== 0
|
||||
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
|
||||
: Dinero({
|
||||
amount: Math.round(val.act_price * 100),
|
||||
})
|
||||
.multiply(val.part_qty || 0)
|
||||
.percentage(Math.abs(val.prt_dsmk_p || 0))
|
||||
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
|
||||
);
|
||||
if (!acc.sublet[partsProfitCenter])
|
||||
acc.sublet[partsProfitCenter] = Dinero();
|
||||
acc.sublet[partsProfitCenter] =
|
||||
acc.sublet[partsProfitCenter].add(partsAmount);
|
||||
}
|
||||
|
||||
//To deal with additional costs.
|
||||
if (!val.part_type && !val.mod_lbr_ty) {
|
||||
@@ -386,7 +462,7 @@ function GenerateCostingData(job) {
|
||||
|
||||
return acc;
|
||||
},
|
||||
{ parts: {}, labor: {}, additional: {} }
|
||||
{ parts: {}, labor: {}, additional: {}, sublet: {} }
|
||||
);
|
||||
|
||||
if (!hasMapaLine) {
|
||||
@@ -441,6 +517,12 @@ function GenerateCostingData(job) {
|
||||
.multiply(bill_val.is_credit_memo ? -1 : 1)
|
||||
);
|
||||
} else {
|
||||
const isSubletCostCenter =
|
||||
line_val.cost_center ===
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.PAS ||
|
||||
line_val.cost_center ===
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.PASL;
|
||||
|
||||
const isAdditionalCostCenter =
|
||||
// line_val.cost_center ===
|
||||
// job.bodyshop.md_responsibility_centers.defaults.costs.PAS ||
|
||||
@@ -465,6 +547,19 @@ function GenerateCostingData(job) {
|
||||
.multiply(line_val.quantity)
|
||||
.multiply(bill_val.is_credit_memo ? -1 : 1)
|
||||
);
|
||||
} else if (isSubletCostCenter) {
|
||||
if (!bill_acc.subletCosts[line_val.cost_center])
|
||||
bill_acc.subletCosts[line_val.cost_center] = Dinero();
|
||||
|
||||
bill_acc.subletCosts[line_val.cost_center] = bill_acc.subletCosts[
|
||||
line_val.cost_center
|
||||
].add(
|
||||
Dinero({
|
||||
amount: Math.round((line_val.actual_cost || 0) * 100),
|
||||
})
|
||||
.multiply(line_val.quantity)
|
||||
.multiply(bill_val.is_credit_memo ? -1 : 1)
|
||||
);
|
||||
} else {
|
||||
if (!bill_acc[line_val.cost_center])
|
||||
bill_acc[line_val.cost_center] = Dinero();
|
||||
@@ -483,7 +578,7 @@ function GenerateCostingData(job) {
|
||||
});
|
||||
return bill_acc;
|
||||
},
|
||||
{ additionalCosts: {} }
|
||||
{ additionalCosts: {}, subletCosts: {} }
|
||||
);
|
||||
|
||||
//If the hourly rates for job costing are set, add them in.
|
||||
@@ -583,14 +678,17 @@ function GenerateCostingData(job) {
|
||||
totalLaborSales: Dinero({ amount: 0 }),
|
||||
totalPartsSales: Dinero({ amount: 0 }),
|
||||
totalAdditionalSales: Dinero({ amount: 0 }),
|
||||
totalSubletSales: Dinero({ amount: 0 }),
|
||||
totalSales: Dinero({ amount: 0 }),
|
||||
totalLaborCost: Dinero({ amount: 0 }),
|
||||
totalPartsCost: Dinero({ amount: 0 }),
|
||||
totalAdditionalCost: Dinero({ amount: 0 }),
|
||||
totalSubletCost: Dinero({ amount: 0 }),
|
||||
totalCost: Dinero({ amount: 0 }),
|
||||
totalLaborGp: Dinero({ amount: 0 }),
|
||||
totalPartsGp: Dinero({ amount: 0 }),
|
||||
totalAdditionalGp: Dinero({ amount: 0 }),
|
||||
totalSubletGp: Dinero({ amount: 0 }),
|
||||
gpdollars: Dinero({ amount: 0 }),
|
||||
totalLaborGppercent: null,
|
||||
totalLaborGppercentFormatted: null,
|
||||
@@ -598,6 +696,8 @@ function GenerateCostingData(job) {
|
||||
totalPartsGppercentFormatted: null,
|
||||
totalAdditionalGppercent: null,
|
||||
totalAdditionalGppercentFormatted: null,
|
||||
totalSubletGppercent: null,
|
||||
totalSubletGppercentFormatted: null,
|
||||
gppercent: null,
|
||||
gppercentFormatted: null,
|
||||
};
|
||||
@@ -610,14 +710,24 @@ function GenerateCostingData(job) {
|
||||
jobLineTotalsByProfitCenter.parts[ccVal] || Dinero({ amount: 0 });
|
||||
const sale_additional =
|
||||
jobLineTotalsByProfitCenter.additional[ccVal] || Dinero({ amount: 0 });
|
||||
const sale_sublet =
|
||||
jobLineTotalsByProfitCenter.sublet[ccVal] || Dinero({ amount: 0 });
|
||||
|
||||
const cost_labor = ticketTotalsByCostCenter[ccVal] || Dinero({ amount: 0 });
|
||||
const cost_parts = billTotalsByCostCenters[ccVal] || Dinero({ amount: 0 });
|
||||
const cost_additional =
|
||||
billTotalsByCostCenters.additionalCosts[ccVal] || Dinero({ amount: 0 });
|
||||
const cost_sublet =
|
||||
billTotalsByCostCenters.subletCosts[ccVal] || Dinero({ amount: 0 });
|
||||
|
||||
const costs = cost_labor.add(cost_parts).add(cost_additional);
|
||||
const totalSales = sale_labor.add(sale_parts).add(sale_additional);
|
||||
const costs = cost_labor
|
||||
.add(cost_parts)
|
||||
.add(cost_additional)
|
||||
.add(cost_sublet);
|
||||
const totalSales = sale_labor
|
||||
.add(sale_parts)
|
||||
.add(sale_additional)
|
||||
.add(sale_sublet);
|
||||
const gpdollars = totalSales.subtract(costs);
|
||||
const gppercent = (
|
||||
(gpdollars.getAmount() / totalSales.getAmount()) *
|
||||
@@ -629,11 +739,14 @@ function GenerateCostingData(job) {
|
||||
summaryData.totalPartsSales = summaryData.totalPartsSales.add(sale_parts);
|
||||
summaryData.totalAdditionalSales =
|
||||
summaryData.totalAdditionalSales.add(sale_additional);
|
||||
summaryData.totalSubletSales =
|
||||
summaryData.totalSubletSales.add(sale_sublet);
|
||||
summaryData.totalSales = summaryData.totalSales.add(totalSales);
|
||||
summaryData.totalLaborCost = summaryData.totalLaborCost.add(cost_labor);
|
||||
summaryData.totalPartsCost = summaryData.totalPartsCost.add(cost_parts);
|
||||
summaryData.totalAdditionalCost =
|
||||
summaryData.totalAdditionalCost.add(cost_additional);
|
||||
summaryData.totalSubletCost = summaryData.totalSubletCost.add(cost_sublet);
|
||||
summaryData.totalCost = summaryData.totalCost.add(costs);
|
||||
|
||||
return {
|
||||
@@ -645,6 +758,8 @@ function GenerateCostingData(job) {
|
||||
sale_parts_dinero: sale_parts,
|
||||
sale_additional: sale_additional && sale_additional.toFormat(),
|
||||
sale_additional_dinero: sale_additional,
|
||||
sale_sublet: sale_sublet && sale_sublet.toFormat(),
|
||||
sale_sublet_dinero: sale_sublet,
|
||||
sales: totalSales.toFormat(),
|
||||
sales_dinero: totalSales,
|
||||
cost_parts: cost_parts && cost_parts.toFormat(),
|
||||
@@ -653,6 +768,8 @@ function GenerateCostingData(job) {
|
||||
cost_labor_dinero: cost_labor,
|
||||
cost_additional: cost_additional && cost_additional.toFormat(),
|
||||
cost_additional_dinero: cost_additional,
|
||||
cost_sublet: cost_sublet && cost_sublet.toFormat(),
|
||||
cost_sublet_dinero: cost_sublet,
|
||||
costs: costs.toFormat(),
|
||||
costs_dinero: costs,
|
||||
gpdollars_dinero: gpdollars,
|
||||
@@ -675,12 +792,20 @@ function GenerateCostingData(job) {
|
||||
sale_labor_dinero: Dinero(),
|
||||
sale_parts: Dinero().toFormat(),
|
||||
sale_parts_dinero: Dinero(),
|
||||
sale_additional: Dinero(),
|
||||
sale_additional_dinero: Dinero(),
|
||||
sale_sublet: Dinero(),
|
||||
sale_sublet_dinero: Dinero(),
|
||||
sales: Dinero().toFormat(),
|
||||
sales_dinero: Dinero(),
|
||||
cost_parts: Dinero().toFormat(),
|
||||
cost_parts_dinero: Dinero(),
|
||||
cost_labor: Adjustment.toFormat(),
|
||||
cost_labor_dinero: Adjustment,
|
||||
cost_additional: Dinero(),
|
||||
cost_additional_dinero: Dinero(),
|
||||
cost_sublet: Dinero(),
|
||||
cost_sublet_dinero: Dinero(),
|
||||
costs: Adjustment.toFormat(),
|
||||
costs_dinero: Adjustment,
|
||||
gpdollars_dinero: Dinero(),
|
||||
@@ -725,6 +850,17 @@ function GenerateCostingData(job) {
|
||||
summaryData.totalAdditionalGppercentFormatted = formatGpPercent(
|
||||
summaryData.totalAdditionalGppercent
|
||||
);
|
||||
summaryData.totalSubletGp = summaryData.totalSubletSales.subtract(
|
||||
summaryData.totalSubletCost
|
||||
);
|
||||
summaryData.totalSubletGppercent = (
|
||||
(summaryData.totalSubletGp.getAmount() /
|
||||
summaryData.totalSubletSales.getAmount()) *
|
||||
100
|
||||
).toFixed(2);
|
||||
summaryData.totalSubletGppercentFormatted = formatGpPercent(
|
||||
summaryData.totalSubletGppercent
|
||||
);
|
||||
|
||||
summaryData.gpdollars = summaryData.totalSales.subtract(
|
||||
summaryData.totalCost
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
@@ -442,8 +468,8 @@ function CalculateAdditional(job) {
|
||||
ret.total = ret.additionalCosts
|
||||
.add(ret.adjustments) //IO-813 Adjustment takes care of GST & PST at labor rate.
|
||||
.add(ret.towing)
|
||||
.add(ret.storage)
|
||||
.add(ret.pvrt);
|
||||
.add(ret.storage);
|
||||
//.add(ret.pvrt);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -453,6 +479,7 @@ function CalculateTaxesTotals(job, otherTotals) {
|
||||
.add(otherTotals.parts.sublets.subtotal)
|
||||
.add(otherTotals.rates.subtotal) //No longer using just rates subtotal to include mapa/mash.
|
||||
.add(otherTotals.additional.total);
|
||||
|
||||
// .add(Dinero({ amount: (job.towing_payable || 0) * 100 }))
|
||||
// .add(Dinero({ amount: (job.storage_payable || 0) * 100 }));
|
||||
|
||||
@@ -522,7 +549,13 @@ function CalculateTaxesTotals(job, otherTotals) {
|
||||
|
||||
let ret = {
|
||||
subtotal: subtotal,
|
||||
federal_tax: subtotal.percentage((job.federal_tax_rate || 0) * 100),
|
||||
federal_tax: subtotal
|
||||
.percentage((job.federal_tax_rate || 0) * 100)
|
||||
.add(
|
||||
otherTotals.additional.pvrt.percentage(
|
||||
(job.federal_tax_rate || 0) * 100
|
||||
)
|
||||
),
|
||||
statePartsTax,
|
||||
state_tax: statePartsTax
|
||||
.add(
|
||||
@@ -540,12 +573,14 @@ function CalculateTaxesTotals(job, otherTotals) {
|
||||
otherTotals.additional.storage.percentage((job.tax_str_rt || 0) * 100)
|
||||
)
|
||||
.add(additionalItemsTax),
|
||||
// .add(otherTotals.additional.pvrt),
|
||||
local_tax: subtotal.percentage((job.local_tax_rate || 0) * 100),
|
||||
};
|
||||
ret.total_repairs = ret.subtotal
|
||||
.add(ret.federal_tax)
|
||||
.add(ret.state_tax)
|
||||
.add(ret.local_tax);
|
||||
.add(ret.local_tax)
|
||||
.add(otherTotals.additional.pvrt);
|
||||
|
||||
ret.custPayable = {
|
||||
deductible: Dinero({ amount: Math.round((job.ded_amt || 0) * 100) }) || 0,
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -9,30 +9,84 @@ const axios = require("axios");
|
||||
const client = require("../graphql-client/graphql-client").client;
|
||||
const emailer = require("../email/sendemail");
|
||||
const logger = require("../utils/logger");
|
||||
|
||||
const moment = require("moment-timezone");
|
||||
exports.taskHandler = async (req, res) => {
|
||||
try {
|
||||
const { bodyshopid, query, variables, text, to, subject } = req.body;
|
||||
const { bodyshopid, query, variables, text, to, subject, timezone } =
|
||||
req.body;
|
||||
//Run the query
|
||||
|
||||
//Check the variables to see if they are an object.
|
||||
Object.keys(variables).forEach((key) => {
|
||||
if (typeof variables[key] === "object") {
|
||||
if (variables[key].function) {
|
||||
variables[key] = functionMapper(variables[key].function, timezone);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const response = await client.request(query, variables);
|
||||
//Massage the data
|
||||
//Send the email
|
||||
const rootElement = response[Object.keys(response)[0]]; //This element shoudl always be an array.
|
||||
let converter = require("json-2-csv");
|
||||
converter.json2csv(rootElement, (err, csv) => {
|
||||
if (err) {
|
||||
res.status(500).json(err);
|
||||
}
|
||||
converter.json2csv(
|
||||
rootElement,
|
||||
(err, csv) => {
|
||||
if (err) {
|
||||
res.status(500).json(err);
|
||||
}
|
||||
|
||||
emailer.sendTaskEmail({
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
attachments: [{ filename: "query.csv", content: csv }],
|
||||
});
|
||||
res.status(200).send(csv);
|
||||
});
|
||||
emailer.sendTaskEmail({
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
attachments: [{ filename: "query.csv", content: csv }],
|
||||
});
|
||||
res.status(200).send(csv);
|
||||
},
|
||||
{ emptyFieldValue: "" }
|
||||
);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error });
|
||||
res.status(500).json({ error: error.message, stack: error.stackTrace });
|
||||
}
|
||||
};
|
||||
|
||||
const isoformat = "YYYY-MM-DD";
|
||||
function functionMapper(f, timezone) {
|
||||
switch (f) {
|
||||
case "date.today":
|
||||
return moment().tz(timezone).format(isoformat);
|
||||
case "date.now":
|
||||
return moment().tz(timezone);
|
||||
case "date.yesterday":
|
||||
return moment().tz(timezone).subtract(1, "day").format(isoformat);
|
||||
case "date.3daysago":
|
||||
return moment().tz(timezone).subtract(3, "days").format(isoformat);
|
||||
case "date.7daysago":
|
||||
return moment().tz(timezone).subtract(7, "days").format(isoformat);
|
||||
case "date.tomorrow":
|
||||
return moment().tz(timezone).add(1, "day").format(isoformat);
|
||||
case "date.3daysfromnow":
|
||||
return moment().tz(timezone).add(3, "days").format(isoformat);
|
||||
case "date.7daysfromnow":
|
||||
return moment().tz(timezone).add(7, "days").format(isoformat);
|
||||
case "date.yesterdaytz":
|
||||
return moment().tz(timezone).subtract(1, "day");
|
||||
case "date.3daysagotz":
|
||||
return moment().tz(timezone).subtract(3, "days");
|
||||
case "date.7daysagotz":
|
||||
return moment().tz(timezone).subtract(7, "days");
|
||||
case "date.tomorrowtz":
|
||||
return moment().tz(timezone).add(1, "day");
|
||||
case "date.3daysfromnowtz":
|
||||
return moment().tz(timezone).add(3, "days");
|
||||
case "date.7daysfromnowtz":
|
||||
return moment().tz(timezone).add(7, "days");
|
||||
|
||||
case "date.now":
|
||||
return moment().tz(timezone);
|
||||
default:
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user