UI updates for audit.

This commit is contained in:
Patrick Fic
2024-05-24 15:12:25 -07:00
parent 019faf1411
commit 020eccc762
10 changed files with 202 additions and 83 deletions

View File

@@ -15,45 +15,60 @@ ipcMain.on(ipcTypes.default.audit.toMain.browseForFile, async (event, { sheetNam
});
if (!result.canceled) {
try {
var obj = xlsx.parse(result.filePaths[0], { cellDates: true });
store.set("auditFilePath", result.filePaths);
var obj = xlsx.parse(result.filePaths[0], { cellDates: true }); // parses a file
const detailSheet = obj.find((sheet) => sheet.name === sheetName);
const claimsArray = [];
let foundHeaderRow, foundTotalRow;
detailSheet.data.forEach((line) => {
//Check the first element. If it's claim number, we have our header row. the next one is important.
if (!foundHeaderRow && line[0] === "Claim Number") {
foundHeaderRow = true;
} else if (foundHeaderRow && !foundTotalRow && line[0] && line[0] !== "Grand Total") {
//Add it to the array
const row = {
clm_no: line[0].startsWith("00") ? line[0].slice(2) : line[0],
close_date: line[1],
v_model_yr: line[3],
v_makedesc: line[4],
v_model: line[5],
under20kmiles: line[6],
pan_total: line[7],
paa_total: line[8],
pal_total: line[9],
pam_total: line[10],
eligible_db_price_total: Math.round((line[11] + Number.EPSILON) * 100) / 100,
eligible_act_price_total: Math.round((line[12] + Number.EPSILON) * 100) / 100,
expected_rps_dollars: Math.round((line[15] + Number.EPSILON) * 100) / 100,
actual_rps_dollars: Math.round((line[16] + Number.EPSILON) * 100) / 100
};
claimsArray.push(row);
} else {
// foundTotalRow = true;
}
event.sender.send(ipcTypes.default.audit.toRenderer.auditFilePath, {
filePath: result.filePaths[0],
sheets: obj.map((sheet) => sheet.name)
});
event.sender.send(ipcTypes.default.audit.toRenderer.auditClaimsArray, claimsArray);
} catch (error) {
console.log("ot some sort of err", error);
console.log("Got some sort of err", error);
log.error("Error when trying to read audit xlsx file", error);
event.sender.send(ipcTypes.default.audit.toRenderer.auditError, error.message);
event.sender.send(ipcTypes.default.audit.toRenderer.auditError, error.meFssage);
}
}
});
ipcMain.on(ipcTypes.default.audit.toMain.runAudit, async (event, { sheetName }) => {
try {
const filePaths = store.get("auditFilePath");
var obj = xlsx.parse(filePaths[0], { cellDates: true }); // parses a file
const detailSheet = obj.find((sheet) => sheet.name === sheetName);
const claimsArray = [];
let foundHeaderRow, foundTotalRow;
detailSheet.data.forEach((line) => {
//Check the first element. If it's claim number, we have our header row. the next one is important.
if (!foundHeaderRow && line[0] === "Claim Number") {
foundHeaderRow = true;
} else if (foundHeaderRow && !foundTotalRow && line[0] && line[0] !== "Grand Total") {
//Add it to the array
const row = {
clm_no: line[0].startsWith("00") ? line[0].slice(2) : line[0],
close_date: line[1],
v_model_yr: line[3],
v_makedesc: line[4],
v_model: line[5],
under20kmiles: line[6],
pan_total: line[7],
paa_total: line[8],
pal_total: line[9],
pam_total: line[10],
eligible_db_price_total: Math.round((line[11] + Number.EPSILON) * 100) / 100,
eligible_act_price_total: Math.round((line[12] + Number.EPSILON) * 100) / 100,
expected_rps_dollars: Math.round((line[15] + Number.EPSILON) * 100) / 100,
actual_rps_dollars: Math.round((line[16] + Number.EPSILON) * 100) / 100
};
claimsArray.push(row);
} else {
// foundTotalRow = true;
}
});
event.sender.send(ipcTypes.default.audit.toRenderer.auditClaimsArray, claimsArray);
} catch (error) {
console.log("ot some sort of err", error);
log.error("Error when trying to read audit xlsx file", error);
event.sender.send(ipcTypes.default.audit.toRenderer.auditError, error.message);
}
});

View File

@@ -146,7 +146,12 @@
},
"1.2.0": {
"title": "Release Notes for 1.2.0",
"date": "05/51/2024",
"date": "05/21/2024",
"notes": "New Features\n* Introducing Score Card Auditing. Simply select your MPI scorecard and instantly compare it to your RPS score card to find discrepancies. Available in your side bar\n\nImprovements\n* Added additional models to SUV and Van databases for better detection.\n* Only -01 and -99 claims will now be brought into RPS.\n* Under the hood fixes and improvements."
},
"1.2.1": {
"title": "Release Notes for 1.2.1",
"date": "05/23/2024",
"notes": "Improvements\n* UI improvements for audit functionality."
}
}

