Merge branch 'release/2021-09-10' into feature/cdk-cert

This commit is contained in:
Patrick Fic
2021-09-08 08:28:54 -07:00
33 changed files with 698 additions and 119 deletions

View File

@@ -115,18 +115,18 @@ export default function JobLinesUpsertModalComponent({
<Form.Item
label={t("joblines.fields.mod_lb_hrs")}
name="mod_lb_hrs"
// rules={[
// ({ getFieldValue }) => ({
// validator(rule, value) {
// if (!!getFieldValue("mod_lbr_ty") === !!value) {
// return Promise.resolve();
// }
// return Promise.reject(
// t("joblines.validations.hrsrequirediflbrtyp")
// );
// },
// }),
// ]}
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
if (!!getFieldValue("mod_lbr_ty") === !!value) {
return Promise.resolve();
}
return Promise.reject(
t("joblines.validations.hrsrequirediflbrtyp")
);
},
}),
]}
>
<InputNumber precision={1} />
</Form.Item>
@@ -169,18 +169,18 @@ export default function JobLinesUpsertModalComponent({
<Form.Item
label={t("joblines.fields.part_qty")}
name="part_qty"
// rules={[
// ({ getFieldValue }) => ({
// validator(rule, value) {
// if (!!getFieldValue("part_type") === !!value) {
// return Promise.resolve();
// }
// return Promise.reject(
// t("joblines.validations.requiredifparttype")
// );
// },
// }),
// ]}
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
if (!!getFieldValue("part_type") === !!value) {
return Promise.resolve();
}
return Promise.reject(
t("joblines.validations.requiredifparttype")
);
},
}),
]}
>
<InputNumber precision={0} min={0} />
</Form.Item>
@@ -190,28 +190,28 @@ export default function JobLinesUpsertModalComponent({
<Form.Item
label={t("joblines.fields.act_price")}
name="act_price"
// rules={[
// ({ getFieldValue }) => ({
// validator(rule, value) {
// if (!value || getFieldValue("part_type") !== "PAE") {
// return Promise.resolve();
// }
// return Promise.reject(
// t("joblines.validations.zeropriceexistingpart")
// );
// },
// }),
// ({ getFieldValue }) => ({
// validator(rule, value) {
// if (!!getFieldValue("part_type") === !!value) {
// return Promise.resolve();
// }
// return Promise.reject(
// t("joblines.validations.requiredifparttype")
// );
// },
// }),
// ]}
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
if (!value || getFieldValue("part_type") !== "PAE") {
return Promise.resolve();
}
return Promise.reject(
t("joblines.validations.zeropriceexistingpart")
);
},
}),
({ getFieldValue }) => ({
validator(rule, value) {
if (!!getFieldValue("part_type") === !!value) {
return Promise.resolve();
}
return Promise.reject(
t("joblines.validations.requiredifparttype")
);
},
}),
]}
>
<InputCurrency precision={2} min={0} />
</Form.Item>

View File

