Merged in release/2022-07-22 (pull request #541)

Release/2022 07 22
This commit is contained in:
Patrick Fic
2022-07-19 21:53:12 +00:00
39 changed files with 4249 additions and 3818 deletions

View File

@@ -11149,6 +11149,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>dateinpast</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>dlexpirebeforereturn</name>
<definition_loaded>false</definition_loaded>
@@ -12370,6 +12391,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>leasereturn</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>out</name>
<definition_loaded>false</definition_loaded>
@@ -14735,6 +14777,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>vacationadded</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>
@@ -32699,6 +32762,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>saving</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>selectexistingornew</name>
<definition_loaded>false</definition_loaded>
@@ -33103,6 +33187,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>tax_number</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>
@@ -41346,6 +41451,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>efficiencyoverperiod</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>jobs</name>
<definition_loaded>false</definition_loaded>

View File

@@ -142,3 +142,9 @@
}
}
}
//Update row highlighting on production board.
.ant-table-tbody > tr.ant-table-row:hover > td {
background: #eaeaea !important;
}

View File

@@ -0,0 +1,251 @@
import { useMutation, useQuery } from "@apollo/client";
import { Button, Form, PageHeader, Popconfirm, Space } from "antd";
import moment from "moment";
import queryString from "query-string";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import {
DELETE_BILL_LINE,
INSERT_NEW_BILL_LINES,
UPDATE_BILL_LINE
} from "../../graphql/bill-lines.queries";
import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import AlertComponent from "../alert/alert.component";
import BillFormContainer from "../bill-form/bill-form.container";
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
import BillReeportButtonComponent from "../bill-reexport-button/bill-reexport-button.component";
import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-gallery.container";
import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import BillDetailEditReturn from "./bill-detail-edit-return.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(BillDetailEditcontainer);
export function BillDetailEditcontainer({
setPartsOrderContext,
insertAuditTrail,
bodyshop,
}) {
const search = queryString.parse(useLocation().search);
const { t } = useTranslation();
const [form] = Form.useForm();
const [visible, setVisible] = useState(false);
const [updateLoading, setUpdateLoading] = useState(false);
const [update_bill] = useMutation(UPDATE_BILL);
const [insertBillLine] = useMutation(INSERT_NEW_BILL_LINES);
const [updateBillLine] = useMutation(UPDATE_BILL_LINE);
const [deleteBillLine] = useMutation(DELETE_BILL_LINE);
const { loading, error, data, refetch } = useQuery(QUERY_BILL_BY_PK, {
variables: { billid: search.billid },
skip: !!!search.billid,
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const handleSave = () => {
//It's got a previously deducted bill line!
if (
data.bills_by_pk.billlines.filter((b) => b.deductedfromlbr).length > 0 ||
form.getFieldValue("billlines").filter((b) => b.deductedfromlbr).length >
0
)
setVisible(true);
else {
form.submit();
}
};
const handleFinish = async (values) => {
setUpdateLoading(true);
//let adjustmentsToInsert = {};
const { billlines, upload, ...bill } = values;
const updates = [];
updates.push(
update_bill({
variables: { billId: search.billid, bill: bill },
})
);
billlines.forEach((l) => {
delete l.selected;
});
//Find bill lines that were deleted.
const deletedJobLines = [];
data.bills_by_pk.billlines.forEach((a) => {
const matchingRecord = billlines.find((b) => b.id === a.id);
if (!matchingRecord) {
deletedJobLines.push(a);
}
});
deletedJobLines.forEach((d) => {
updates.push(deleteBillLine({ variables: { id: d.id } }));
});
billlines.forEach((billline) => {
const { deductedfromlbr, inventories, jobline, ...il } = billline;
delete il.__typename;
if (il.id) {
updates.push(
updateBillLine({
variables: {
billLineId: il.id,
billLine: {
...il,
deductedfromlbr: deductedfromlbr,
joblineid: il.joblineid === "noline" ? null : il.joblineid,
},
},
})
);
} else {
//It's a new line, have to insert it.
updates.push(
insertBillLine({
variables: {
billLines: [
{
...il,
deductedfromlbr: deductedfromlbr,
billid: search.billid,
joblineid: il.joblineid === "noline" ? null : il.joblineid,
},
],
},
})
);
}
});
await Promise.all(updates);
insertAuditTrail({
jobid: bill.jobid,
billid: search.billid,
operation: AuditTrailMapping.billupdated(bill.invoice_number),
});
await refetch();
form.setFieldsValue(transformData(data));
form.resetFields();
setVisible(false);
setUpdateLoading(false);
};
if (error) return <AlertComponent message={error.message} type="error" />;
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
return (
<>
{loading && <LoadingSkeleton />}
{data && (
<>
<PageHeader
title={
data &&
`${data.bills_by_pk.invoice_number} - ${data.bills_by_pk.vendor.name}`
}
extra={
<Space>
<BillDetailEditReturn data={data} />
<Popconfirm
visible={visible}
onConfirm={() => form.submit()}
onCancel={() => setVisible(false)}
okButtonProps={{ loading: updateLoading }}
title={t("bills.labels.editadjwarning")}
>
<Button
htmlType="submit"
disabled={exported}
onClick={handleSave}
loading={updateLoading}
type="primary"
>
{t("general.actions.save")}
</Button>
</Popconfirm>
<BillReeportButtonComponent bill={data && data.bills_by_pk} />
<BillMarkExportedButton bill={data && data.bills_by_pk} />
</Space>
}
/>
<Form
form={form}
onFinish={handleFinish}
initialValues={transformData(data)}
layout="vertical"
>
<BillFormContainer form={form} billEdit disabled={exported} />
{bodyshop.uselocalmediaserver ? (
<JobsDocumentsLocalGallery
job={{ id: data ? data.bills_by_pk.jobid : null }}
invoice_number={data ? data.bills_by_pk.invoice_number : null}
vendorid={data ? data.bills_by_pk.vendorid : null}
/>
) : (
<JobDocumentsGallery
jobId={data ? data.bills_by_pk.jobid : null}
billId={search.billid}
documentsList={data ? data.bills_by_pk.documents : []}
billsCallback={refetch}
/>
)}
</Form>
</>
)}
</>
);
}
const transformData = (data) => {
return data
? {
...data.bills_by_pk,
billlines: data.bills_by_pk.billlines.map((i) => {
return {
...i,
joblineid: !!i.joblineid ? i.joblineid : "noline",
applicable_taxes: {
federal:
(i.applicable_taxes && i.applicable_taxes.federal) || false,
state: (i.applicable_taxes && i.applicable_taxes.state) || false,
local: (i.applicable_taxes && i.applicable_taxes.local) || false,
},
};
}),
date: data.bills_by_pk ? moment(data.bills_by_pk.date) : null,
}
: {};
};

View File

@@ -0,0 +1,172 @@
import { Button, Checkbox, Form, Modal } from "antd";
import queryString from "query-string";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(BillDetailEditReturn);
export function BillDetailEditReturn({
setPartsOrderContext,
insertAuditTrail,
bodyshop,
data,
disabled,
}) {
const search = queryString.parse(useLocation().search);
const history = useHistory();
const { t } = useTranslation();
const [form] = Form.useForm();
const [visible, setVisible] = useState(false);
const handleFinish = ({ billlines }) => {
const selectedLines = billlines.filter((l) => l.selected).map((l) => l.id);
setPartsOrderContext({
actions: {},
context: {
jobId: data.bills_by_pk.jobid,
vendorId: data.bills_by_pk.vendorid,
returnFromBill: data.bills_by_pk.id,
invoiceNumber: data.bills_by_pk.invoice_number,
linesToOrder: data.bills_by_pk.billlines
.filter((l) => selectedLines.includes(l.id))
.map((i) => {
return {
line_desc: i.line_desc,
// db_price: i.actual_price,
act_price: i.actual_price,
cost: i.actual_cost,
quantity: i.quantity,
joblineid: i.joblineid,
oem_partno: i.jobline && i.jobline.oem_partno,
part_type: i.jobline && i.jobline.part_type,
};
}),
isReturn: true,
},
});
delete search.billid;
history.push({ search: queryString.stringify(search) });
setVisible(false);
};
useEffect(() => {
if (visible === false) form.resetFields();
}, [visible, form]);
return (
<>
<Modal
visible={visible}
onCancel={() => setVisible(false)}
destroyOnClose
title={t("bills.actions.return")}
onOk={() => form.submit()}
>
<Form
initialValues={data && data.bills_by_pk}
onFinish={handleFinish}
form={form}
>
<Form.List name={["billlines"]}>
{(fields, { add, remove, move }) => {
return (
<table style={{ tableLayout: "auto", width: "100%" }}>
<thead>
<tr>
<td></td>
<td>{t("billlines.fields.line_desc")}</td>
<td>{t("billlines.fields.quantity")}</td>
<td>{t("billlines.fields.actual_price")}</td>
<td>{t("billlines.fields.actual_cost")}</td>
</tr>
</thead>
<tbody>
{fields.map((field, index) => (
<tr key={field.key}>
<td>
<Form.Item
// label={t("joblines.fields.selected")}
key={`${index}selected`}
name={[field.name, "selected"]}
valuePropName="checked"
>
<Checkbox />
</Form.Item>
</td>
<td>
<Form.Item
// label={t("joblines.fields.line_desc")}
key={`${index}line_desc`}
name={[field.name, "line_desc"]}
>
<ReadOnlyFormItemComponent />
</Form.Item>
</td>
<td>
<Form.Item
// label={t("joblines.fields.quantity")}
key={`${index}quantity`}
name={[field.name, "quantity"]}
>
<ReadOnlyFormItemComponent />
</Form.Item>
</td>
<td>
<Form.Item
// label={t("joblines.fields.actual_price")}
key={`${index}actual_price`}
name={[field.name, "actual_price"]}
>
<ReadOnlyFormItemComponent type="currency" />
</Form.Item>
</td>
<td>
<Form.Item
// label={t("joblines.fields.actual_cost")}
key={`${index}actual_cost`}
name={[field.name, "actual_cost"]}
>
<ReadOnlyFormItemComponent type="currency" />
</Form.Item>
</td>
</tr>
))}
</tbody>
</table>
);
}}
</Form.List>
</Form>
</Modal>
<Button
disabled={data.bills_by_pk.is_credit_memo || disabled}
onClick={() => {
setVisible(true);
}}
>
{t("bills.actions.return")}
</Button>
</>
);
}

View File

@@ -1,68 +1,12 @@
import { useMutation, useQuery } from "@apollo/client";
import {
Button,
Drawer,
Form,
Grid,
PageHeader,
Popconfirm,
Space,
} from "antd";
import moment from "moment";
import { Drawer, Grid } from "antd";
import queryString from "query-string";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import React from "react";
import { useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import {
DELETE_BILL_LINE,
INSERT_NEW_BILL_LINES,
UPDATE_BILL_LINE,
} from "../../graphql/bill-lines.queries";
import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import AlertComponent from "../alert/alert.component";
import BillFormContainer from "../bill-form/bill-form.container";
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
import BillReeportButtonComponent from "../bill-reexport-button/bill-reexport-button.component";
import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-gallery.container";
import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import BillDetailEditComponent from "./bill-detail-edit-component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(BillDetailEditcontainer);
export function BillDetailEditcontainer({
setPartsOrderContext,
insertAuditTrail,
bodyshop,
}) {
export default function BillDetailEditcontainer() {
const search = queryString.parse(useLocation().search);
const history = useHistory();
const { t } = useTranslation();
const [form] = Form.useForm();
const [visible, setVisible] = useState(false);
const [updateLoading, setUpdateLoading] = useState(false);
const [update_bill] = useMutation(UPDATE_BILL);
const [insertBillLine] = useMutation(INSERT_NEW_BILL_LINES);
const [updateBillLine] = useMutation(UPDATE_BILL_LINE);
const [deleteBillLine] = useMutation(DELETE_BILL_LINE);
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1])
@@ -80,114 +24,6 @@ export function BillDetailEditcontainer({
? bpoints[selectedBreakpoint[0]]
: "100%";
const { loading, error, data, refetch } = useQuery(QUERY_BILL_BY_PK, {
variables: { billid: search.billid },
skip: !!!search.billid,
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const handleSave = () => {
//It's got a previously deducted bill line!
if (
data.bills_by_pk.billlines.filter((b) => b.deductedfromlbr).length > 0 ||
form.getFieldValue("billlines").filter((b) => b.deductedfromlbr).length >
0
)
setVisible(true);
else {
form.submit();
}
};
const handleFinish = async (values) => {
setUpdateLoading(true);
//let adjustmentsToInsert = {};
const { billlines, upload, ...bill } = values;
const updates = [];
updates.push(
update_bill({
variables: { billId: search.billid, bill: bill },
})
);
//Find bill lines that were deleted.
const deletedJobLines = [];
data.bills_by_pk.billlines.forEach((a) => {
const matchingRecord = billlines.find((b) => b.id === a.id);
if (!matchingRecord) {
deletedJobLines.push(a);
}
});
deletedJobLines.forEach((d) => {
updates.push(deleteBillLine({ variables: { id: d.id } }));
});
billlines.forEach((billline) => {
const { deductedfromlbr, inventories, jobline, ...il } = billline;
delete il.__typename;
if (il.id) {
updates.push(
updateBillLine({
variables: {
billLineId: il.id,
billLine: {
...il,
deductedfromlbr: deductedfromlbr,
joblineid: il.joblineid === "noline" ? null : il.joblineid,
},
},
})
);
} else {
//It's a new line, have to insert it.
updates.push(
insertBillLine({
variables: {
billLines: [
{
...il,
deductedfromlbr: deductedfromlbr,
billid: search.billid,
joblineid: il.joblineid === "noline" ? null : il.joblineid,
},
],
},
})
);
}
});
await Promise.all(updates);
insertAuditTrail({
jobid: bill.jobid,
billid: search.billid,
operation: AuditTrailMapping.billupdated(bill.invoice_number),
});
await refetch();
form.setFieldsValue(transformData(data));
form.resetFields();
setVisible(false);
setUpdateLoading(false);
};
useEffect(() => {
if (search.billid && data) {
form.resetFields();
}
}, [form, search.billid, data]);
if (error) return <AlertComponent message={error.message} type="error" />;
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
return (
<Drawer
width={drawerPercentage}
@@ -195,119 +31,10 @@ export function BillDetailEditcontainer({
delete search.billid;
history.push({ search: queryString.stringify(search) });
}}
destroyOnClose
visible={search.billid}
>
{loading && <LoadingSkeleton />}
{!loading && (
<>
<PageHeader
title={
data &&
`${data.bills_by_pk.invoice_number} - ${data.bills_by_pk.vendor.name}`
}
extra={
<Space>
<Button
disabled={data.bills_by_pk.is_credit_memo}
onClick={() => {
delete search.billid;
history.push({ search: queryString.stringify(search) });
setPartsOrderContext({
actions: {},
context: {
jobId: data.bills_by_pk.jobid,
vendorId: data.bills_by_pk.vendorid,
returnFromBill: data.bills_by_pk.id,
invoiceNumber: data.bills_by_pk.invoice_number,
linesToOrder: data.bills_by_pk.billlines.map((i) => {
return {
line_desc: i.line_desc,
// db_price: i.actual_price,
act_price: i.actual_price,
cost: i.actual_cost,
quantity: i.quantity,
joblineid: i.joblineid,
oem_partno: i.jobline && i.jobline.oem_partno,
part_type: i.jobline && i.jobline.part_type,
};
}),
isReturn: true,
},
});
}}
>
{t("bills.actions.return")}
</Button>
<Popconfirm
visible={visible}
onConfirm={() => form.submit()}
onCancel={() => setVisible(false)}
okButtonProps={{ loading: updateLoading }}
title={t("bills.labels.editadjwarning")}
>
<Button
htmlType="submit"
disabled={exported}
onClick={handleSave}
loading={updateLoading}
type="primary"
>
{t("general.actions.save")}
</Button>
</Popconfirm>
<BillReeportButtonComponent bill={data && data.bills_by_pk} />
<BillMarkExportedButton bill={data && data.bills_by_pk} />
</Space>
}
/>
<Form
form={form}
onFinish={handleFinish}
initialValues={transformData(data)}
layout="vertical"
>
<BillFormContainer form={form} billEdit disabled={exported} />
{bodyshop.uselocalmediaserver ? (
<JobsDocumentsLocalGallery
job={{ id: data ? data.bills_by_pk.jobid : null }}
invoice_number={data ? data.bills_by_pk.invoice_number : null}
vendorid={data ? data.bills_by_pk.vendorid : null}
/>
) : (
<JobDocumentsGallery
jobId={data ? data.bills_by_pk.jobid : null}
billId={search.billid}
documentsList={data ? data.bills_by_pk.documents : []}
billsCallback={refetch}
/>
)}
</Form>
</>
)}
<BillDetailEditComponent />
</Drawer>
);
}
const transformData = (data) => {
return data
? {
...data.bills_by_pk,
billlines: data.bills_by_pk.billlines.map((i) => {
return {
...i,
joblineid: !!i.joblineid ? i.joblineid : "noline",
applicable_taxes: {
federal:
(i.applicable_taxes && i.applicable_taxes.federal) || false,
state: (i.applicable_taxes && i.applicable_taxes.state) || false,
local: (i.applicable_taxes && i.applicable_taxes.local) || false,
},
};
}),
date: data.bills_by_pk ? moment(data.bills_by_pk.date) : null,
}
: {};
};

View File

@@ -1,14 +1,14 @@
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react";
import {
Button,
Form,
Button, Form,
Input,
InputNumber,
Select,
Space,
Switch,
Table,
Tooltip,
Tooltip
} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -17,9 +17,8 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CiecaSelect from "../../utils/Ciecaselect";
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.component";
import { useTreatments } from "@splitsoftware/splitio-react";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser

View File

@@ -12,6 +12,7 @@ import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants";
import BillDeleteButton from "../bill-delete-button/bill-delete-button.component";
import BillDetailEditReturnComponent from "../bill-detail-edit/bill-detail-edit-return.component";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
const mapStateToProps = createStructuredSelector({
@@ -58,6 +59,14 @@ export function BillsListTableComponent({
</Button>
)}
<BillDeleteButton bill={record} />
<BillDetailEditReturnComponent
data={{ bills_by_pk: { ...record, jobid: job.id } }}
disabled={
record.is_credit_memo ||
record.vendorid === bodyshop.inhousevendorid ||
jobRO
}
/>
<Button
disabled={
record.is_credit_memo ||

View File

@@ -279,31 +279,80 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
<Form.Item label={t("courtesycars.fields.notes")} name="notes">
<Input.TextArea />
</Form.Item>
<div>
<Form.Item
label={t("courtesycars.fields.registrationexpires")}
name="registrationexpires"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<FormDatePicker />
</Form.Item>
<Form.Item
shouldUpdate={(p, c) =>
p.registrationexpires !== c.registrationexpires
}
>
{() => {
const expires = form.getFieldValue("registrationexpires");
<Form.Item
label={t("courtesycars.fields.registrationexpires")}
name="registrationexpires"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<FormDatePicker />
</Form.Item>
<Form.Item
label={t("courtesycars.fields.insuranceexpires")}
name="insuranceexpires"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<FormDatePicker />
</Form.Item>
const dateover = expires && moment(expires).isBefore(moment());
if (dateover)
return (
<Space direction="vertical" style={{ color: "tomato" }}>
<span>
<WarningFilled style={{ marginRight: ".3rem" }} />
{t("contracts.labels.dateinpast")}
</span>
</Space>
);
return <></>;
}}
</Form.Item>
</div>
<div>
<Form.Item
label={t("courtesycars.fields.insuranceexpires")}
name="insuranceexpires"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<FormDatePicker />
</Form.Item>
<Form.Item
shouldUpdate={(p, c) =>
p.insuranceexpires !== c.insuranceexpires
}
>
{() => {
const expires = form.getFieldValue("insuranceexpires");
const dateover = expires && moment(expires).isBefore(moment());
if (dateover)
return (
<Space direction="vertical" style={{ color: "tomato" }}>
<span>
<WarningFilled style={{ marginRight: ".3rem" }} />
{t("contracts.labels.dateinpast")}
</span>
</Space>
);
return <></>;
}}
</Form.Item>
</div>
<Form.Item label={t("courtesycars.fields.dailycost")} name="dailycost">
<CurrencyInput />
</Form.Item>

View File

@@ -34,6 +34,9 @@ const CourtesyCarStatusComponent = ({ value, onChange }, ref) => {
<Option value="courtesycars.status.sold">
{t("courtesycars.status.sold")}
</Option>
<Option value="courtesycars.status.leasereturn">
{t("courtesycars.status.leasereturn")}
</Option>
</Select>
);
};

View File

@@ -52,6 +52,10 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
text: t("courtesycars.status.sold"),
value: "courtesycars.status.sold",
},
{
text: t("courtesycars.status.leasereturn"),
value: "courtesycars.status.leasereturn",
},
],
onFilter: (value, record) => value.includes(record.status),
sortOrder:

View File

@@ -5,7 +5,7 @@ import AlertComponent from "../alert/alert.component";
import { Prompt, useLocation } from "react-router-dom";
import "./form-fields-changed.styles.scss";
export default function FormsFieldChanged({ form }) {
export default function FormsFieldChanged({ form, skipPrompt }) {
const { t } = useTranslation();
const handleReset = () => {
@@ -25,7 +25,7 @@ export default function FormsFieldChanged({ form }) {
return (
<Space direction="vertical" style={{ width: "100%" }}>
<Prompt
when={true}
when={skipPrompt ? false : true}
message={(location) => {
if (loc.pathname === location.pathname) return false;
return t("general.messages.unsavedchangespopup");

View File

@@ -477,7 +477,7 @@ export function JobLinesComponent({
setState({
...state,
filteredInfo: {
part_type: ["PAN,PAC,PAR,PAL,PAA,PAM,PAP,PAS,PASL,PAG"],
part_type: ["PAN","PAC","PAR","PAL","PAA","PAM","PAP","PAS","PASL","PAG"],
},
});
}}

View File

@@ -66,10 +66,8 @@ export function JobLineConvertToLabor({
const newAdjustments = _.cloneDeep(
existingAdjustments.data.jobs_by_pk.lbr_adjustments
);
newAdjustments[mod_lbr_ty] =
(newAdjustments[mod_lbr_ty] || 0) +
calculateAdjustment({ mod_lbr_ty, job, jobline });
const adjustment = calculateAdjustment({ mod_lbr_ty, job, jobline });
newAdjustments[mod_lbr_ty] = (newAdjustments[mod_lbr_ty] || 0) + adjustment;
const jobUpdate = client.mutate({
mutation: UPDATE_JOB,
@@ -83,7 +81,13 @@ export function JobLineConvertToLabor({
mutation: UPDATE_JOB_LINE,
variables: {
lineId: jobline.id,
line: { convertedtolbr: true },
line: {
convertedtolbr: true,
convertedtolbr_data: {
mod_lbr_ty: mod_lbr_ty,
mod_lb_hrs: adjustment,
},
},
},
});

View File

@@ -1,3 +1,4 @@
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import { useMutation } from "@apollo/client";
import { Button, Form, notification } from "antd";
import React, { useEffect, useState } from "react";
@@ -87,6 +88,7 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
: null,
}}
>
<FormFieldsChanged form={form} />
<LayoutFormRow header={t("jobs.forms.estdates")}>
<Form.Item
label={t("jobs.fields.date_estimated")}

View File

@@ -181,6 +181,11 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
<DataLabel key="4" label={t("owners.fields.ownr_ea")}>
{job.ownr_ea || ""}
</DataLabel>
{job.owner?.tax_number && (
<DataLabel key="5" label={t("owners.fields.tax_number")}>
{job.owner?.tax_number || ""}
</DataLabel>
)}
</div>
</Card>
</Col>

View File

@@ -1,12 +1,13 @@
import { EditFilled } from "@ant-design/icons";
import { Card, Col, Row, Space, Table } from "antd";
import _ from "lodash";
import React, { useEffect, useState } from "react";
import React, { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters";
import LaborAllocationsAdjustmentEdit from "../labor-allocations-adjustment-edit/labor-allocations-adjustment-edit.component";
import "./labor-allocations-table.styles.scss";
@@ -44,10 +45,14 @@ export function LaborAllocationsTable({
if (!jobId) setTotals([]);
}, [joblines, timetickets, bodyshop, adjustments, jobId]);
// const convertedLines = useMemo(
// () => joblines && joblines.filter((j) => j.convertedtolbr),
// [joblines]
// );
const convertedLines = useMemo(
() => joblines && joblines.filter((j) => j.convertedtolbr),
[joblines]
);
console.log(
"🚀 ~ file: labor-allocations-table.component.jsx ~ line 52 ~ convertedLines",
convertedLines
);
const columns = [
{
@@ -120,52 +125,68 @@ export function LaborAllocationsTable({
),
},
];
// const convertedTableCols = [
// {
// title: t("joblines.fields.line_desc"),
// dataIndex: "line_desc",
// key: "line_desc",
// ellipsis: true,
// },
// {
// title: t("joblines.fields.op_code_desc"),
// dataIndex: "op_code_desc",
// key: "op_code_desc",
// ellipsis: true,
// render: (text, record) =>
// `${record.op_code_desc || ""}${
// record.alt_partm ? ` ${record.alt_partm}` : ""
// }`,
// },
const convertedTableCols = [
{
title: t("joblines.fields.line_desc"),
dataIndex: "line_desc",
key: "line_desc",
ellipsis: true,
},
{
title: t("joblines.fields.op_code_desc"),
dataIndex: "op_code_desc",
key: "op_code_desc",
ellipsis: true,
render: (text, record) =>
`${record.op_code_desc || ""}${
record.alt_partm ? ` ${record.alt_partm}` : ""
}`,
},
// {
// title: t("joblines.fields.act_price"),
// dataIndex: "act_price",
// key: "act_price",
// ellipsis: true,
// render: (text, record) => (
// <>
// <CurrencyFormatter>
// {record.db_ref === "900510" || record.db_ref === "900511"
// ? record.prt_dsmk_m
// : record.act_price}
// </CurrencyFormatter>
// {record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? (
// <span
// style={{ marginLeft: ".2rem" }}
// >{`(${record.prt_dsmk_p}%)`}</span>
// ) : (
// <></>
// )}
// </>
// ),
// },
// {
// title: t("joblines.fields.part_qty"),
// dataIndex: "part_qty",
// key: "part_qty",
// },
// ];
{
title: t("joblines.fields.act_price"),
dataIndex: "act_price",
key: "act_price",
ellipsis: true,
render: (text, record) => (
<>
<CurrencyFormatter>
{record.db_ref === "900510" || record.db_ref === "900511"
? record.prt_dsmk_m
: record.act_price}
</CurrencyFormatter>
{record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? (
<span
style={{ marginLeft: ".2rem" }}
>{`(${record.prt_dsmk_p}%)`}</span>
) : (
<></>
)}
</>
),
},
{
title: t("joblines.fields.part_qty"),
dataIndex: "part_qty",
key: "part_qty",
},
{
title: t("joblines.fields.mod_lbr_ty"),
dataIndex: "conv_mod_lbr_ty",
key: "conv_mod_lbr_ty",
render: (text, record) =>
record.convertedtolbr_data && record.convertedtolbr_data.mod_lbr_ty,
},
{
title: t("joblines.fields.mod_lb_hrs"),
dataIndex: "conv_mod_lb_hrs",
key: "conv_mod_lb_hrs",
render: (text, record) =>
record.convertedtolbr_data &&
record.convertedtolbr_data.mod_lb_hrs &&
record.convertedtolbr_data.mod_lb_hrs.toFixed(1),
},
];
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
@@ -187,23 +208,21 @@ export function LaborAllocationsTable({
/>
</Card>
</Col>
{
// convertedLines && convertedLines.length > 0 && (
// <Col span={24}>
// <Card title={t("jobs.labels.convertedtolabor")}>
// <Table
// columns={convertedTableCols}
// rowKey="id"
// pagination={false}
// dataSource={convertedLines}
// scroll={{
// x: true,
// }}
// />
// </Card>
// </Col>
// )
}
{convertedLines && convertedLines.length > 0 && (
<Col span={24}>
<Card title={t("jobs.labels.convertedtolabor")}>
<Table
columns={convertedTableCols}
rowKey="id"
pagination={false}
dataSource={convertedLines}
scroll={{
x: true,
}}
/>
</Card>
</Col>
)}
</Row>
);
}

View File

@@ -94,6 +94,12 @@ export default function OwnerDetailFormComponent({ form, loading }) {
>
<Input />
</Form.Item>
<Form.Item
label={t("owners.fields.tax_number")}
name="tax_number"
>
<Input />
</Form.Item>
</LayoutFormRow>
<Form.Item label={t("owners.fields.note")} name="note">
<Input.TextArea rows={4} />

View File

@@ -20,9 +20,10 @@ function OwnerDetailFormContainer({ owner, refetch }) {
if (!!result.errors) {
notification["error"]({
message: t("owners.errors.saving", {
message: JSON.stringify(result.errors),
error: JSON.stringify(result.errors),
}),
});
setLoading(false);
return;
}

View File

@@ -39,7 +39,9 @@ export function ScheduleCalendarHeaderComponent({
const ATSToday = useMemo(() => {
if (!events) return [];
return _.groupBy(
events.filter((e) => moment(date).isSame(moment(e.start), "day")),
events.filter(
(e) => !e.vacation && moment(date).isSame(moment(e.start), "day")
),
"job.alt_transport"
);
}, [events, date]);

View File

@@ -80,6 +80,7 @@ export default function ScoreboardTimeTickets() {
totalThisMonth: 0,
totalLastMonth: 0,
totalOverPeriod: 0,
actualTotalOverPeriod: 0,
employees: {},
};
data.fixedperiod.forEach((ticket) => {
@@ -92,6 +93,7 @@ export default function ScoreboardTimeTickets() {
totalThisMonth: 0,
totalLastMonth: 0,
totalOverPeriod: 0,
actualTotalOverPeriod: 0,
};
}
@@ -182,6 +184,9 @@ export default function ScoreboardTimeTickets() {
ret.employees[ticket.employee.employee_number].totalOverPeriod =
ret.employees[ticket.employee.employee_number].totalOverPeriod +
ticket.productivehrs;
ret.employees[ticket.employee.employee_number].actualTotalOverPeriod =
ret.employees[ticket.employee.employee_number].actualTotalOverPeriod +
(ticket.actualhrs || 0);
if (!totals.employees[ticket.employee.employee_number])
totals.employees[ticket.employee.employee_number] = {
@@ -219,7 +224,7 @@ export default function ScoreboardTimeTickets() {
roundObject(ret);
roundObject(totals);
roundObject(ret2);
console.log(ret);
return {
fixed: ret,
timeperiod: {
@@ -231,6 +236,8 @@ export default function ScoreboardTimeTickets() {
};
}, [fixedPeriods, data, startDate, endDate]);
console.log(calculatedData);
if (error) return <AlertComponent message={error.message} type="error" />;
if (loading) return <LoadingSpinner />;
return (

View File

@@ -19,7 +19,6 @@ export default connect(
export function ScoreboardTicketsStats({ data, bodyshop }) {
const { t } = useTranslation();
console.log(data);
const columns = [
{
title: t("employees.fields.employee_number"),
@@ -57,6 +56,16 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
key: "totalOverPeriod",
sorter: (a, b) => a.totalOverPeriod - b.totalOverPeriod,
},
{
title: t("scoreboard.labels.efficiencyoverperiod"),
dataIndex: "efficiencyoverperiod",
key: "efficiencyoverperiod",
render: (text, record) =>
`${(
(record.totalOverPeriod / (record.actualTotalOverPeriod || 1)) *
100
).toFixed(1)} %`,
},
];
const tableData = data
@@ -112,6 +121,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
<Col md={24} lg={20}>
<Table
columns={columns}
rowKey='employee_number'
dataSource={tableData}
id="employee_number"
scroll={{ y: "300px" }}

View File

@@ -43,7 +43,7 @@ export default function ShopEmployeeAddVacation({ employee }) {
});
} else {
notification["success"]({
message: t("employees.successes.added"),
message: t("employees.successes.vacationadded"),
});
}
setLoading(false);

View File

@@ -1494,7 +1494,7 @@ export default function ShopInfoGeneral({ form }) {
}
const ReceivableCustomFieldSelect = (
<Select>
<Select allowClear>
<Select.Option value="v_vin">VIN</Select.Option>
<Select.Option value="clm_no">Claim No.</Select.Option>
<Select.Option value="ded_amt">Deductible Amount</Select.Option>

View File

@@ -2,7 +2,7 @@ import React from "react";
export default function VehicleVinDisplay({ children }) {
if (!children) return null;
console.log(children);
if (typeof children !== "string" || children.length !== 17) return children;
const vin = children.trim();

View File

@@ -1,75 +1,77 @@
import { gql } from "@apollo/client";
export const QUERY_ALL_ACTIVE_APPOINTMENTS = gql`
query QUERY_ALL_ACTIVE_APPOINTMENTS(
$start: timestamptz!
$end: timestamptz!
$startd: date!
$endd: date!
query QUERY_ALL_ACTIVE_APPOINTMENTS(
$start: timestamptz!
$end: timestamptz!
$startd: date!
$endd: date!
) {
employee_vacation(
where: { _or: [{ start: { _gte: $startd } },
{ end: { _lte: $endd } },
{_and:[{start:{_lte: $startd}},{end:{_gte:$endd}}]}] }
) {
employee_vacation(
where: { _or: [{ start: { _gte: $startd } }, { end: { _lte: $endd } }] }
) {
id
start
end
employee {
id
start
end
employee {
id
last_name
first_name
last_name
first_name
}
}
appointments(
where: {
canceled: { _eq: false }
end: { _lte: $end }
start: { _gte: $start }
}
) {
start
id
end
arrived
title
isintake
block
color
note
job {
alt_transport
ro_number
ownr_ln
ownr_co_nm
ownr_fn
ownr_ph1
ownr_ph2
ownr_ea
clm_total
id
clm_no
ins_co_nm
v_model_yr
v_make_desc
v_model_desc
labhrs: joblines_aggregate(
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
) {
aggregate {
sum {
mod_lb_hrs
}
}
}
larhrs: joblines_aggregate(
where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }
) {
aggregate {
sum {
mod_lb_hrs
}
}
}
}
appointments(
where: {
canceled: { _eq: false }
end: { _lte: $end }
start: { _gte: $start }
}
) {
start
id
end
arrived
title
isintake
block
color
note
job {
alt_transport
ro_number
ownr_ln
ownr_co_nm
ownr_fn
ownr_ph1
ownr_ph2
ownr_ea
clm_total
id
clm_no
ins_co_nm
v_model_yr
v_make_desc
v_model_desc
labhrs: joblines_aggregate(
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
) {
aggregate {
sum {
mod_lb_hrs
}
}
}
larhrs: joblines_aggregate(
where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }
) {
aggregate {
sum {
mod_lb_hrs
}
}
}
}
}
}
`;

View File

@@ -49,6 +49,7 @@ export const GET_LINE_TICKET_BY_PK = gql`
lbr_amt
op_code_desc
convertedtolbr
convertedtolbr_data
}
timetickets(where: { jobid: { _eq: $id } }) {
actualhrs
@@ -179,6 +180,7 @@ export const UPDATE_JOB_LINE = gql`
status
removed
convertedtolbr
convertedtolbr_data
}
}
}

View File

@@ -621,6 +621,7 @@ export const GET_JOB_BY_PK = gql`
ownr_ctry
ownr_ph1
ownr_ph2
tax_number
}
labor_rate_desc
rate_la1
@@ -855,6 +856,7 @@ export const QUERY_JOB_CARD_DETAILS = gql`
id
allow_text_message
preferred_contact
tax_number
}
vehicleid
v_model_yr
@@ -1907,6 +1909,7 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
profitcenter_part
prt_dsmk_p
convertedtolbr
convertedtolbr_data
}
}
}

View File

@@ -67,6 +67,7 @@ export const QUERY_OWNER_BY_ID = gql`
ownr_zip
preferred_contact
note
tax_number
jobs {
id
ro_number

View File

@@ -79,6 +79,7 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
date
id
rate
actualhrs
productivehrs
memo
jobid

View File

@@ -678,6 +678,7 @@
"refuelqty": "Refuel qty.?"
},
"correctdataonform": "Please review the information above. If any of it is not correct, you can fix it later.",
"dateinpast": "Date is in the past.",
"dlexpirebeforereturn": "The driver's license expires before the car is expected to return. ",
"driverinformation": "Driver's Information",
"findcontract": "Find Contract",
@@ -752,6 +753,7 @@
"status": {
"in": "Available",
"inservice": "In Service",
"leasereturn": "Lease Returned",
"out": "Rented",
"sold": "Sold"
},
@@ -920,7 +922,8 @@
},
"successes": {
"delete": "Employee deleted successfully.",
"save": "Employee saved successfully."
"save": "Employee saved successfully.",
"vacationadded": "Employee vacation added."
},
"validation": {
"unique_employee_number": "You must enter a unique employee number."
@@ -1935,6 +1938,7 @@
},
"errors": {
"noaccess": "The record does not exist or you do not have access to it. ",
"saving": "Error saving owner. {{error}}.",
"selectexistingornew": "Select an existing owner record or create a new one. "
},
"fields": {
@@ -1955,7 +1959,8 @@
"ownr_st": "Province/State",
"ownr_title": "Title",
"ownr_zip": "Zip/Postal Code",
"preferred_contact": "Preferred Contact Method"
"preferred_contact": "Preferred Contact Method",
"tax_number": "Tax Number"
},
"forms": {
"address": "Address",
@@ -2456,6 +2461,7 @@
"calendarperiod": "Periods based on calendar weeks/months.",
"dailyactual": "Actual (D)",
"dailytarget": "Daily",
"efficiencyoverperiod": "Efficiency over Selected Dates",
"jobs": "Jobs",
"lastmonth": "Last Month",
"lastweek": "Last Week",

View File

@@ -678,6 +678,7 @@
"refuelqty": ""
},
"correctdataonform": "",
"dateinpast": "",
"dlexpirebeforereturn": "",
"driverinformation": "",
"findcontract": "",
@@ -752,6 +753,7 @@
"status": {
"in": "",
"inservice": "",
"leasereturn": "",
"out": "",
"sold": ""
},
@@ -920,7 +922,8 @@
},
"successes": {
"delete": "Empleado eliminado con éxito.",
"save": "Empleado guardado con éxito."
"save": "Empleado guardado con éxito.",
"vacationadded": ""
},
"validation": {
"unique_employee_number": ""
@@ -1935,6 +1938,7 @@
},
"errors": {
"noaccess": "El registro no existe o no tiene acceso a él.",
"saving": "",
"selectexistingornew": ""
},
"fields": {
@@ -1955,7 +1959,8 @@
"ownr_st": "Provincia del estado",
"ownr_title": "Título",
"ownr_zip": "código postal",
"preferred_contact": "Método de Contacto Preferido"
"preferred_contact": "Método de Contacto Preferido",
"tax_number": ""
},
"forms": {
"address": "",
@@ -2456,6 +2461,7 @@
"calendarperiod": "",
"dailyactual": "",
"dailytarget": "",
"efficiencyoverperiod": "",
"jobs": "",
"lastmonth": "",
"lastweek": "",

View File

@@ -678,6 +678,7 @@
"refuelqty": ""
},
"correctdataonform": "",
"dateinpast": "",
"dlexpirebeforereturn": "",
"driverinformation": "",
"findcontract": "",
@@ -752,6 +753,7 @@
"status": {
"in": "",
"inservice": "",
"leasereturn": "",
"out": "",
"sold": ""
},
@@ -920,7 +922,8 @@
},
"successes": {
"delete": "L'employé a bien été supprimé.",
"save": "L'employé a enregistré avec succès."
"save": "L'employé a enregistré avec succès.",
"vacationadded": ""
},
"validation": {
"unique_employee_number": ""
@@ -1935,6 +1938,7 @@
},
"errors": {
"noaccess": "L'enregistrement n'existe pas ou vous n'y avez pas accès.",
"saving": "",
"selectexistingornew": ""
},
"fields": {
@@ -1955,7 +1959,8 @@
"ownr_st": "Etat / Province",
"ownr_title": "Titre",
"ownr_zip": "Zip / code postal",
"preferred_contact": "Méthode de contact préférée"
"preferred_contact": "Méthode de contact préférée",
"tax_number": ""
},
"forms": {
"address": "",
@@ -2456,6 +2461,7 @@
"calendarperiod": "",
"dailyactual": "",
"dailytarget": "",
"efficiencyoverperiod": "",
"jobs": "",
"lastmonth": "",
"lastweek": "",

View File

@@ -2385,6 +2385,7 @@
- bett_type
- cert_part
- convertedtolbr
- convertedtolbr_data
- created_at
- db_hrs
- db_price
@@ -2449,6 +2450,7 @@
- bett_type
- cert_part
- convertedtolbr
- convertedtolbr_data
- created_at
- db_hrs
- db_price
@@ -2524,6 +2526,7 @@
- bett_type
- cert_part
- convertedtolbr
- convertedtolbr_data
- created_at
- db_hrs
- db_price
@@ -4012,6 +4015,7 @@
- ownr_zip
- preferred_contact
- shopid
- tax_number
- updated_at
filter:
bodyshop:
@@ -4047,6 +4051,7 @@
- ownr_zip
- preferred_contact
- shopid
- tax_number
- updated_at
filter:
bodyshop:

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,2 @@
alter table "public"."owners" add column "tax_number" text
null;

View File

@@ -218,7 +218,7 @@ async function JobCostingMulti(req, res) {
(multiSummary.summaryData.totalLaborGp.getAmount() /
multiSummary.summaryData.totalLaborSales.getAmount()) *
100
).toFixed(2);
).toFixed(1);
multiSummary.summaryData.totalLaborGppercentFormatted = formatGpPercent(
multiSummary.summaryData.totalLaborGppercent
);
@@ -227,7 +227,7 @@ async function JobCostingMulti(req, res) {
(multiSummary.summaryData.totalPartsGp.getAmount() /
multiSummary.summaryData.totalPartsSales.getAmount()) *
100
).toFixed(2);
).toFixed(1);
multiSummary.summaryData.totalPartsGppercentFormatted = formatGpPercent(
multiSummary.summaryData.totalPartsGppercent
@@ -237,7 +237,7 @@ async function JobCostingMulti(req, res) {
(multiSummary.summaryData.totalAdditionalGp.getAmount() /
multiSummary.summaryData.totalAdditionalSales.getAmount()) *
100
).toFixed(2);
).toFixed(1);
multiSummary.summaryData.totalAdditionalGppercentFormatted =
formatGpPercent(multiSummary.summaryData.totalAdditionalGppercent);
@@ -246,7 +246,7 @@ async function JobCostingMulti(req, res) {
(multiSummary.summaryData.totalSubletGp.getAmount() /
multiSummary.summaryData.totalSubletSales.getAmount()) *
100
).toFixed(2);
).toFixed(1);
multiSummary.summaryData.totalSubletGppercentFormatted = formatGpPercent(
multiSummary.summaryData.totalSubletGppercent
@@ -256,7 +256,7 @@ async function JobCostingMulti(req, res) {
(multiSummary.summaryData.gpdollars.getAmount() /
multiSummary.summaryData.totalSales.getAmount()) *
100
).toFixed(2);
).toFixed(1);
multiSummary.summaryData.gppercentFormatted = formatGpPercent(
multiSummary.summaryData.gppercent
@@ -282,7 +282,7 @@ async function JobCostingMulti(req, res) {
(
(c.gpdollars_dinero.getAmount() / c.sales_dinero.getAmount()) *
100
).toFixed(2)
).toFixed(1)
),
};
});
@@ -730,9 +730,9 @@ function GenerateCostingData(job) {
.add(sale_sublet);
const gpdollars = totalSales.subtract(costs);
const gppercent = (
(gpdollars.getAmount() / totalSales.getAmount()) *
(gpdollars.getAmount() / Math.abs(totalSales.getAmount())) *
100
).toFixed(2);
).toFixed(1);
//Push summary data to avoid extra loop.
summaryData.totalLaborSales = summaryData.totalLaborSales.add(sale_labor);
@@ -823,7 +823,7 @@ function GenerateCostingData(job) {
(summaryData.totalLaborGp.getAmount() /
summaryData.totalLaborSales.getAmount()) *
100
).toFixed(2);
).toFixed(1);
summaryData.totalLaborGppercentFormatted = formatGpPercent(
summaryData.totalLaborGppercent
);
@@ -835,7 +835,7 @@ function GenerateCostingData(job) {
(summaryData.totalPartsGp.getAmount() /
summaryData.totalPartsSales.getAmount()) *
100
).toFixed(2);
).toFixed(1);
summaryData.totalPartsGppercentFormatted = formatGpPercent(
summaryData.totalPartsGppercent
);
@@ -846,7 +846,7 @@ function GenerateCostingData(job) {
(summaryData.totalAdditionalGp.getAmount() /
summaryData.totalAdditionalSales.getAmount()) *
100
).toFixed(2);
).toFixed(1);
summaryData.totalAdditionalGppercentFormatted = formatGpPercent(
summaryData.totalAdditionalGppercent
);
@@ -857,7 +857,7 @@ function GenerateCostingData(job) {
(summaryData.totalSubletGp.getAmount() /
summaryData.totalSubletSales.getAmount()) *
100
).toFixed(2);
).toFixed(1);
summaryData.totalSubletGppercentFormatted = formatGpPercent(
summaryData.totalSubletGppercent
);
@@ -866,9 +866,10 @@ function GenerateCostingData(job) {
summaryData.totalCost
);
summaryData.gppercent = (
(summaryData.gpdollars.getAmount() / summaryData.totalSales.getAmount()) *
(summaryData.gpdollars.getAmount() /
Math.abs(summaryData.totalSales.getAmount())) *
100
).toFixed(2);
).toFixed(1);
if (isNaN(summaryData.gppercent)) summaryData.gppercentFormatted = 0;
else if (!isFinite(summaryData.gppercent))

6670
yarn.lock

File diff suppressed because it is too large Load Diff