Auditing refinements.

This commit is contained in:
Patrick Fic
2024-05-09 16:11:45 -07:00
parent 9ab6e511b9
commit 2f77f0fb45
10 changed files with 161 additions and 75 deletions

View File

@@ -28,7 +28,7 @@ ipcMain.on(ipcTypes.default.audit.toMain.browseForFile, async (event, { sheetNam
} else if (foundHeaderRow && !foundTotalRow && line[0] && line[0] !== "Grand Total") {
//Add it to the array
const row = {
clm_no: line[0],
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],

View File

@@ -1,14 +1,5 @@
const path = require("path");
const {
app,
BrowserWindow,
Tray,
Menu,
ipcMain,
dialog,
shell,
globalShortcut,
} = require("electron");
const { app, BrowserWindow, Tray, Menu, ipcMain, dialog, shell, globalShortcut } = require("electron");
const isDev = require("electron-is-dev");
const { default: ipcTypes } = require("../src/ipc.types.commonjs");
const { store } = require("./electron-store");
@@ -26,8 +17,8 @@ Sentry.init({
ignoreErrors: [
"SimpleURLLoaderWrapper",
"Cannot read properties of null (reading 'webContents')",
"EBUSY: resource busy or locked",
],
"EBUSY: resource busy or locked"
]
});
autoUpdater.autoDownload = true;
@@ -52,28 +43,28 @@ var menu = Menu.buildFromTemplate([
click() {
app.exit();
app.relaunch();
},
}
},
{
label: "Clear Settings",
click() {
store.reset("filePaths");
mainWindow.webContents.session.clearStorageData();
},
}
},
{
label: "Sign Out",
click() {
mainWindow.webContents.send(ipcTypes.app.toRenderer.signOut);
},
}
},
{
label: "Exit",
click() {
app.quit();
},
},
],
}
}
]
// Other code removed for brevity
},
{
@@ -83,13 +74,13 @@ var menu = Menu.buildFromTemplate([
label: "Rescue",
click() {
shell.openExternal("http://imexrescue.com");
},
}
},
{
label: `Check for Updates (currently ${app.getVersion()})`,
click() {
checkForUpdates();
},
}
},
{
label: `Show Release Notes`,
@@ -98,28 +89,28 @@ var menu = Menu.buildFromTemplate([
ipcTypes.app.toRenderer.setReleaseNotes,
require("./changelog.json")[app.getVersion()]
);
},
}
},
{
label: "Open Config File",
click() {
shell.openPath(store.path);
},
}
},
{
label: "Open Log File",
click() {
shell.openPath(path.join(app.getPath("appData"), "ImeX RPS\\logs"));
},
}
},
{
label: "Third Party Notices",
click() {
openNoticeWindow();
},
},
],
},
}
}
]
}
]);
let mainWindow;
@@ -142,8 +133,8 @@ function createWindow() {
webSecurity: true,
worldSafeExecuteJavaScript: true,
contextIsolation: true,
preload: path.join(__dirname, "preload.js"), // use a preload script
},
preload: path.join(__dirname, "preload.js") // use a preload script
}
});
const gotTheLock = app.requestSingleInstanceLock();
@@ -162,11 +153,7 @@ function createWindow() {
// and load the index.html of the app.
// win.loadFile("index.html");
mainWindow.loadURL(
isDev
? "http://localhost:3000"
: `file://${path.join(__dirname, "/../build/index.html")}`
);
mainWindow.loadURL(isDev ? "http://localhost:3000" : `file://${path.join(__dirname, "/../build/index.html")}`);
// mainWindow.on("close", function (event) {
// event.preventDefault();
@@ -256,15 +243,15 @@ function createTray() {
label: "Show",
click: function () {
mainWindow.show();
},
}
},
{
label: "Exit",
click: function () {
app.isQuiting = true;
app.quit();
},
},
}
}
]);
appIcon.on("double-click", function (event) {
@@ -297,7 +284,7 @@ function openNoticeWindow() {
noticeWindow = new BrowserWindow({
height: 600,
width: 800,
title: "ImEX RPS - Third Party Notices",
title: "ImEX RPS - Third Party Notices"
});
noticeWindow.loadURL("file://" + __dirname + "/licenses.txt");
@@ -333,7 +320,7 @@ autoUpdater.on("update-downloaded", (ev, info) => {
// if (process.env.NODE_ENV === "production") {
mainWindow.webContents.send(ipcTypes.app.toRenderer.downloadProgress, {
...ev,
percent: 100,
percent: 100
});
dialog
@@ -341,7 +328,7 @@ autoUpdater.on("update-downloaded", (ev, info) => {
type: "info",
title: "ImeX RPS Update Manager",
message: `ImEX RPS is ready to update to Version ${ev.version}. It is highly recommended that you update immediately. Would you like to update now? RPS will automatically restart.`,
buttons: ["Yes", "No"],
buttons: ["Yes", "No"]
})
.then(({ response }) => {
if (response === 0) {

10
package-lock.json generated
View File

@@ -42,6 +42,7 @@
"react-infinite-scroller": "^1.2.6",
"react-redux": "^9.1.1",
"react-router-dom": "^6.22.3",
"react-to-print": "^2.15.1",
"recharts": "^2.12.5",
"redux": "^5.0.1",
"redux-logger": "^3.0.6",
@@ -13473,6 +13474,15 @@
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-to-print": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/react-to-print/-/react-to-print-2.15.1.tgz",
"integrity": "sha512-1foogIFbCpzAVxydkhBiDfMiFYhIMphiagDOfcG4X/EcQ+fBPqJ0rby9Wv/emzY1YLkIQy/rEgOrWQT+rBKhjw==",
"peerDependencies": {
"react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",

View File

@@ -40,6 +40,7 @@
"react-infinite-scroller": "^1.2.6",
"react-redux": "^9.1.1",
"react-router-dom": "^6.22.3",
"react-to-print": "^2.15.1",
"recharts": "^2.12.5",
"redux": "^5.0.1",
"redux-logger": "^3.0.6",

View File

@@ -93,3 +93,17 @@ body {
opacity: 0;
}
}
@media print {
.no-print {
display: none;
}
body {
height: unset;
page-break-before: always;
}
@page {
// size: landscape;
}
}

View File

@@ -1,43 +1,71 @@
import { Card, Col, Divider, Space, Table, Tooltip } from "antd";
import Dinero from "dinero.js";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setSelectedJobTargetPc } from "../../../redux/application/application.actions";
import { selectAuditData } from "../../../redux/reporting/reporting.selectors";
import { setSelectedJobId, setSelectedJobTargetPc } from "../../../redux/application/application.actions";
import { selectAuditData, selectAuditLoading } from "../../../redux/reporting/reporting.selectors";
import { DateFormat } from "../../../util/constants";
import dayjs from "../../../util/day";
import Dinero from "dinero.js";
import { alphaSort, dateSort } from "../../../util/sorters";
import { Link } from "react-router-dom";
const mapStateToProps = createStructuredSelector({
selectAuditData: selectAuditData
selectAuditData: selectAuditData,
auditLoading: selectAuditLoading
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
setSelectedJobId: (id) => dispatch(setSelectedJobId(id)),
setSelectedJobTargetPc: (job) => dispatch(setSelectedJobTargetPc(job))
});
export function AuditResultsOrganism({ selectAuditData }) {
export function AuditResultsOrganism({ auditLoading, setSelectedJobId, selectAuditData }) {
const missingColumns = [
{
key: "clm_no",
width: "20%",
width: "12%",
title: "Claim No.",
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
dataIndex: "clm_no"
dataIndex: "clm_no",
render: (text, record) => (
<Link onClick={() => setSelectedJobId(record.id)} to={"/"}>
<Space>{text}</Space>
</Link>
)
},
{
key: "close_date",
width: "20%",
width: "12%",
title: "[RPS] R4P",
dataIndex: "close_date",
defaultSortOrder: "ascend",
sorter: (a, b) => dateSort(a.close_date, b.close_date),
render: (text, record) => dayjs(record.close_date).format(DateFormat)
},
{ key: "v_model_yr", width: "20%", title: "Model Year", dataIndex: "v_model_yr" },
{ key: "v_model_yr", width: "12%", title: "Model Year", dataIndex: "v_model_yr" },
{ key: "v_makedesc", width: "20%", title: "Make", dataIndex: "v_makedesc" },
{ key: "v_model", width: "20%", title: "Model", dataIndex: "v_model" }
{ key: "v_model", width: "20%", title: "Model", dataIndex: "v_model" },
{
key: "expected_rps",
width: "12%",
title: "Expected RPS ",
dataIndex: ["audit", "expectedRpsDollars"],
render: (text, record) =>
record.expectedRpsDollars
? record?.expectedRpsDollars?.toFormat()
: Dinero({ amount: Math.round((record?.expected_rps_dollars || 0) * 100) }).toFormat()
},
{
key: "actual_rps",
width: "12%",
title: " Actual RPS ",
dataIndex: ["audit", "jobRpsDollars"],
render: (text, record) =>
record?.jobRpsDollars
? record?.jobRpsDollars.toFormat()
: Dinero({ amount: Math.round((record?.actual_rps_dollars || 0) * 100) }).toFormat()
}
];
const mismatchColumns = [
@@ -71,7 +99,7 @@ export function AuditResultsOrganism({ selectAuditData }) {
{
key: "expected_rps",
width: "12%",
title: "Expected RPS",
title: "Expected RPS (RPS/Audit)",
dataIndex: ["audit", "expectedRpsDollars"],
render: (text, record) => (
<Space split={<Divider type="vertical" />}>
@@ -85,7 +113,7 @@ export function AuditResultsOrganism({ selectAuditData }) {
{
key: "actual_rps",
width: "12%",
title: " Actual RPS",
title: " Actual RPS (RPS/Audit)",
dataIndex: ["audit", "jobRpsDollars"],
render: (text, record) => (
<Space split={<Divider type="vertical" />}>
@@ -101,22 +129,46 @@ export function AuditResultsOrganism({ selectAuditData }) {
<>
<Col span={24}>
<Card title="Jobs in RPS, not found in MPI Audit">
<Table columns={missingColumns} dataSource={selectAuditData?.missingFromRps} rowKey="clm_no" />
<Table
loading={auditLoading}
columns={missingColumns}
pagination={false}
dataSource={selectAuditData?.missingFromRps}
rowKey="clm_no"
/>
</Card>
</Col>
<Col span={24}>
<Card title="Jobs in MPI Audit, not in RPS">
<Table columns={missingColumns} dataSource={selectAuditData?.missingFromAudit} rowKey="clm_no" />
<Table
loading={auditLoading}
columns={missingColumns}
pagination={false}
dataSource={selectAuditData?.missingFromAudit}
rowKey="clm_no"
/>
</Card>
</Col>
<Col span={24}>
<Card title="Mismatch - Expected Savings">
<Table columns={mismatchColumns} dataSource={selectAuditData?.expectedMismatch} rowKey="clm_no" />
<Table
loading={auditLoading}
columns={mismatchColumns}
pagination={false}
dataSource={selectAuditData?.expectedMismatch}
rowKey="clm_no"
/>
</Card>
</Col>
<Col span={24}>
<Card title="Mismatch - Actual Savings">
<Table columns={mismatchColumns} dataSource={selectAuditData?.actualMismatch} rowKey="clm_no" />
<Table
loading={auditLoading}
columns={mismatchColumns}
pagination={false}
dataSource={selectAuditData?.actualMismatch}
rowKey="clm_no"
/>
</Card>
</Col>
</>

View File

@@ -1,6 +1,8 @@
import { Alert, Button, Card, Col, DatePicker, Form, Input, Row } from "antd";
import React from "react";
import { PrinterFilled } from "@ant-design/icons";
import { Alert, Button, Card, Col, DatePicker, Form, Input, Row, Space } from "antd";
import React, { useRef } from "react";
import { connect } from "react-redux";
import { useReactToPrint } from "react-to-print";
import { createStructuredSelector } from "reselect";
import ipcTypes from "../../../ipc.types";
import { queryReportingData } from "../../../redux/reporting/reporting.actions";
@@ -29,17 +31,25 @@ export function AuditPage({ auditError, queryReportingData }) {
});
ipcRenderer.send(ipcTypes.audit.toMain.browseForFile, { sheetName });
};
const componentRef = useRef();
const handlePrint = useReactToPrint({
content: () => componentRef.current,
bodyClass: "audit-container-print"
});
window.ref = componentRef.current;
if (auditError) console.log("Error when opening audit file.", auditError);
return (
<FeatureWrapper featureName="audit">
<div className="audit-container">
<Row gutter={[16, 16]}>
<div className="audit-container" id="audit-results-container">
<Row gutter={[16, 16]} ref={componentRef}>
<Col span={24}>
<Card>
<Form onFinish={handleBrowseForFile}>
<Form.Item
label="Ready for Payment Date Between"
label="1. Ready for Payment Date Between"
name="dateRange"
tooltip="Select the time period that you would like to audit. This is typically a whole month or quarter."
rules={[
{ type: "array", required: true },
{
@@ -80,15 +90,24 @@ export function AuditPage({ auditError, queryReportingData }) {
}}
/>
</Form.Item>
<Form.Item
label="Sheet Name"
tooltip="The name of the sheet which contains detailed RPS claim data."
name="sheetName"
initialValue="Shop RPS Claim Detail"
>
<Input width="200px" />
</Form.Item>
<Button htmlType="submit">Select MPI Audit XLS File</Button>
<Space align="middle" wrap>
<Form.Item
label="2. Sheet Name"
tooltip="The name of the sheet which contains detailed RPS claim data."
name="sheetName"
initialValue="Shop RPS Claim Detail"
required
>
<Input width="200px" />
</Form.Item>
<Button type="primary" htmlType="submit">
Select MPI Audit XLS File
</Button>
<Button onClick={handlePrint}>
<PrinterFilled />
</Button>
</Space>
</Form>
</Card>
</Col>

View File

@@ -16,7 +16,7 @@ function FeatureWrapper({ bodyshop, featureName, noauth, children, ...restProps
noauth || (
<Alert
message={
"You do not currently have access to this feature. Please reach out to support at support@thinkimex.com or 604-839-3431 to request access."
"You do not currently have access to this feature. Please reach out to support at support@thinkimex.com or 604-839-3431 to subscribe."
}
type="warning"
/>

View File

@@ -5,6 +5,7 @@ const INITIAL_STATE = {
scoreCard: null,
error: null,
loading: false,
auditLoading: false,
audit: {},
auditError: null
};
@@ -30,10 +31,12 @@ const applicationReducer = (state = INITIAL_STATE, action) => {
return { ...state, data: action.payload };
case ReportingActionTypes.SET_SCORE_CARD:
return { ...state, loading: false, scoreCard: action.payload };
case ReportingActionTypes.CALCULATE_AUDIT:
return { ...state, auditLoading: true };
case ReportingActionTypes.SET_AUDIT_RESULTS:
return { ...state, loading: false, auditError: null, audit: action.payload };
return { ...state, loading: false, auditLoading: false, auditError: null, audit: action.payload };
case ReportingActionTypes.SET_AUDIT_ERROR:
return { ...state, auditError: action.payload };
return { ...state, auditLoading: false, auditError: action.payload };
case ReportingActionTypes.TOGGLE_GROUP_VERIFIED:
return {
...state,

View File

@@ -9,7 +9,7 @@ export const selectReportingError = createSelector([selectReporting], (reporting
export const selectReportData = createSelector([selectReporting], (reporting) => reporting.data);
export const selectAuditData = createSelector([selectReporting], (reporting) => reporting.audit);
export const selectAuditError = createSelector([selectReporting], (reporting) => reporting.auditError);
export const selectAuditLoading = createSelector([selectReporting], (reporting) => reporting.auditLoading);
// export const selectWatchedPaths = createSelector(
// [selectReporting],
// (application) => application.watchedPaths