@@ -98,23 +98,7 @@ export function JobsAvailableContainer({
return;
}
//IO-539 Check for Parts Rate on PAL for SGI use case.
if (
estData.est_data.parts_tax_rates &&
estData.est_data.parts_tax_rates.PAL &&
(estData.est_data.parts_tax_rates.PAL.prt_tax_rt === null ||
estData.est_data.parts_tax_rates.PAL.prt_tax_rt === 0)
) {
console.log("checking");
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for used parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}%.`
);
if (res) {
estData.est_data.parts_tax_rates.PAL.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.est_data.parts_tax_rates.PAL.prt_tax_in = true;
}
}
await CheckTaxRates(estData, bodyshop);
const newTotals = (
await Axios.post("/job/totals", {
@@ -215,22 +199,7 @@ export function JobsAvailableContainer({
});
} else {
//IO-539 Check for Parts Rate on PAL for SGI use case.
if (
estData.est_data.parts_tax_rates &&
estData.est_data.parts_tax_rates.PAL &&
(estData.est_data.parts_tax_rates.PAL.prt_tax_rt === null ||
estData.est_data.parts_tax_rates.PAL.prt_tax_rt === 0)
) {
console.log("checking");
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for used parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}%.`
);
if (res) {
estData.est_data.parts_tax_rates.PAL.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.est_data.parts_tax_rates.PAL.prt_tax_in = true;
}
}
await CheckTaxRates(estData, bodyshop);
//create upsert job
let supp = replaceEmpty({ ...estData.est_data });
@@ -432,3 +401,98 @@ function confirmDialog(msg) {
return confirmed ? resolve(true) : resolve(false);
});
}
async function CheckTaxRates(estData, bodyshop) {
//LKQ Check
if (
!estData.est_data.parts_tax_rates?.PAL ||
estData.est_data.parts_tax_rates?.PAL?.prt_tax_rt === null ||
estData.est_data.parts_tax_rates?.PAL?.prt_tax_rt === 0
) {
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for LKQ parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
);
if (res) {
if (!estData.est_data.parts_tax_rates.PAL) {
estData.est_data.parts_tax_rates.PAL = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAL",
};
}
estData.est_data.parts_tax_rates.PAL.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.est_data.parts_tax_rates.PAL.prt_tax_in = true;
}
}
//PAC Check
if (
!estData.est_data.parts_tax_rates?.PAC ||
estData.est_data.parts_tax_rates?.PAC?.prt_tax_rt === null ||
estData.est_data.parts_tax_rates?.PAC?.prt_tax_rt === 0
) {
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for rechromed parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
);
if (res) {
if (!estData.est_data.parts_tax_rates.PAC) {
estData.est_data.parts_tax_rates.PAC = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAC",
};
}
estData.est_data.parts_tax_rates.PAC.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.est_data.parts_tax_rates.PAC.prt_tax_in = true;
}
}
//PAM Check
if (
!estData.est_data.parts_tax_rates?.PAM ||
estData.est_data.parts_tax_rates?.PAM?.prt_tax_rt === null ||
estData.est_data.parts_tax_rates?.PAM?.prt_tax_rt === 0
) {
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for remanufactured parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
);
if (res) {
if (!estData.est_data.parts_tax_rates.PAM) {
estData.est_data.parts_tax_rates.PAM = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAM",
};
}
estData.est_data.parts_tax_rates.PAM.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.est_data.parts_tax_rates.PAM.prt_tax_in = true;
}
}
if (
!estData.est_data.parts_tax_rates?.PAR ||
estData.est_data.parts_tax_rates?.PAR?.prt_tax_rt === null ||
estData.est_data.parts_tax_rates?.PAR?.prt_tax_rt === 0
) {
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for recored parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
);
if (res) {
if (!estData.est_data.parts_tax_rates.PAR) {
estData.est_data.parts_tax_rates.PAR = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAR",
};
}
estData.est_data.parts_tax_rates.PAR.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.est_data.parts_tax_rates.PAR.prt_tax_in = true;
}
}
}

View File

@@ -15,6 +15,7 @@ import JobAltTransportChange from "../job-at-change/job-at-change.component";
import JobEmployeeAssignments from "../job-employee-assignments/job-employee-assignments.container";
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
import "./jobs-detail-header.styles.scss";
import JobsRelatedRos from "../jobs-related-ros/jobs-related-ros.component";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
@@ -80,6 +81,11 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
<span style={{ margin: "0rem .5rem" }}>/</span>
<CurrencyFormatter>{job.owner_owing}</CurrencyFormatter>
</DataLabel>
{job.converted && (
<DataLabel label={t("jobs.labels.relatedros")}>
<JobsRelatedRos jobid={job.id} />
</DataLabel>
)}
<DataLabel label={t("jobs.fields.alt_transport")}>
{job.alt_transport}
<JobAltTransportChange job={job} />

View File

