Initial work for claims clerk.

This commit is contained in:
Patrick Fic
2025-02-19 10:31:56 -08:00
parent 4e663977b3
commit d044cce054
16 changed files with 330 additions and 55 deletions

View File

@@ -0,0 +1,47 @@
import { Badge, Card, Collapse, Skeleton, Space } from "antd";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import ErrorResultAtom from "../../atoms/error-result/error-result.atom";
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(JobsClaimClerk);
export function JobsClaimClerk({ loading, job }) {
// const { token } = theme.useToken();
if (loading) return <Skeleton active />;
if (!job)
return <ErrorResultAtom title="Error displaying job." errorMessage="It looks like this job doesn't exist." />;
const alertData = job.joblines
.map((jobline) =>
jobline.alerts?.map((alert, idx) => ({
key: `${jobline.line_no}-${alert.key}-${idx}`,
label: `Line ${jobline.line_no}: ${alert.key}`,
children: <div dangerouslySetInnerHTML={{ __html: alert.alert }} />
// style: {
// backgroundColor: token.colorErrorBgHover
//
}))
)
.flat();
return (
<Card
title={
<Space>
Claims Clerk AI <Badge count={alertData.length}></Badge>
</Space>
}
bordered={false}
>
<Collapse items={alertData} bordered={false} />
</Card>
);
}

View File

