Added fundamentals for v2 rule set intro.
This commit is contained in:
@@ -98,5 +98,10 @@
|
||||
"title": "Release Notes for 1.0.28",
|
||||
"date": "05/26/2022",
|
||||
"notes": "Bug Fix: \n- Improved SUV detection."
|
||||
},
|
||||
"1.1.0": {
|
||||
"title": "Release Notes for 1.1.0",
|
||||
"date": "TBD",
|
||||
"notes": "Bug Fix: \n- Improved vehicle detection.\n- Updates for MPIs new rule base effective in 2023.\n- Security updates."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ async function ImportJob(path) {
|
||||
});
|
||||
} else {
|
||||
log.info(`Ignored job. ${newJob.ERROR}`);
|
||||
// Nucleus.track("IGNORE_JOB", { reason: newJob.ERROR });
|
||||
// Nucleus.track("IGNORE_JOB", { reason: newJob.ERROR });
|
||||
NewNotification({
|
||||
title: "Job Ignored",
|
||||
body: newJob.ERROR,
|
||||
@@ -345,177 +345,17 @@ async function DecodeLinFile(extensionlessFilePath) {
|
||||
}
|
||||
);
|
||||
});
|
||||
// .filter(
|
||||
// (jobline) =>
|
||||
// jobline.part_type &&
|
||||
// !jobline.db_ref.startsWith("900") &&
|
||||
// !jobline.line_desc.toLowerCase().startsWith("urethane") &&
|
||||
// !jobline.line_desc.toLowerCase().startsWith("wheel") &&
|
||||
// !jobline.line_desc.toLowerCase().startsWith("hazardous") &&
|
||||
// !jobline.line_desc.toLowerCase().startsWith("detail") &&
|
||||
// !jobline.line_desc.toLowerCase().startsWith("clean") &&
|
||||
// jobline.part_type.toUpperCase() !== "PAG" &&
|
||||
// jobline.part_type.toUpperCase() !== "PAS" &&
|
||||
// jobline.part_type.toUpperCase() !== "PASL" &&
|
||||
// jobline.part_type.toUpperCase() !== "PAE" &&
|
||||
// jobline.glass_flag === false
|
||||
// )
|
||||
|
||||
//Apply ruleset.
|
||||
|
||||
joblines.map((jobline) => {
|
||||
//Removed as a result of conversation with Norm.
|
||||
// Appears you are calculating based on ‘N.A.’ part values in the Mitchell database.
|
||||
// Reminder – if Mitchell DB has $0.00 price updates can be tracked, if it has N.A. it cannot calculate RPS value.
|
||||
|
||||
//Removed as a part of $0db removal on 12/17/2022.
|
||||
// if (
|
||||
// (jobline.db_price === null || jobline.db_price === 0) &&
|
||||
// !!jobline.act_price &&
|
||||
// jobline.act_price > 0
|
||||
// ) {
|
||||
// // log.info(
|
||||
// // "DB Price null/lower than act price",
|
||||
// // jobline.line_desc,
|
||||
// // jobline.db_price,
|
||||
// // jobline.act_price
|
||||
// // );
|
||||
// jobline.db_price = jobline.act_price;
|
||||
// }
|
||||
|
||||
//*****TODO LINE****
|
||||
//Any PAL, Remanufactured, Recycled, Aftermarket, Used,
|
||||
// If DB Price 0, ignore them.
|
||||
// if (
|
||||
// (jobline.part_type === "PAL" ||
|
||||
// jobline.part_type === "PAM" ||
|
||||
// jobline.part_type === "PAR" ||
|
||||
// jobline.part_type === "PAA") &&
|
||||
// jobline.db_price === 0
|
||||
// ) {
|
||||
// jobline.ignore = true;
|
||||
// }
|
||||
|
||||
jobline.ignore = false;
|
||||
|
||||
//Wheel Repair Pricing PRS-82
|
||||
//Verified on 08/24/21
|
||||
if (
|
||||
jobline.part_type === "PAN" &&
|
||||
jobline.line_desc.toLowerCase().includes("wheel") &&
|
||||
Math.abs(jobline.prt_dsmk_m) ===
|
||||
Math.round((jobline.act_price / 2) * 100) / 100
|
||||
) {
|
||||
log.info(`Jobline '${jobline.line_desc}' ignored due to wheel repair.`);
|
||||
jobline.ignore = true;
|
||||
if (false) {
|
||||
jobline = V2Ruleset(jobline);
|
||||
} else {
|
||||
jobline = V1Ruleset(jobline);
|
||||
}
|
||||
|
||||
//RPS-46 Ignore NA Line Items.
|
||||
//Removed on 05/20. We are seeing more $0DB lines than NA lines and they are getting incorrectly ignored.
|
||||
// if (
|
||||
// jobline.part_type === "PAN" &&
|
||||
// jobline.price_j === true &&
|
||||
// jobline.db_price === 0
|
||||
// ) {
|
||||
// jobline.ignore = true;
|
||||
// }
|
||||
|
||||
//RPS-39 - OEM ON OEM SAVINGS
|
||||
//Verified on 08/24/21
|
||||
if (
|
||||
jobline.part_type === "PAN" &&
|
||||
jobline.db_price !== jobline.act_price &&
|
||||
jobline.db_price !== 0
|
||||
) {
|
||||
jobline.db_price = jobline.act_price;
|
||||
}
|
||||
|
||||
//Remove all $0DB line items 02/17/2022.
|
||||
if (
|
||||
(jobline.part_type === "PAA" ||
|
||||
jobline.part_type === "PAL" ||
|
||||
jobline.part_type === "PAN") &&
|
||||
jobline.db_price === 0
|
||||
) {
|
||||
jobline.ignore = true;
|
||||
}
|
||||
|
||||
//05/20
|
||||
//We’ll have to apply a rule that will not count any A/M, Reman or new part price that is manually changed to $0.00.
|
||||
//Only recycled parts that are changed to $0.00 can be counted towards RPS.
|
||||
// This is separate from $0.00 DB prices that need to be updated to the manually entered price.
|
||||
//Verified on 08/24/21
|
||||
if (
|
||||
jobline.part_type !== "PAL" &&
|
||||
jobline.act_price === 0 &&
|
||||
jobline.price_j
|
||||
) {
|
||||
log.info(
|
||||
`Jobline '${jobline.line_desc}' ignored because it was manually changed to 0..`
|
||||
);
|
||||
jobline.ignore = true;
|
||||
}
|
||||
|
||||
//08/24/21 - Norm to take as action item to determine what the final set of rules were per Derek.
|
||||
// if (jobline.glass_flag && jobline.part_type !== "PAL") {
|
||||
// jobline.ignore = true;
|
||||
|
||||
// }
|
||||
|
||||
//09/2021 Detect NAGS lines using RegEx for the Part Number
|
||||
if (
|
||||
jobline.line_desc.toLowerCase().includes("glass") &&
|
||||
jobline.oem_partno.match(`[A-Z]{2}[0-9]{5,6}[A-Z]{3}`)
|
||||
) {
|
||||
console.log(jobline.line_desc, "NAGS Line ignored");
|
||||
jobline.ignore = true;
|
||||
}
|
||||
|
||||
//Logic Based Exclusions.
|
||||
//Verified on 08/24/21
|
||||
if (
|
||||
!jobline.part_type ||
|
||||
jobline.db_ref.startsWith("900") ||
|
||||
jobline.line_desc.toLowerCase().startsWith("urethane") ||
|
||||
jobline.line_desc.toLowerCase().startsWith("w/shield adhesive") ||
|
||||
//jobline.line_desc.toLowerCase().includes("wheel") || Removed as a part of RPS-41
|
||||
jobline.line_desc.toLowerCase().includes("tire") ||
|
||||
jobline.line_desc.toLowerCase().startsWith("hazardous") ||
|
||||
jobline.line_desc.toLowerCase().startsWith("detail") ||
|
||||
jobline.line_desc.toLowerCase().startsWith("clean") ||
|
||||
// jobline.part_type.toUpperCase() === "PAG" ||Removed for RPS-43.
|
||||
jobline.part_type.toUpperCase() === "PAS" ||
|
||||
jobline.part_type.toUpperCase() === "PASL" ||
|
||||
jobline.part_type.toUpperCase() === "PAE"
|
||||
//jobline.glass_flag === true //Removed for RPS-43.
|
||||
) {
|
||||
jobline.ignore = true;
|
||||
}
|
||||
|
||||
//Check to see if this is a discount line i.e. a 900511
|
||||
if (jobline.db_ref === "900511" && jobline.prt_dsmk_p !== 50) {
|
||||
jobline.ignore = false;
|
||||
//Calculate the discount to be added as a negative.
|
||||
jobline.act_price = jobline.prt_dsmk_m;
|
||||
//Check to see if the parent line has a discount.
|
||||
const parentLine = joblines.find(
|
||||
(r) => parseInt(r.unq_seq) === jobline.line_ref
|
||||
);
|
||||
if (parentLine && parentLine.ignore) {
|
||||
jobline.ignore = true;
|
||||
}
|
||||
}
|
||||
|
||||
//RPS-42 Dynamic Inclusion or Exclusion of Lines based on RPS-EXCLUDE or RPS.
|
||||
if (jobline.oem_partno.toLowerCase().includes("/rps-exclude")) {
|
||||
jobline.ignore = true;
|
||||
} else if (jobline.oem_partno.toLowerCase().includes("/rps")) {
|
||||
jobline.ignore = false;
|
||||
}
|
||||
|
||||
delete jobline.prt_dsmk_m; //Delete price markup for wheel repair
|
||||
delete jobline.prt_dsmk_p;
|
||||
delete jobline.glass_flag;
|
||||
delete jobline.price_j;
|
||||
delete jobline.line_ref;
|
||||
return jobline;
|
||||
});
|
||||
|
||||
@@ -526,3 +366,130 @@ async function DecodeLinFile(extensionlessFilePath) {
|
||||
|
||||
exports.DecodeEstimate = DecodeEstimate;
|
||||
exports.ImportJob = ImportJob;
|
||||
|
||||
function V1Ruleset(jobline) {
|
||||
//These set of MPI rules are valid until April 1, 2023.
|
||||
log.info(`Using V1 ruleset for line scanning.`);
|
||||
|
||||
//Wheel Repair Pricing PRS-82
|
||||
//Verified on 08/24/21
|
||||
if (
|
||||
jobline.part_type === "PAN" &&
|
||||
jobline.line_desc.toLowerCase().includes("wheel") &&
|
||||
Math.abs(jobline.prt_dsmk_m) ===
|
||||
Math.round((jobline.act_price / 2) * 100) / 100
|
||||
) {
|
||||
log.info(`Jobline '${jobline.line_desc}' ignored due to wheel repair.`);
|
||||
jobline.ignore = true;
|
||||
}
|
||||
|
||||
//RPS-39 - OEM ON OEM SAVINGS
|
||||
//Verified on 08/24/21
|
||||
if (
|
||||
jobline.part_type === "PAN" &&
|
||||
jobline.db_price !== jobline.act_price &&
|
||||
jobline.db_price !== 0
|
||||
) {
|
||||
jobline.db_price = jobline.act_price;
|
||||
}
|
||||
|
||||
//Remove all $0DB line items 02/17/2022.
|
||||
if (
|
||||
(jobline.part_type === "PAA" ||
|
||||
jobline.part_type === "PAL" ||
|
||||
jobline.part_type === "PAN") &&
|
||||
jobline.db_price === 0
|
||||
) {
|
||||
jobline.ignore = true;
|
||||
}
|
||||
|
||||
//05/20
|
||||
//We’ll have to apply a rule that will not count any A/M, Reman or new part price that is manually changed to $0.00.
|
||||
//Only recycled parts that are changed to $0.00 can be counted towards RPS.
|
||||
// This is separate from $0.00 DB prices that need to be updated to the manually entered price.
|
||||
//Verified on 08/24/21
|
||||
if (
|
||||
jobline.part_type !== "PAL" &&
|
||||
jobline.act_price === 0 &&
|
||||
jobline.price_j
|
||||
) {
|
||||
log.info(
|
||||
`Jobline '${jobline.line_desc}' ignored because it was manually changed to 0..`
|
||||
);
|
||||
jobline.ignore = true;
|
||||
}
|
||||
|
||||
//09/2021 Detect NAGS lines using RegEx for the Part Number
|
||||
if (
|
||||
jobline.line_desc.toLowerCase().includes("glass") &&
|
||||
jobline.oem_partno.match(`[A-Z]{2}[0-9]{5,6}[A-Z]{3}`)
|
||||
) {
|
||||
console.log(jobline.line_desc, "NAGS Line ignored");
|
||||
jobline.ignore = true;
|
||||
}
|
||||
|
||||
//Logic Based Exclusions.
|
||||
//Verified on 08/24/21
|
||||
if (
|
||||
!jobline.part_type ||
|
||||
jobline.db_ref.startsWith("900") ||
|
||||
jobline.line_desc.toLowerCase().startsWith("urethane") ||
|
||||
jobline.line_desc.toLowerCase().startsWith("w/shield adhesive") ||
|
||||
//jobline.line_desc.toLowerCase().includes("wheel") || Removed as a part of RPS-41
|
||||
jobline.line_desc.toLowerCase().includes("tire") ||
|
||||
jobline.line_desc.toLowerCase().startsWith("hazardous") ||
|
||||
jobline.line_desc.toLowerCase().startsWith("detail") ||
|
||||
jobline.line_desc.toLowerCase().startsWith("clean") ||
|
||||
// jobline.part_type.toUpperCase() === "PAG" ||Removed for RPS-43.
|
||||
jobline.part_type.toUpperCase() === "PAS" ||
|
||||
jobline.part_type.toUpperCase() === "PASL" ||
|
||||
jobline.part_type.toUpperCase() === "PAE"
|
||||
//jobline.glass_flag === true //Removed for RPS-43.
|
||||
) {
|
||||
jobline.ignore = true;
|
||||
}
|
||||
|
||||
//Check to see if this is a discount line i.e. a 900511
|
||||
if (jobline.db_ref === "900511" && jobline.prt_dsmk_p !== 50) {
|
||||
jobline.ignore = false;
|
||||
//Calculate the discount to be added as a negative.
|
||||
jobline.act_price = jobline.prt_dsmk_m;
|
||||
//Check to see if the parent line has a discount.
|
||||
const parentLine = joblines.find(
|
||||
(r) => parseInt(r.unq_seq) === jobline.line_ref
|
||||
);
|
||||
if (parentLine && parentLine.ignore) {
|
||||
jobline.ignore = true;
|
||||
}
|
||||
}
|
||||
|
||||
//RPS-42 Dynamic Inclusion or Exclusion of Lines based on RPS-EXCLUDE or RPS.
|
||||
if (jobline.oem_partno.toLowerCase().includes("/rps-exclude")) {
|
||||
jobline.ignore = true;
|
||||
} else if (jobline.oem_partno.toLowerCase().includes("/rps")) {
|
||||
jobline.ignore = false;
|
||||
}
|
||||
|
||||
delete jobline.prt_dsmk_m; //Delete price markup for wheel repair
|
||||
delete jobline.prt_dsmk_p;
|
||||
delete jobline.glass_flag;
|
||||
delete jobline.price_j;
|
||||
delete jobline.line_ref;
|
||||
return jobline;
|
||||
}
|
||||
|
||||
function V2Ruleset(jobline) {
|
||||
//This is the rules psot 04/01/2023. They are a further restrictive set, and therefore
|
||||
//V1 rules are called first, and then run through this filter as well.
|
||||
|
||||
V1Ruleset(jobline);
|
||||
|
||||
//Remove any glass related items.
|
||||
if (jobline.part_type.toUpperCase() === "PAG") {
|
||||
jobline.ignore = true;
|
||||
}
|
||||
|
||||
//ADAS Part Line
|
||||
|
||||
return jobline;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ insert_permissions:
|
||||
- loss_date
|
||||
- ownr_fn
|
||||
- ownr_ln
|
||||
- requires_reimport
|
||||
- ro_number
|
||||
- updated_at
|
||||
- v_age
|
||||
@@ -60,6 +61,7 @@ select_permissions:
|
||||
- loss_date
|
||||
- ownr_fn
|
||||
- ownr_ln
|
||||
- requires_reimport
|
||||
- ro_number
|
||||
- updated_at
|
||||
- v_age
|
||||
@@ -92,6 +94,7 @@ update_permissions:
|
||||
- loss_date
|
||||
- ownr_fn
|
||||
- ownr_ln
|
||||
- requires_reimport
|
||||
- ro_number
|
||||
- updated_at
|
||||
- v_age
|
||||
|
||||
@@ -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 "requires_reimport" boolean
|
||||
-- not null default 'false';
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."jobs" add column "requires_reimport" boolean
|
||||
not null default 'false';
|
||||
@@ -1,16 +1,15 @@
|
||||
import { WarningOutlined } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { DatePicker, message, Spin } from "antd";
|
||||
import { DatePicker, message, notification, Spin } from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { UPDATE_JOB } from "../../../graphql/jobs.queries";
|
||||
import ipcTypes from "../../../ipc.types";
|
||||
import { CalculateVehicleAge } from "../../../ipc/ipc-estimate-utils";
|
||||
import { DateFormat } from "../../../util/constants";
|
||||
import { ChangeOfRuleSet, DateFormat } from "../../../util/constants";
|
||||
const { ipcRenderer } = window;
|
||||
|
||||
export default function CloseDateDisplayMolecule({ job, jobId, close_date }) {
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [value, setValue] = useState(moment(close_date));
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [updateJob] = useMutation(UPDATE_JOB);
|
||||
@@ -21,8 +20,17 @@ export default function CloseDateDisplayMolecule({ job, jobId, close_date }) {
|
||||
});
|
||||
setLoading(true);
|
||||
setValue(newDate);
|
||||
console.log("New R4P Date", newDate);
|
||||
//Recalculate vehicle age.
|
||||
const requires_reimport = ChangeOfRuleSet({
|
||||
prevDateMoment: job.close_date ? moment(job.close_date) : moment(),
|
||||
newDateMoment: newDate ? newDate : moment(),
|
||||
});
|
||||
if (requires_reimport) {
|
||||
notification.open({
|
||||
type: "warning",
|
||||
message:
|
||||
"Changing the R4P date has changed the applicable ruleset. Please re-import the job for accurate scoring.",
|
||||
});
|
||||
}
|
||||
|
||||
const result = await updateJob({
|
||||
variables: {
|
||||
@@ -30,6 +38,7 @@ export default function CloseDateDisplayMolecule({ job, jobId, close_date }) {
|
||||
job: {
|
||||
close_date: newDate,
|
||||
v_age: CalculateVehicleAge({ ...job, close_date: newDate }),
|
||||
requires_reimport: job.requires_reimport || requires_reimport,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -42,23 +51,15 @@ export default function CloseDateDisplayMolecule({ job, jobId, close_date }) {
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
if (editMode)
|
||||
return (
|
||||
<div onBlur={() => setEditMode(false)}>
|
||||
<DatePicker
|
||||
value={value && value.isValid() ? value : null}
|
||||
onChange={handleChange}
|
||||
format='MM/DD/YYYY'
|
||||
/>
|
||||
{loading && <Spin size="small" />}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={{ cursor: "pointer" }} onClick={() => setEditMode(true)}>
|
||||
{value && value.isValid() ? (
|
||||
value.format(DateFormat)
|
||||
) : (
|
||||
<div>
|
||||
<DatePicker
|
||||
value={value && value.isValid() ? value : null}
|
||||
onChange={handleChange}
|
||||
format={DateFormat}
|
||||
/>
|
||||
{loading && <Spin size="small" />}
|
||||
{value && !value.isValid() && (
|
||||
<div>
|
||||
<span>No date set.</span>
|
||||
<WarningOutlined style={{ marginLeft: ".5rem", color: "orange" }} />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Descriptions, PageHeader, Skeleton, Tooltip } from "antd";
|
||||
import { Alert, Descriptions, PageHeader, Skeleton, Tooltip } from "antd";
|
||||
import React from "react";
|
||||
import CurrencyFormatterAtom from "../../atoms/currency-formatter/currency-formatter.atom";
|
||||
import ErrorResultAtom from "../../atoms/error-result/error-result.atom";
|
||||
@@ -27,7 +27,21 @@ export default function JobsDetailDescriptionMolecule({ loading, job }) {
|
||||
ghost={false}
|
||||
title={job.clm_no}
|
||||
subTitle={job.ins_co_nm}
|
||||
extra={[<DeleteJobAtom key="delete" jobId={job.id} />]}
|
||||
extra={[
|
||||
job.requires_reimport && (
|
||||
<Tooltip
|
||||
key="reimport"
|
||||
title="There are different rules that apply for claims submitted in different periods. In order to ensure accurate calculation, you must import this job again to determine which parts are eligible for parts autonomy. This happened because the R4P date was changed between these periods."
|
||||
>
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
message="A change in R4P date changed the applicable rules. Reimport this job for accurate scoring."
|
||||
/>
|
||||
</Tooltip>
|
||||
),
|
||||
<DeleteJobAtom key="delete" jobId={job.id} />,
|
||||
]}
|
||||
>
|
||||
<Descriptions column={{ xxl: 5, xl: 4, lg: 3, md: 3, sm: 2, xs: 1 }}>
|
||||
<Descriptions.Item label="Owner">{`${job.ownr_fn} ${job.ownr_ln}`}</Descriptions.Item>
|
||||
@@ -41,12 +55,6 @@ export default function JobsDetailDescriptionMolecule({ loading, job }) {
|
||||
<JobGroupMolecule jobId={job.id} group={job.group} job={job} />
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Age">{job.v_age}</Descriptions.Item>
|
||||
<Descriptions.Item label="Loss Date">
|
||||
{job.loss_date
|
||||
? moment(job.loss_date).format(DateFormat)
|
||||
: "No Loss Date"}
|
||||
</Descriptions.Item>
|
||||
|
||||
<Descriptions.Item
|
||||
label={
|
||||
<Tooltip title="This is the date you will submit for payment from MPI. This should always be the latest date in the case of supplements.">
|
||||
@@ -60,7 +68,6 @@ export default function JobsDetailDescriptionMolecule({ loading, job }) {
|
||||
close_date={job.close_date}
|
||||
/>
|
||||
</Descriptions.Item>
|
||||
|
||||
<Descriptions.Item label="Last Updated">
|
||||
<TimeAgoFormatter>{job.updated_at}</TimeAgoFormatter>
|
||||
</Descriptions.Item>
|
||||
@@ -69,6 +76,11 @@ export default function JobsDetailDescriptionMolecule({ loading, job }) {
|
||||
job.joblines.filter((i) => !i.ignore && i.db_ref !== "900511")
|
||||
.length}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Loss Date">
|
||||
{job.loss_date
|
||||
? moment(job.loss_date).format(DateFormat)
|
||||
: "No Loss Date"}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</PageHeader>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { WarningOutlined } from "@ant-design/icons";
|
||||
import { WarningOutlined, CloudUploadOutlined } from "@ant-design/icons";
|
||||
import { List } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
@@ -63,6 +63,12 @@ export function JobsListItemMolecule({
|
||||
style={{ marginLeft: ".5rem", color: "orange" }}
|
||||
/>
|
||||
)}
|
||||
{item.requires_reimport && (
|
||||
<CloudUploadOutlined
|
||||
title="A change in R4P date changed the applicable rules. Import this job again for accurate scoring."
|
||||
style={{ marginLeft: ".5rem", color: "tomato" }}
|
||||
/>
|
||||
)}
|
||||
</strong>
|
||||
<span style={{ fontStyle: "italic" }}>
|
||||
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
|
||||
|
||||
@@ -34,7 +34,7 @@ export function ReportingDatesMolecule({ queryReportingData }) {
|
||||
rules={[{ type: "array", required: true }]}
|
||||
>
|
||||
<DatePicker.RangePicker
|
||||
format="YYYY-MM-DD"
|
||||
format="MM/DD/YYYY"
|
||||
ranges={{
|
||||
Today: [moment(), moment()],
|
||||
"Last 14 days": [moment().subtract(14, "days"), moment()],
|
||||
@@ -45,6 +45,7 @@ export function ReportingDatesMolecule({ queryReportingData }) {
|
||||
moment().startOf("month").subtract(1, "month"),
|
||||
moment().startOf("month").subtract(1, "month").endOf("month"),
|
||||
],
|
||||
|
||||
"This Month": [
|
||||
moment().startOf("month"),
|
||||
moment().endOf("month"),
|
||||
@@ -64,6 +65,10 @@ export function ReportingDatesMolecule({ queryReportingData }) {
|
||||
.add(1, "quarter")
|
||||
.subtract(1, "day"),
|
||||
],
|
||||
"Last 3 Months": [
|
||||
moment().startOf("month").subtract(3, "month"),
|
||||
moment().startOf("month").subtract(1, "month").endOf("month"),
|
||||
],
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Input, Table } from "antd";
|
||||
import { CloudUploadOutlined } from "@ant-design/icons";
|
||||
import { Alert, Input, Space, Table } from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { useMemo } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -41,7 +43,15 @@ export function ReportingJobsListMolecule({
|
||||
key: "clm_no",
|
||||
render: (text, record) => (
|
||||
<Link onClick={() => setSelectedJobId(record.id)} to={"/"}>
|
||||
{text}
|
||||
<Space>
|
||||
{text}
|
||||
{record.requires_reimport && (
|
||||
<CloudUploadOutlined
|
||||
title="A change in R4P date changed the applicable rules. Import this job again for accurate scoring."
|
||||
style={{ marginLeft: ".5rem", color: "tomato" }}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
|
||||
@@ -146,8 +156,20 @@ export function ReportingJobsListMolecule({
|
||||
)
|
||||
: reportData;
|
||||
|
||||
const ErroredRecords = useMemo(
|
||||
() => reportData.filter((r) => r.requires_reimport).length,
|
||||
[reportData]
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ErroredRecords > 0 && (
|
||||
<Alert
|
||||
showIcon
|
||||
message={`${ErroredRecords} job(s) may have innacurate data due to R4P and applicable rule changes. Please import them again.`}
|
||||
type="warning"
|
||||
/>
|
||||
)}
|
||||
<Table
|
||||
title={() => (
|
||||
<Input.Search
|
||||
@@ -177,7 +199,9 @@ export function ReportingJobsListMolecule({
|
||||
<Table.Summary.Cell index={0}>
|
||||
<strong>Totals</strong>
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell index={1}>{`${data.length} record(s)`}</Table.Summary.Cell>
|
||||
<Table.Summary.Cell
|
||||
index={1}
|
||||
>{`${data.length} record(s)`}</Table.Summary.Cell>
|
||||
<Table.Summary.Cell index={2}></Table.Summary.Cell>
|
||||
<Table.Summary.Cell index={3}></Table.Summary.Cell>
|
||||
<Table.Summary.Cell index={4}></Table.Summary.Cell>
|
||||
|
||||
@@ -60,7 +60,7 @@ export function ReportingTotalsStatsMolecule({ reportingLoading, scoreCard }) {
|
||||
valueStyle={{
|
||||
color: scoreCard.variancePc < 0 ? "tomato" : "seagreen",
|
||||
}}
|
||||
value={(scoreCard.variancePc * 100).toFixed(2)}
|
||||
value={((scoreCard.variancePc || 0) * 100).toFixed(2)}
|
||||
suffix="%"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -28,6 +28,7 @@ export const QUERY_ALL_JOBS_PAGINATED = gql`
|
||||
clm_no
|
||||
close_date
|
||||
updated_at
|
||||
requires_reimport
|
||||
}
|
||||
jobs_aggregate {
|
||||
aggregate {
|
||||
@@ -63,6 +64,7 @@ export const SEARCH_JOBS_PAGINATED = gql`
|
||||
clm_no
|
||||
updated_at
|
||||
close_date
|
||||
requires_reimport
|
||||
}
|
||||
search_jobs_aggregate(
|
||||
args: { enddate: $endDate, search: $search, startdate: $startDate }
|
||||
@@ -97,6 +99,7 @@ export const QUERY_JOB_BY_PK = gql`
|
||||
loss_date
|
||||
close_date
|
||||
updated_at
|
||||
requires_reimport
|
||||
joblines(order_by: { line_no: asc }) {
|
||||
id
|
||||
line_no
|
||||
@@ -157,6 +160,7 @@ export const UPDATE_JOB = gql`
|
||||
v_age
|
||||
group
|
||||
group_verified
|
||||
requires_reimport
|
||||
joblines(order_by: { unq_seq: asc }) {
|
||||
id
|
||||
act_price
|
||||
|
||||
@@ -28,6 +28,7 @@ export const REPORTING_GET_JOBS = gql`
|
||||
v_model_yr
|
||||
v_vin
|
||||
v_type
|
||||
requires_reimport
|
||||
joblines {
|
||||
act_price
|
||||
db_price
|
||||
|
||||
@@ -26,7 +26,6 @@ export function CalculateVehicleAge(job) {
|
||||
const closeDate = moment(job.close_date);
|
||||
const lossDate = moment(job.loss_date);
|
||||
let ret;
|
||||
console.log(job);
|
||||
|
||||
if (closeDate.isSameOrAfter(moment("2023-04-01"))) {
|
||||
//Post April 2023 rules where the age is calculated based on loss date.
|
||||
@@ -35,7 +34,7 @@ export function CalculateVehicleAge(job) {
|
||||
"Using post 0423 ruleset to calculate vehicle age for job.",
|
||||
job
|
||||
);
|
||||
ret = vehicleYr.diff(lossDate, "years");
|
||||
ret = Math.max(0, lossDate.year() - vehicleYr);
|
||||
} else {
|
||||
//Pre-April 2023 rules where the age was calculated based on model year, not loss date.
|
||||
ipcRenderer.send(
|
||||
@@ -64,6 +63,7 @@ export async function UpsertEstimate(job) {
|
||||
|
||||
job = {
|
||||
...job,
|
||||
requires_reimport: false,
|
||||
v_mileage: (job.v_mileage !== "" && job.v_mileage) || null,
|
||||
v_type: DetermineVehicleType(job),
|
||||
v_age: CalculateVehicleAge({
|
||||
|
||||
@@ -1,31 +1,28 @@
|
||||
import { message, notification } from "antd";
|
||||
import { message } from "antd";
|
||||
import moment from "moment";
|
||||
//import LogRocket from "logrocket";
|
||||
import { all, call, put, takeLatest, delay } from "redux-saga/effects";
|
||||
import { all, call, delay, put, takeLatest } from "redux-saga/effects";
|
||||
import {
|
||||
auth,
|
||||
getCurrentUser,
|
||||
updateCurrentUser,
|
||||
updateCurrentUser
|
||||
} from "../../firebase/firebase.utils";
|
||||
import {
|
||||
QUERY_BODYSHOP,
|
||||
QUERY_NOTIFICATIONS,
|
||||
QUERY_NOTIFICATIONS
|
||||
} from "../../graphql/bodyshop.queries";
|
||||
import client from "../../graphql/GraphQLClient";
|
||||
import { UPSERT_USER } from "../../graphql/user.queries";
|
||||
import ipcTypes from "../../ipc.types";
|
||||
import {
|
||||
sendPasswordResetFailure,
|
||||
checkForNotification, sendPasswordResetFailure,
|
||||
sendPasswordResetSuccess,
|
||||
setBodyshop,
|
||||
signInFailure,
|
||||
setBodyshop, setNotification, signInFailure,
|
||||
signInSuccess,
|
||||
signOutFailure,
|
||||
signOutSuccess,
|
||||
unauthorizedUser,
|
||||
updateUserDetailsSuccess,
|
||||
checkForNotification,
|
||||
setNotification,
|
||||
updateUserDetailsSuccess
|
||||
} from "./user.actions";
|
||||
import UserActionTypes from "./user.types";
|
||||
|
||||
|
||||
@@ -1 +1,38 @@
|
||||
import moment from "moment";
|
||||
|
||||
export const DateFormat = "MM/DD/yyyy";
|
||||
|
||||
export function ChangeOfRuleSet({
|
||||
prevDateMoment = moment(),
|
||||
newDateMoment = moment(),
|
||||
}) {
|
||||
console.log("🚀 ~ file: constants.js ~ line 9 ~ newDateMoment", newDateMoment)
|
||||
console.log("🚀 ~ file: constants.js ~ line 9 ~ prevDateMoment", prevDateMoment)
|
||||
|
||||
|
||||
//Define the rule periods.
|
||||
const V1 = {
|
||||
title: "V1",
|
||||
range: [moment("2010-01-01"), moment("2023-04-01")],
|
||||
};
|
||||
const V2 = {
|
||||
title: "V2",
|
||||
range: [moment("2023-04-01"), moment("2040-01-01")],
|
||||
}; //Arbitrarily long away date.
|
||||
|
||||
const RuleSets = [V1, V2];
|
||||
|
||||
const prevRuleSet = RuleSets.find(
|
||||
(r) =>
|
||||
prevDateMoment.isSameOrAfter(r.range[0]) &&
|
||||
prevDateMoment.isSameOrBefore(r.range[1])
|
||||
);
|
||||
|
||||
const newRuleSet = RuleSets.find(
|
||||
(r) =>
|
||||
newDateMoment.isSameOrAfter(r.range[0]) &&
|
||||
newDateMoment.isSameOrBefore(r.range[1])
|
||||
);
|
||||
|
||||
return prevRuleSet?.title !== newRuleSet?.title;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user