@@ -0,0 +1,112 @@
import React, { useState } from "react";
import { useQuery, useMutation } from "@apollo/client";
import { Tag, Space, Button, Popover, Card, Form } from "antd";
import {
DELETE_RELATED_RO,
INSERT_RELATED_ROS,
QUERY_RELATED_ROS,
} from "../../graphql/jobs.queries";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import AlertComponent from "../alert/alert.component";
import { PlusCircleOutlined } from "@ant-design/icons";
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
export default function JobsRelatedRos({ jobid }) {
const [roSearchVisible, setRoSearchVisible] = useState(false);
const [saveLoading, setSaveLoading] = useState(false);
const [insertRelationship] = useMutation(INSERT_RELATED_ROS);
const [deleteRelationship] = useMutation(DELETE_RELATED_RO);
const { loading, error, data } = useQuery(QUERY_RELATED_ROS, {
variables: { jobid },
skip: !jobid,
});
const { t } = useTranslation();
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
const relatedJobs = data.relatedjobs.map((r) => {
if (r.parentjob === jobid) {
return { relationshipid: r.id, ...r.childjob_rel };
}
return { relationshipid: r.id, ...r.parentjob_rel };
});
const handleAddRo = async ({ relatedjobid }) => {
setSaveLoading(true);
await insertRelationship({
variables: { relationship: { parentjob: jobid, childjob: relatedjobid } },
update(cache, { data }) {
cache.modify({
fields: {
relatedjobs(rj, { readField }) {
return [rj, data.insert_relatedjobs_one];
},
},
});
},
});
setSaveLoading(false);
setRoSearchVisible(false);
};
const handleDelete = async (id) => {
setSaveLoading(true);
await deleteRelationship({
variables: {
relationshipid: id,
},
update(cache, { data }) {
cache.modify({
fields: {
relatedjobs(rj, { readField }) {
return rj.filter((r) => r.id !== id);
},
},
});
},
});
setSaveLoading(false);
setRoSearchVisible(false);
};
const popContent = (
<Card style={{ minWidth: "25rem" }}>
<Form layout="vertical" onFinish={handleAddRo}>
<Form.Item
name="relatedjobid"
label={t("jobs.fields.ro_number")}
rules={[{ required: true }]}
>
<JobSearchSelectComponent convertedOnly />
</Form.Item>
<Space>
<Button loading={saveLoading} htmlType="submit">
{t("general.actions.add")}
</Button>
<Button onClick={() => setRoSearchVisible(false)}>
{t("general.actions.cancel")}
</Button>
</Space>
</Form>
</Card>
);
return (
<Space wrap>
{relatedJobs.map((r) => (
<Tag key={r.id} closable onClose={() => handleDelete(r.relationshipid)}>
<Link to={`/manage/jobs/${r?.id}`}>{r.ro_number}</Link>
</Tag>
))}
<Popover content={popContent} visible={roSearchVisible}>
<Button type="link" onClick={() => setRoSearchVisible(true)}>
<PlusCircleOutlined />
</Button>
</Popover>
</Space>
);
}

View File

@@ -1893,3 +1893,48 @@ export const QUERY_JOB_EXPORT_DMS = gql`
}
}
`;
export const QUERY_RELATED_ROS = gql`
query QUERY_RELATED_ROS($jobid: uuid!) {
relatedjobs(
where: {
_or: [{ childjob: { _eq: $jobid } }, { parentjob: { _eq: $jobid } }]
}
) {
parentjob
id
parentjob_rel {
id
ro_number
}
childjob
childjob_rel {
id
ro_number
}
}
}
`;
export const INSERT_RELATED_ROS = gql`
mutation INSERT_RELATED_ROS($relationship: relatedjobs_insert_input!) {
insert_relatedjobs_one(object: $relationship) {
parentjob
id
parentjob_rel {
id
ro_number
}
childjob
childjob_rel {
id
ro_number
}
}
}
`;
export const DELETE_RELATED_RO = gql`
mutation DELETE_RELATED_RO($relationshipid: uuid!) {
delete_relatedjobs_by_pk(id: $relationshipid) {
id
}
}
`;

View File

@@ -1033,7 +1033,7 @@
"PAP": "OEM Partial",
"PAR": "Recored",
"PAS": "Sublet",
"PASL": "Sublet"
"PASL": "Sublet (L)"
},
"profitcenter_labor": "Profit Center: Labor",
"profitcenter_part": "Profit Center: Part",
@@ -1469,6 +1469,7 @@
"removedpartsstrikethrough": "Strike through lines represent parts that have been removed from the estimate. They are included for completeness of reconciliation."
},
"reconciliationheader": "Parts & Sublet Reconciliation",
"relatedros": "Related ROs",
"returntotals": "Return Totals",
"rosaletotal": "RO Parts Total",
"sale_labor": "Sales - Labor",

View File

@@ -1469,6 +1469,7 @@
"removedpartsstrikethrough": ""
},
"reconciliationheader": "",
"relatedros": "",
"returntotals": "",
"rosaletotal": "",
"sale_labor": "",

View File

@@ -1469,6 +1469,7 @@
"removedpartsstrikethrough": ""
},
"reconciliationheader": "",
"relatedros": "",
"returntotals": "",
"rosaletotal": "",
"sale_labor": "",