Merged in release/2022-04-15 (pull request #445)

release/2022-04-15

Approved-by: Patrick Fic
This commit is contained in:
Patrick Fic
2022-04-13 22:13:29 +00:00
17 changed files with 338 additions and 19 deletions

View File

@@ -1,4 +1,4 @@
<babeledit_project version="1.2" be_version="2.7.1">
<babeledit_project be_version="2.7.1" version="1.2">
<!--
BabelEdit project file
@@ -3654,6 +3654,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>bill_allow_post_to_closed</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>bill_federal_tax_rate</name>
<definition_loaded>false</definition_loaded>
@@ -4753,6 +4774,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>private</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>state</name>
<definition_loaded>false</definition_loaded>
@@ -20334,6 +20376,27 @@
</concept_node>
</children>
</folder_node>
<concept_node>
<name>auto_add_ats</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>ca_bc_pvrt</name>
<definition_loaded>false</definition_loaded>
@@ -23236,6 +23299,27 @@
</concept_node>
</children>
</folder_node>
<concept_node>
<name>rate_ats</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>rate_la1</name>
<definition_loaded>false</definition_loaded>

View File

@@ -14,6 +14,7 @@ import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useLocation, useHistory } from "react-router-dom";
import {
DELETE_BILL_LINE,
INSERT_NEW_BILL_LINES,
UPDATE_BILL_LINE,
} from "../../graphql/bill-lines.queries";
@@ -58,6 +59,7 @@ export function BillDetailEditcontainer({
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])
@@ -107,6 +109,20 @@ export function BillDetailEditcontainer({
})
);
//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, jobline, ...il } = billline;
delete il.__typename;
@@ -142,6 +158,7 @@ export function BillDetailEditcontainer({
);
}
});
await Promise.all(updates);
insertAuditTrail({

View File

@@ -229,6 +229,7 @@ export function BillFormComponent({
({ getFieldValue }) => ({
validator(rule, value) {
if (
!bodyshop.bill_allow_post_to_closed &&
(job.status === bodyshop.md_ro_statuses.default_invoiced ||
job.status === bodyshop.md_ro_statuses.default_exported ||
job.status === bodyshop.md_ro_statuses.default_void) &&

View File

@@ -1,9 +1,10 @@
import { useLazyQuery } from "@apollo/client";
import { LoadingOutlined } from "@ant-design/icons";
import { AutoComplete, Divider, Space } from "antd";
import _ from "lodash";
import React from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { Link, useHistory } from "react-router-dom";
import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import AlertComponent from "../alert/alert.component";
@@ -12,8 +13,9 @@ import OwnerNameDisplay, {
} from "../owner-name-display/owner-name-display.component";
export default function GlobalSearch() {
const { t } = useTranslation();
const [callSearch, { error, data }] = useLazyQuery(GLOBAL_SEARCH_QUERY);
const history = useHistory();
const [callSearch, { loading, error, data }] =
useLazyQuery(GLOBAL_SEARCH_QUERY);
const executeSearch = (v) => {
if (v && v.variables.search && v.variables.search !== "") callSearch(v);
@@ -171,8 +173,13 @@ export default function GlobalSearch() {
<AutoComplete
options={options}
onSearch={handleSearch}
suffixIcon={loading && <LoadingOutlined spin />}
defaultActiveFirstOption
placeholder={t("general.labels.globalsearch")}
allowClear
onSelect={(val, opt) => {
history.push(opt.label.props.to);
}}
></AutoComplete>
);
}

View File

@@ -41,9 +41,10 @@ import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.con
import _ from "lodash";
import JobCreateIOU from "../job-create-iou/job-create-iou.component";
import JobLinesExpander from "./job-lines-expander.component";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly,
technician: selectTechnician,
});
@@ -56,6 +57,7 @@ const mapDispatchToProps = (dispatch) => ({
});
export function JobLinesComponent({
bodyshop,
jobRO,
technician,
setPartsOrderContext,
@@ -75,6 +77,9 @@ export function JobLinesComponent({
filteredInfo: {},
});
const { t } = useTranslation();
const jobIsPrivate = bodyshop.md_ins_cos.find(
(c) => c.name === job.ins_co_nm
)?.private;
const columns = [
{
@@ -286,7 +291,7 @@ export function JobLinesComponent({
key: "actions",
render: (text, record) => (
<div>
{record.manual_line && (
{(record.manual_line || jobIsPrivate) && (
<Space>
<Button
disabled={jobRO}

View File

@@ -216,6 +216,7 @@ export function JobLinesUpsertModalComponent({
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
console.log(value);
if (!value || getFieldValue("part_type") !== "PAE") {
return Promise.resolve();
}
@@ -226,7 +227,10 @@ export function JobLinesUpsertModalComponent({
}),
({ getFieldValue }) => ({
validator(rule, value) {
if (!!getFieldValue("part_type") === !!value) {
console.log(value, !!value);
if (
!!getFieldValue("part_type") === (!!value || value === 0)
) {
return Promise.resolve();
}
return Promise.reject(

View File

@@ -18,7 +18,7 @@ import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import axios from "axios";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
@@ -53,6 +53,12 @@ export function JobsConvertButton({
variables: { jobId: job.id, ...values },
});
if (values.ca_gst_registrant) {
await axios.post("/job/totalsssu", {
id: job.id,
});
}
if (!res.errors) {
refetch();
notification["success"]({

View File

@@ -88,6 +88,33 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
</Form.Item>
<CABCpvrtCalculator form={form} disabled={jobRO} />
</Space>
<Form.Item
label={t("jobs.fields.auto_add_ats")}
name="auto_add_ats"
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
nostyle
shouldUpdate={(prev, cur) => prev.auto_add_ats !== cur.auto_add_ats}
>
{() => {
if (form.getFieldValue("auto_add_ats"))
return (
<Form.Item
label={t("jobs.fields.rate_ats")}
name="rate_ats"
initialValue={bodyshop.shoprates.rate_atp}
>
<CurrencyInput disabled={jobRO} />
</Form.Item>
);
return null;
}}
</Form.Item>
</FormRow>
<FormRow>
<Form.Item
@@ -100,7 +127,13 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
label={t("jobs.fields.state_tax_rate")}
name="state_tax_rate"
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO} autoComplete="new-password"/>
<InputNumber
min={0}
max={1}
precision={2}
disabled={jobRO}
autoComplete="new-password"
/>
</Form.Item>
<Form.Item
label={t("jobs.fields.local_tax_rate")}

View File

@@ -558,6 +558,13 @@ export default function ShopInfoGeneral({ form }) {
>
<Switch />
</Form.Item>
<Form.Item
name={["bill_allow_post_to_closed"]}
label={t("bodyshop.fields.bill_allow_post_to_closed")}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
name={["md_ded_notes"]}
label={t("bodyshop.fields.md_ded_notes")}
@@ -811,14 +818,23 @@ export default function ShopInfoGeneral({ form }) {
>
<Input />
</Form.Item>
<Space>
<Form.Item
label={t("bodyshop.fields.md_ins_co.zip")}
key={`${index}zip`}
name={[field.name, "zip"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.md_ins_co.zip")}
key={`${index}zip`}
name={[field.name, "zip"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.md_ins_co.private")}
key={`${index}private`}
name={[field.name, "private"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);

View File

@@ -12,6 +12,13 @@ export const UPDATE_BILL_LINE = gql`
}
}
`;
export const DELETE_BILL_LINE = gql`
mutation DELETE_BILL_LINE($id: uuid!) {
delete_billlines_by_pk(id: $id) {
id
}
}
`;
export const INSERT_NEW_BILL_LINES = gql`
mutation INSERT_NEW_BILL_LINES($billLines: [billlines_insert_input!]!) {

View File

@@ -105,6 +105,7 @@ export const QUERY_BODYSHOP = gql`
md_from_emails
last_name_first
md_parts_order_comment
bill_allow_post_to_closed
employees {
user_email
id
@@ -207,6 +208,7 @@ export const UPDATE_SHOP = gql`
md_from_emails
last_name_first
md_parts_order_comment
bill_allow_post_to_closed
employees {
id
first_name

View File

@@ -611,6 +611,8 @@ export const GET_JOB_BY_PK = gql`
ownerid
ded_note
materials
auto_add_ats
rate_ats
owner {
id
ownr_fn

View File

@@ -233,6 +233,7 @@
},
"appt_length": "Default Appointment Length",
"attach_pdf_to_email": "Attach PDF copy to sent emails?",
"bill_allow_post_to_closed": "Allow Bills to be posted to Closed Jobs",
"bill_federal_tax_rate": "Bills - Federal Tax Rate %",
"bill_local_tax_rate": "Bill - Provincial/State Tax Rate %",
"bill_state_tax_rate": "Bill - Provincial/State Tax Rate %",
@@ -295,6 +296,7 @@
"md_ins_co": {
"city": "City",
"name": "Insurance Company Name",
"private": "Private",
"state": "Province/State",
"street1": "Street 1",
"street2": "Street 2",
@@ -1244,6 +1246,7 @@
"08": "Left Rear Side",
"09": "Left Side"
},
"auto_add_ats": "Automatically Add/Update ATS",
"ca_bc_pvrt": "PVRT",
"ca_customer_gst": "Customer Portion of GST",
"ca_gst_registrant": "GST Registrant",
@@ -1391,6 +1394,7 @@
"production_vars": {
"note": "Production Note"
},
"rate_ats": "ATS Rate",
"rate_la1": "LA1",
"rate_la2": "LA2",
"rate_la3": "LA3",

View File

@@ -233,6 +233,7 @@
},
"appt_length": "",
"attach_pdf_to_email": "",
"bill_allow_post_to_closed": "",
"bill_federal_tax_rate": "",
"bill_local_tax_rate": "",
"bill_state_tax_rate": "",
@@ -295,6 +296,7 @@
"md_ins_co": {
"city": "",
"name": "",
"private": "",
"state": "",
"street1": "",
"street2": "",
@@ -1244,6 +1246,7 @@
"08": "",
"09": ""
},
"auto_add_ats": "",
"ca_bc_pvrt": "",
"ca_customer_gst": "",
"ca_gst_registrant": "",
@@ -1391,6 +1394,7 @@
"production_vars": {
"note": ""
},
"rate_ats": "",
"rate_la1": "Tarifa LA1",
"rate_la2": "Tarifa LA2",
"rate_la3": "Tarifa LA3",

View File

@@ -233,6 +233,7 @@
},
"appt_length": "",
"attach_pdf_to_email": "",
"bill_allow_post_to_closed": "",
"bill_federal_tax_rate": "",
"bill_local_tax_rate": "",
"bill_state_tax_rate": "",
@@ -295,6 +296,7 @@
"md_ins_co": {
"city": "",
"name": "",
"private": "",
"state": "",
"street1": "",
"street2": "",
@@ -1244,6 +1246,7 @@
"08": "",
"09": ""
},
"auto_add_ats": "",
"ca_bc_pvrt": "",
"ca_customer_gst": "",
"ca_gst_registrant": "",
@@ -1391,6 +1394,7 @@
"production_vars": {
"note": ""
},
"rate_ats": "",
"rate_la1": "Taux LA1",
"rate_la2": "Taux LA2",
"rate_la3": "Taux LA3",

View File

@@ -20,6 +20,38 @@ mutation UNARCHIVE_CONVERSATION($id: uuid!) {
}
`;
exports.INSERT_NEW_JOB_LINE = `
mutation INSERT_NEW_JOB_LINE($lineInput: [joblines_insert_input!]!) {
insert_joblines(objects: $lineInput) {
returning {
id
}
}
}
`;
exports.UPDATE_JOB_LINE = `
mutation UPDATE_JOB_LINE($lineId: uuid!, $line: joblines_set_input!) {
update_joblines(where: { id: { _eq: $lineId } }, _set: $line) {
returning {
id
notes
mod_lbr_ty
part_qty
db_price
act_price
line_desc
line_no
oem_partno
notes
location
status
removed
}
}
}
`;
exports.RECEIVE_MESSAGE = `
mutation RECEIVE_MESSAGE($msg: [messages_insert_input!]!) {
insert_messages(objects: $msg) {
@@ -970,6 +1002,8 @@ exports.GET_JOB_BY_PK = ` query GET_JOB_BY_PK($id: uuid!) {
ca_bc_pvrt
ca_customer_gst
materials
auto_add_ats
rate_ats
joblines(where: { removed: { _eq: false } }){
id
line_no

View File

@@ -24,7 +24,7 @@ exports.totalsSsu = async function (req, res) {
});
const newTotals = await TotalsServerSide(
{ body: { job: job.jobs_by_pk } },
{ body: { job: job.jobs_by_pk, client: client } },
res,
true
);
@@ -53,7 +53,9 @@ exports.totalsSsu = async function (req, res) {
//IMPORTANT*** These two functions MUST be mirrrored.
async function TotalsServerSide(req, res) {
const { job } = req.body;
const { job, client } = req.body;
await AutoAddAtsIfRequired({ job: job, client: client });
try {
let ret = {
parts: CalculatePartsTotals(job.joblines),
@@ -78,6 +80,16 @@ async function Totals(req, res) {
jobid: job.id,
});
const BearerToken = req.headers.authorization;
const { id } = req.body;
logger.log("job-totals-ssu", "DEBUG", req.user.email, id, null);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
headers: {
Authorization: BearerToken,
},
});
await AutoAddAtsIfRequired({ job, client });
try {
let ret = {
parts: CalculatePartsTotals(job.joblines),
@@ -96,6 +108,83 @@ async function Totals(req, res) {
}
}
async function AutoAddAtsIfRequired({ job, client }) {
//Check if ATS should be automatically added.
if (job.auto_add_ats) {
//Get the total sum of hours that should be the ATS amount.
//Check to see if an ATS line exists.
let atsLineIndex = null;
const atsHours = job.joblines.reduce((acc, val, index) => {
if (val.line_desc && val.line_desc.toLowerCase() === "ats amount") {
atsLineIndex = index;
}
if (
val.mod_lbr_ty !== "LA1" &&
val.mod_lbr_ty !== "LA2" &&
val.mod_lbr_ty !== "LA3" &&
val.mod_lbr_ty !== "LA4" &&
val.mod_lbr_ty !== "LAU" &&
val.mod_lbr_ty !== "LAG" &&
val.mod_lbr_ty !== "LAS" &&
val.mod_lbr_ty !== "LAA"
) {
acc = acc + val.mod_lb_hrs;
}
return acc;
}, 0);
const atsAmount = atsHours * (job.rate_ats || 0);
//If it does, update it in place, and make sure it is updated for local calculations.
if (atsLineIndex === null) {
const newAtsLine = {
jobid: job.id,
alt_partm: null,
line_no: 35,
unq_seq: 0,
line_ind: "E",
line_desc: "ATS Amount",
line_ref: 0.0,
part_type: null,
oem_partno: null,
db_price: 0.0,
act_price: atsAmount,
part_qty: 1,
mod_lbr_ty: null,
db_hrs: 0.0,
mod_lb_hrs: 0.0,
lbr_op: "OP13",
lbr_amt: 0.0,
op_code_desc: "ADDITIONAL COSTS",
status: null,
location: null,
tax_part: true,
db_ref: null,
manual_line: true,
prt_dsmk_p: 0.0,
prt_dsmk_m: 0.0,
};
const result = await client.request(queries.INSERT_NEW_JOB_LINE, {
lineInput: [newAtsLine],
});
job.joblines.push(newAtsLine);
}
//If it does not, create one for local calculations and insert it.
else {
const result = await client.request(queries.UPDATE_JOB_LINE, {
line: { act_price: atsAmount },
lineId: job.joblines[atsLineIndex].id,
});
job.joblines[atsLineIndex].act_price = atsAmount;
}
console.log(job.jobLines);
}
}
function CalculateRatesTotals(ratesList) {
const jobLines = ratesList.joblines.filter((jl) => !jl.removed);