View File

@@ -3,7 +3,7 @@
"productName": "ImEX RPS",
"author": "ImEX Systems Inc. <support@thinkimex.com>",
"description": "ImEX RPS",
"version": "1.2.0",
"version": "1.2.1",
"main": "electron/main.js",
"homepage": "./",
"dependencies": {

View File

@@ -48,11 +48,6 @@ export function ReportingDatesMolecule({ queryReportingData }) {
<DatePicker.RangePicker
format="MM/DD/YYYY"
ranges={{
Today: [dayjs(), dayjs()],
"Last 14 days": [dayjs().subtract(14, "day"), dayjs()],
"Last 7 days": [dayjs().subtract(7, "day"), dayjs()],
"Next 7 days": [dayjs(), dayjs().add(7, "day")],
"Next 14 days": [dayjs(), dayjs().add(14, "day")],
"Last Month": [
dayjs().startOf("month").subtract(1, "month"),
dayjs().startOf("month").subtract(1, "month").endOf("month")
@@ -71,10 +66,11 @@ export function ReportingDatesMolecule({ queryReportingData }) {
dayjs().startOf("quarter"),
dayjs().startOf("quarter").add(1, "quarter").subtract(1, "day")
],
"Last 3 Months": [
"Last 3 Months (Exlcusive)": [
dayjs().startOf("month").subtract(3, "month"),
dayjs().startOf("month").subtract(1, "month").endOf("month")
]
],
"Last 3 Months (Inclusive)": [dayjs().startOf("month").subtract(2, "month"), dayjs().endOf("month")]
}}
/>
</Form.Item>

View File

@@ -1,4 +1,4 @@
import { Card, Col, Divider, Space, Table, Tooltip } from "antd";
import { Card, Col, Divider, Empty, Result, Space, Table, Tooltip } from "antd";
import Dinero from "dinero.js";
import React from "react";
import { connect } from "react-redux";
@@ -28,16 +28,19 @@ export function AuditResultsOrganism({ auditLoading, setSelectedJobId, selectAud
title: "Claim No.",
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
dataIndex: "clm_no",
render: (text, record) => (
<Link onClick={() => setSelectedJobId(record.id)} to={"/"}>
<Space>{text}</Space>
</Link>
)
render: (text, record) =>
record.id ? (
<Link onClick={() => setSelectedJobId(record.id)} to={"/"}>
{text}
</Link>
) : (
text
)
},
{
key: "close_date",
width: "12%",
title: "[RPS] R4P",
title: "[ImEX RPS] R4P",
dataIndex: "close_date",
defaultSortOrder: "ascend",
sorter: (a, b) => dateSort(a.close_date, b.close_date),
@@ -59,7 +62,7 @@ export function AuditResultsOrganism({ auditLoading, setSelectedJobId, selectAud
{
key: "actual_rps",
width: "12%",
title: " Actual RPS ",
title: " Actual RPS",
dataIndex: ["audit", "jobRpsDollars"],
render: (text, record) =>
record?.jobRpsDollars
@@ -74,11 +77,19 @@ export function AuditResultsOrganism({ auditLoading, setSelectedJobId, selectAud
width: "12%",
title: "Claim No.",
sorter: (a, b) => alphaSort(a.rps.clm_no, b.rps.clm_no),
dataIndex: ["rps", "clm_no"]
dataIndex: ["rps", "clm_no"],
render: (text, record) =>
record.rps?.id ? (
<Link onClick={() => setSelectedJobId(record.rps.id)} to={"/"}>
{text}
</Link>
) : (
text
)
},
{
key: "close_date",
title: "[RPS] R4P",
title: "[ImEX RPS] R4P",
dataIndex: "close_date",
defaultSortOrder: "ascend",
sorter: (a, b) => dateSort(a.rps.close_date, b.rps.close_date),
@@ -99,11 +110,11 @@ export function AuditResultsOrganism({ auditLoading, setSelectedJobId, selectAud
{
key: "expected_rps",
width: "12%",
title: "Expected RPS (RPS/Audit)",
title: "Expected RPS (ImEX RPS/Audit)",
dataIndex: ["audit", "expectedRpsDollars"],
render: (text, record) => (
<Space split={<Divider type="vertical" />}>
<Tooltip title="RPS Expected Savings">{record.rps.expectedRpsDollars.toFormat()}</Tooltip>
<Tooltip title="ImEX RPS Expected Savings">{record.rps.expectedRpsDollars.toFormat()}</Tooltip>
<Tooltip title="MPI Audit Expected Savings">
{Dinero({ amount: Math.round(record.audit.expected_rps_dollars * 100) }).toFormat()}
</Tooltip>
@@ -113,11 +124,11 @@ export function AuditResultsOrganism({ auditLoading, setSelectedJobId, selectAud
{
key: "actual_rps",
width: "12%",
title: " Actual RPS (RPS/Audit)",
title: " Actual RPS (ImEX RPS/Audit)",
dataIndex: ["audit", "jobRpsDollars"],
render: (text, record) => (
<Space split={<Divider type="vertical" />}>
<Tooltip title="RPS Actual Savings">{record.rps.jobRpsDollars.toFormat()}</Tooltip>
<Tooltip title="ImeX RPS Actual Savings">{record.rps.jobRpsDollars.toFormat()}</Tooltip>
<Tooltip title="MPI Audit Actual Savings">
{Dinero({ amount: Math.round(record.audit.actual_rps_dollars * 100) }).toFormat()}
</Tooltip>
@@ -125,27 +136,51 @@ export function AuditResultsOrganism({ auditLoading, setSelectedJobId, selectAud
)
}
];
if (Object.keys(selectAuditData).length === 0)
return (
<Col span={24}>
<Card>
<Empty
// image="https://gw.alipayobjects.com/zos/antfincdn/ZHrcdLPrvN/empty.svg"
// imageStyle={{
// height: 60
// }}
description={
<span>No audit has been run yet. Please select a date range and audit file to view results.</span>
}
/>
</Card>
</Col>
);
return (
<>
<Col span={24}>
<Card title="Jobs in RPS, not found in MPI Audit">
<Card title="Jobs in ImEX RPS, not found in MPI Audit">
<Table
loading={auditLoading}
columns={missingColumns}
pagination={false}
dataSource={selectAuditData?.missingFromRps}
rowKey="clm_no"
locale={{
emptyText: <Result status="success" subTitle="No discrepancies found." />
}}
/>
</Card>
</Col>
<Col span={24}>
<Card title="Jobs in MPI Audit, not in RPS">
<Card title="Jobs in MPI Audit, not in ImEX RPS">
<Table
loading={auditLoading}
columns={missingColumns}
pagination={false}
dataSource={selectAuditData?.missingFromAudit}
rowKey="clm_no"
locale={{
emptyText: <Result status="success" subTitle="No discrepancies found." />
}}
/>
</Card>
</Col>
@@ -157,6 +192,9 @@ export function AuditResultsOrganism({ auditLoading, setSelectedJobId, selectAud
pagination={false}
dataSource={selectAuditData?.expectedMismatch}
rowKey="clm_no"
locale={{
emptyText: <Result status="success" subTitle="No discrepancies found." />
}}
/>
</Card>
</Col>
@@ -168,6 +206,9 @@ export function AuditResultsOrganism({ auditLoading, setSelectedJobId, selectAud
pagination={false}
dataSource={selectAuditData?.actualMismatch}
rowKey="clm_no"
locale={{
emptyText: <Result status="success" subTitle="No discrepancies found." />
}}
/>
</Card>
</Col>

View File

@@ -1,6 +1,6 @@
import { PrinterFilled } from "@ant-design/icons";
import { Alert, Button, Card, Col, DatePicker, Form, Input, Result, Row, Space } from "antd";
import React, { useRef } from "react";
import { Alert, Button, Card, Col, DatePicker, Form, Input, Result, Row, Select, Space } from "antd";
import React, { useEffect, useRef, useState } from "react";
import { connect } from "react-redux";
import { useReactToPrint } from "react-to-print";
import { createStructuredSelector } from "reselect";
@@ -26,13 +26,36 @@ const mapDispatchToProps = (dispatch) => ({
export default connect(mapStateToProps, mapDispatchToProps)(AuditPage);
export function AuditPage({ auditError, queryReportingData, bodyshop }) {
const handleBrowseForFile = async ({ sheetName, dateRange }) => {
queryReportingData({
startDate: dateRange[0] || dayjs("2024-03-01"),
endDate: dateRange[1] || dayjs("2024-03-31")
const [form] = Form.useForm();
const [sheets, setSheets] = useState([]);
useEffect(() => {
ipcRenderer.on(ipcTypes.audit.toRenderer.auditFilePath, async (event, { filePath, sheets }) => {
setSheets(sheets);
if (sheets.includes("Shop RPS Claim Detail")) {
form.setFieldsValue({ sheetName: "Shop RPS Claim Detail" });
}
form.setFieldsValue({ filePath });
});
ipcRenderer.send(ipcTypes.audit.toMain.browseForFile, { sheetName });
return () => {
//ipcRenderer.removeListener(ipcTypes.audit.toRenderer.auditFilePath);
};
}, [form]);
const handleBrowseForFile = async () => {
const { dateRange } = form.getFieldsValue();
queryReportingData({
startDate: dateRange[0],
endDate: dateRange[1]
});
ipcRenderer.send(ipcTypes.audit.toMain.browseForFile, {});
};
const handleRunAudit = async ({ sheetName, dateRange }) => {
console.log("🚀 ~ handleRunAudit ~ sheetName:", sheetName);
ipcRenderer.send(ipcTypes.audit.toMain.runAudit, { sheetName });
};
const componentRef = useRef();
const handlePrint = useReactToPrint({
content: () => componentRef.current,
@@ -53,7 +76,7 @@ export function AuditPage({ auditError, queryReportingData, bodyshop }) {
<Row gutter={[16, 16]} ref={componentRef}>
<Col span={24}>
<Card>
<Form onFinish={handleBrowseForFile}>
<Form form={form} onFinish={handleRunAudit}>
<Form.Item
label="1. Ready for Payment Date Between"
name="dateRange"
@@ -101,17 +124,44 @@ export function AuditPage({ auditError, queryReportingData, bodyshop }) {
<Space align="middle" wrap>
<Form.Item
label="2. Sheet Name"
label="2. File Path"
name="filePath"
tooltip="File path to the MPI score card you want to audit. Ensure that this scorecard has not been modified or altered in any way."
rules={[{ required: true }]}
>
<Input disabled width="200px" />
</Form.Item>
<Form.Item shouldUpdate>
{() => {
const disabled = !form.getFieldsValue().dateRange;
return (
<Button disabled={disabled} onClick={handleBrowseForFile}>
Select MPI Audit XLS File
</Button>
);
}}
</Form.Item>
<Form.Item
label="3. Sheet Name"
tooltip="The name of the sheet which contains detailed RPS claim data."
name="sheetName"
initialValue="Shop RPS Claim Detail"
required
rules={[{ required: true }]}
>
<Input width="200px" />
{/* <Input width="200px" /> */}
<Select
style={{ minWidth: "200px" }}
options={sheets.map((sheet) => ({ label: sheet, value: sheet }))}
/>
</Form.Item>
<Form.Item shouldUpdate>
{() => {
return (
<Button type="primary" htmlType="submit">
Run Audit
</Button>
);
}}
</Form.Item>
<Button type="primary" htmlType="submit">
Select MPI Audit XLS File
</Button>
<Button onClick={handlePrint}>
<PrinterFilled />
</Button>
@@ -139,8 +189,8 @@ function NoAuditAccess({ features }) {
return (
<Result
status="warning"
title="You do not currently have access to the audit feature of RPS."
subTitle="Auditing allows you to instantly and automatically find discrepancies between the data you have recorded in RPS and the scorecard provided to your by your SRA."
title="You do not currently have access to the audit feature of ImEX RPS."
subTitle="Auditing allows you to instantly and automatically find discrepancies between the data you have recorded in ImEX RPS and the scorecard provided to your by your SRA."
extra={[
<Button
size="large"

View File

@@ -3,6 +3,6 @@
overflow-y: auto;
background-color: rgb(244, 244, 244);
& > * {
margin: 0.7rem;
padding: 1rem;
}
}

View File

@@ -3,6 +3,6 @@
overflow-y: auto;
background-color: rgb(244, 244, 244);
& > .reporting-cards > * {
margin: 0.7rem;
padding: 1rem;
}
}

View File

@@ -68,8 +68,12 @@
},
"audit": {
"toMain": { "browseForFile": "audit__browseForFile" },
"toRenderer": { "auditClaimsArray": "audit__filepath", "auditError": "audit__auditError" }
"toMain": { "browseForFile": "audit__browseForFile", "runAudit": "audit__runAudit", "readFile": "audit__readFile" },
"toRenderer": {
"auditFilePath": "audit__filepath",
"auditClaimsArray": "audit_claimsArray",
"auditError": "audit__auditError"
}
},
"estimate": {
"toRenderer": {

View File

@@ -11,7 +11,8 @@ import {
setReportingData,
setScoreCard,
setReportingError,
setAuditResults
setAuditResults,
setAuditError
} from "./reporting.actions";
import ReportingApplicationTypes from "./reporting.types";
@@ -46,11 +47,18 @@ export function* onCalculateAudit() {
yield takeLatest(ReportingApplicationTypes.CALCULATE_AUDIT, handleCalculateAudit);
}
export function* handleCalculateAudit({ payload: claimsArrayFromAudit }) {
if (claimsArrayFromAudit.length === 0) {
yield put(
setAuditError(
"The excel file did not return any matching results for this sheet. Please ensure you have selected the correct sheet. This is typically 'Shop RPS Claim Detail' but may vary."
)
);
return;
}
const rpsJobs = yield select((state) => state.reporting.data);
//Get List of Claims delta.
const missingFromRps = rpsJobs.filter((job) => !claimsArrayFromAudit.find((c) => c.clm_no.includes(job.clm_no)));
const missingFromAudit = claimsArrayFromAudit.filter((c) => !rpsJobs.find((job) => c.clm_no.includes(job.clm_no)));
console.log("Missing From RPS/From Audit", missingFromRps.length, missingFromAudit.length);
//For the items in both spots, highlight the discrepancy.
const claimsArrayHashObject = {};