@@ -1,5 +1,5 @@
import { CalculatorOutlined } from "@ant-design/icons";
import { Input, Table } from "antd";
import { Input, Space, Table, Tag } from "antd";
import React, { useState } from "react";
import ipcTypes from "../../../ipc.types";
import { alphaSort } from "../../../util/sorters";
@@ -8,6 +8,7 @@ import ErrorResultAtom from "../../atoms/error-result/error-result.atom";
import IgnoreJobLine from "../../atoms/ignore-job-line/ignore-job-line.atom";
import partTypeConverterAtom from "../../atoms/part-type-converter/part-type-converter.atom";
import PriceDiffPcFormatterAtom from "../../atoms/price-diff-pc-formatter/price-diff-pc-formatter.atom";
import { render } from "sass";
const { ipcRenderer } = window;
export default function JobLinesTableMolecule({ loading, job }) {
@@ -15,12 +16,7 @@ export default function JobLinesTableMolecule({ loading, job }) {
const [filters, setFilters] = useState({ ignore: ["false"] });
if (!job) {
return (
<ErrorResultAtom
title="Error Displaying Job Lines"
errorMessage="It looks like this job doesn't exist."
/>
);
return <ErrorResultAtom title="Error Displaying Job Lines" errorMessage="It looks like this job doesn't exist." />;
}
const { joblines } = job;
const columns = [
@@ -29,14 +25,14 @@ export default function JobLinesTableMolecule({ loading, job }) {
dataIndex: "line_no",
key: "line_no",
sorter: (a, b) => a.line_no - b.line_no,
width: "5%",
width: "5%"
},
{
title: "S#",
dataIndex: "line_ind",
key: "line_ind",
width: "5%",
sorter: (a, b) => alphaSort(a.line_ind, b.line_ind),
sorter: (a, b) => alphaSort(a.line_ind, b.line_ind)
},
{
title: "Line Description",
@@ -44,6 +40,14 @@ export default function JobLinesTableMolecule({ loading, job }) {
key: "line_desc",
width: "25%",
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
render: (text, record) => (
<Space wrap size={"small"}>
{record.line_desc}
{record.alerts &&
record.alerts.length > 0 &&
record.alerts.map((alert) => <Tag color="red">{alert.key}</Tag>)}
</Space>
)
},
{
title: "Part Type",
@@ -51,21 +55,21 @@ export default function JobLinesTableMolecule({ loading, job }) {
key: "part_type",
width: "5%",
sorter: (a, b) => alphaSort(a.part_type, b.part_type),
render: (text, record) => partTypeConverterAtom(text),
render: (text, record) => partTypeConverterAtom(text)
},
{
title: "Part Number",
dataIndex: "oem_partno",
key: "oem_partno",
width: "15%",
sorter: (a, b) => alphaSort(a.oem_partno, b.oem_partno),
sorter: (a, b) => alphaSort(a.oem_partno, b.oem_partno)
},
{
title: "Qty.",
dataIndex: "part_qty",
key: "part_qty",
width: "5%",
sorter: (a, b) => a.part_qty - b.part_qty,
sorter: (a, b) => a.part_qty - b.part_qty
},
{
title: "Database Price",
@@ -73,9 +77,7 @@ export default function JobLinesTableMolecule({ loading, job }) {
key: "db_price",
width: "10%",
sorter: (a, b) => a.db_price - b.db_price,
render: (text, record) => (
<CurrencyFormatterAtom>{record.db_price}</CurrencyFormatterAtom>
),
render: (text, record) => <CurrencyFormatterAtom>{record.db_price}</CurrencyFormatterAtom>
},
{
title: "Actual Price",
@@ -83,9 +85,7 @@ export default function JobLinesTableMolecule({ loading, job }) {
key: "act_price",
width: "10%",
sorter: (a, b) => a.act_price - b.act_price,
render: (text, record) => (
<CurrencyFormatterAtom>{record.act_price}</CurrencyFormatterAtom>
),
render: (text, record) => <CurrencyFormatterAtom>{record.act_price}</CurrencyFormatterAtom>
},
{
title: "Price Diff.",
@@ -93,9 +93,7 @@ export default function JobLinesTableMolecule({ loading, job }) {
key: "price_diff",
width: "10%",
sorter: (a, b) => a.price_diff - b.price_diff,
render: (text, record) => (
<CurrencyFormatterAtom>{record.price_diff}</CurrencyFormatterAtom>
),
render: (text, record) => <CurrencyFormatterAtom>{record.price_diff}</CurrencyFormatterAtom>
},
{
title: "Price Diff. %",
@@ -104,12 +102,8 @@ export default function JobLinesTableMolecule({ loading, job }) {
width: "10%",
sorter: (a, b) => a.price_diff_pc - b.price_diff_pc,
render: (text, record) => (
<PriceDiffPcFormatterAtom
price_diff_pc={record.price_diff_pc}
v_age={job.v_age}
group={job.group}
/>
),
<PriceDiffPcFormatterAtom price_diff_pc={record.price_diff_pc} v_age={job.v_age} group={job.group} />
)
},
{
title: <CalculatorOutlined />,
@@ -117,27 +111,17 @@ export default function JobLinesTableMolecule({ loading, job }) {
key: "ignore",
filters: [
{ text: "Eligible for RPS Calculation", value: false },
{ text: "Ineligible for RPS Calculation", value: true },
{ text: "Ineligible for RPS Calculation", value: true }
],
width: "5%",
filteredValue: filters.ignore || null,
onFilter: (value, record) => value === record.ignore,
render: (text, record) => (
<IgnoreJobLine
lineId={record.id}
ignore={record.ignore}
line_desc={record.line_desc}
/>
),
},
render: (text, record) => <IgnoreJobLine lineId={record.id} ignore={record.ignore} line_desc={record.line_desc} />
}
];
const data =
searchText !== ""
? joblines.filter((j) =>
j.line_desc.toLowerCase().includes(searchText.toLowerCase())
)
: joblines;
searchText !== "" ? joblines.filter((j) => j.line_desc.toLowerCase().includes(searchText.toLowerCase())) : joblines;
const handleChange = (pagination, filters, sorter) => {
setFilters(filters);
@@ -150,7 +134,7 @@ export default function JobLinesTableMolecule({ loading, job }) {
onSearch={(val) => {
ipcRenderer.send(ipcTypes.app.toMain.track, {
event: "JOB_LINES_SEARCH",
query: val,
query: val
});
setSearchText(val);
}}
@@ -167,7 +151,7 @@ export default function JobLinesTableMolecule({ loading, job }) {
onChange={handleChange}
scroll={{
x: true,
y: "20rem",
y: "20rem"
}}
/>
</div>

View File

@@ -1,5 +1,5 @@
import { CloudUploadOutlined } from "@ant-design/icons";
import { Alert, Input, Space, Table } from "antd";
import { ExclamationCircleOutlined } from "@ant-design/icons";
import { Alert, Badge, Input, Space, Table, Tooltip } from "antd";
import React, { useMemo, useState } from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
@@ -9,9 +9,10 @@ import { setSelectedJobId } from "../../../redux/application/application.actions
import { selectReportData, selectReportLoading, selectScorecard } from "../../../redux/reporting/reporting.selectors";
import dayjs from "../../../util/day.js";
import { alphaSort } from "../../../util/sorters";
import RequiresReimportDisplay from "../../atoms/requires-reimport/requires-reimport.atom.jsx";
import VehicleGroupAlertAtom from "../../atoms/vehicle-group-alert/vehicle-group-alert.atom";
import GroupVerifySwitch from "../group-verify-switch/group-verify-switch.component";
import RequiresReimportDisplay from "../../atoms/requires-reimport/requires-reimport.atom.jsx";
import JobsClaimsClerkMolecule from "../jobs-claims-clerk/jobs-claims-clerk.molecule.jsx";
const { ipcRenderer } = window;
@@ -36,6 +37,12 @@ export function ReportingJobsListMolecule({ scoreCard, reportingLoading, reportD
<Link onClick={() => setSelectedJobId(record.id)} to={"/"}>
<Space>
{text}
{record.alerts && record.alerts.length > 0 && (
<Tooltip title="Claims Clerk AI has detected possible issues with this estimate. Review the estimate to ensure you are following MPI guidelines.">
<Badge count={record.alerts.length} size="small" />
</Tooltip>
)}
<RequiresReimportDisplay job={record} />
</Space>
</Link>
@@ -174,6 +181,10 @@ export function ReportingJobsListMolecule({ scoreCard, reportingLoading, reportD
size="small"
pagination={false}
dataSource={data}
expandable={{
expandedRowRender: (record) => <JobsClaimsClerkMolecule job={record} />,
rowExpandable: (record) => record.alerts && record.alerts.length > 0
}}
scroll={{
x: true
}}

View File

@@ -12,6 +12,7 @@ import JobsDetailDescriptionMolecule from "../../molecules/jobs-detail-descripti
import JobsLinesTableMolecule from "../../molecules/jobs-lines-table/jobs-lines-table.molecule";
import JobsTargetsStatsMolecule from "../../molecules/jobs-targets-stats/jobs-targets-stats.molecule";
import "./jobs-detail.organism.styles.scss";
import JobsClaimClerk from "../../molecules/jobs-claims-clerk/jobs-claims-clerk.molecule";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -62,6 +63,9 @@ export function JobsDetailOrganism({ selectedJobId, setSelectedJobTargetPc }) {
<Card>
<JobsLinesTableMolecule loading={loading} job={data ? data.jobs_by_pk : {}} />
</Card>
<Card>
<JobsClaimClerk loading={loading} job={data ? data.jobs_by_pk : null} />
</Card>
<Card>
<JobsTargetsStatsMolecule loading={loading} job={data ? data.jobs_by_pk : null} />
<div

View File

@@ -117,6 +117,7 @@ export const QUERY_JOB_BY_PK = gql`
price_diff_pc
ignore
db_ref
alerts
}
}
}

View File

@@ -47,6 +47,7 @@ export const REPORTING_GET_JOBS = gql`
id
db_ref
unq_seq
alerts
}
}
}

View File

@@ -12,12 +12,6 @@
"EXCURSION",
"EXPLORER LIMITED",
"EXPLORER PLATINUM ECOBOOST",
"EXPLORER SPORT TRAC",
"EXPLORER SPORT TRAC ADRENAL V8",
"EXPLORER SPORT TRAC LIMITED",
"EXPLORER SPORT TRAC LIMITED V8",
"EXPLORER SPORT TRAC XLT",
"EXPLORER SPORT TRAC XLT V8",
"EXPLORER XLT",
"FLEX",
"FLEX SE",

View File

@@ -168,9 +168,23 @@ export function* handleCalculateScoreCard({ payload: jobs }) {
jobRpsPc: isNaN(jobRpsPc) ? -1 : jobRpsPc
});
const jobAlerts = job.joblines
.map((jobline) =>
jobline.alerts?.map((alert, idx) => ({
key: idx,
label: `Line ${jobline.line_no}: ${alert.key}`,
children: alert.alert
// style: {
// backgroundColor: token.colorErrorBgHover
// }
}))
)
.flat();
//sum db price * percentage expected.
return {
...job,
alerts: jobAlerts,
actPriceSum,
jobRpsDollars,
dbPriceSum,