Begin Audit Functionality testing.
This commit is contained in:
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"globals": {
|
||||
"Atomics": "readonly",
|
||||
"SharedArrayBuffer": "readonly"
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"no-console": "off"
|
||||
},
|
||||
"settings": {}
|
||||
}
|
||||
51
electron/audit/audit-ipc.js
Normal file
51
electron/audit/audit-ipc.js
Normal file
@@ -0,0 +1,51 @@
|
||||
const { ipcMain, dialog } = require("electron");
|
||||
|
||||
const ipcTypes = require("../../src/ipc.types.commonjs");
|
||||
const { mainWindow } = require("../main");
|
||||
const _ = require("lodash");
|
||||
const { store } = require("../electron-store");
|
||||
const path = require("path");
|
||||
var xlsx = require("node-xlsx");
|
||||
|
||||
ipcMain.on(ipcTypes.default.audit.toMain.browseForFile, async (event, arg) => {
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
filters: [{ extensions: ["xls", "xlsx"], name: "Excel Files" }],
|
||||
properties: ["openFile"]
|
||||
});
|
||||
if (!result.canceled) {
|
||||
store.set("auditFilePath", result.filePaths);
|
||||
var obj = xlsx.parse(result.filePaths[0], { cellDates: true }); // parses a file
|
||||
|
||||
const detailSheet = obj.find((sheet) => sheet.name === "Shop RPS Claim Detail");
|
||||
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],
|
||||
close_date: line[1],
|
||||
v_model_yr: line[3],
|
||||
v_model_desc: line[4],
|
||||
under20kmiles: line[5],
|
||||
pan_total: line[6],
|
||||
paa_total: line[7],
|
||||
pal_total: line[8],
|
||||
pam_total: line[9],
|
||||
eligible_db_price_total: Math.round((line[10] + Number.EPSILON) * 100) / 100,
|
||||
eligible_act_price_total: Math.round((line[11] + 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);
|
||||
}
|
||||
});
|
||||
@@ -8,6 +8,7 @@ const { ImportJobWithCloseDate } = require("./decoder/decoder");
|
||||
//Import Ipc Handlers
|
||||
require("./file-watcher/file-watcher-ipc");
|
||||
require("./file-scan/file-scan-ipc");
|
||||
require("./audit/audit-ipc");
|
||||
|
||||
console.log("*** Added IPC Handlers ***");
|
||||
|
||||
|
||||
2903
package-lock.json
generated
2903
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -25,11 +25,14 @@
|
||||
"electron-reload": "^2.0.0-alpha.1",
|
||||
"electron-store": "^8.2.0",
|
||||
"electron-updater": "^6.1.8",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"firebase": "^10.11.0",
|
||||
"graphql": "^16.8.1",
|
||||
"lodash": "^4.17.21",
|
||||
"logrocket": "^8.1.0",
|
||||
"moment": "^2.30.1",
|
||||
"node-xlsx": "^0.24.0",
|
||||
"nucleus-nodejs": "^3.0.9",
|
||||
"query-string": "^9.0.0",
|
||||
"react": "^18.2.0",
|
||||
@@ -79,6 +82,7 @@
|
||||
"electron": "^30.0.0",
|
||||
"electron-builder": "^24.13.3",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"eslint-config-react": "^1.1.7",
|
||||
"vite": "^5.0.11",
|
||||
"vite-plugin-babel": "^1.2.0",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
CloseOutlined,
|
||||
BarChartOutlined,
|
||||
FileAddFilled,
|
||||
LogoutOutlined,
|
||||
LogoutOutlined
|
||||
} from "@ant-design/icons";
|
||||
import { Menu } from "antd";
|
||||
import React from "react";
|
||||
@@ -26,28 +26,33 @@ export default function SiderMenuOrganism() {
|
||||
{
|
||||
key: "/",
|
||||
icon: <PieChartOutlined />,
|
||||
label: <Link to="/">Jobs</Link>,
|
||||
label: <Link to="/">Jobs</Link>
|
||||
},
|
||||
{
|
||||
key: "/scan",
|
||||
icon: <FileAddFilled />,
|
||||
label: <Link to="/scan">File Scan</Link>,
|
||||
label: <Link to="/scan">File Scan</Link>
|
||||
},
|
||||
{
|
||||
key: "/reporting",
|
||||
icon: <BarChartOutlined />,
|
||||
label: <Link to="/reporting">Reporting</Link>,
|
||||
label: <Link to="/reporting">Reporting</Link>
|
||||
},
|
||||
{
|
||||
key: "/audit",
|
||||
icon: <BarChartOutlined />,
|
||||
label: <Link to="/audit">Audit</Link>
|
||||
},
|
||||
{
|
||||
key: "/settings",
|
||||
icon: <SettingFilled />,
|
||||
label: <Link to="/settings">Settings</Link>,
|
||||
label: <Link to="/settings">Settings</Link>
|
||||
},
|
||||
{ type: "divider" },
|
||||
{
|
||||
key: "signout",
|
||||
icon: <LogoutOutlined style={{ color: "tomato" }} />,
|
||||
label: <SiderSignOut />,
|
||||
label: <SiderSignOut />
|
||||
},
|
||||
{
|
||||
key: "quit",
|
||||
@@ -60,8 +65,8 @@ export default function SiderMenuOrganism() {
|
||||
>
|
||||
Quit
|
||||
</span>
|
||||
),
|
||||
},
|
||||
)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
32
src/components/pages/audit/audit.page.jsx
Normal file
32
src/components/pages/audit/audit.page.jsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Button } from "antd";
|
||||
import React from "react";
|
||||
import ipcTypes from "../../../ipc.types";
|
||||
import ReportingDatesMolecule from "../../molecules/reporting-dates/reporting-dates.molecule";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { queryReportingData } from "../../../redux/reporting/reporting.actions";
|
||||
import dayjs from "../../../util/day";
|
||||
|
||||
const { ipcRenderer } = window;
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
queryReportingData: (dates) => dispatch(queryReportingData(dates))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AuditPage);
|
||||
|
||||
export function AuditPage({ queryReportingData }) {
|
||||
const handleBrowseForFile = async () => {
|
||||
queryReportingData({ startDate: dayjs("2024-03-01"), endDate: dayjs("2024-03-31") });
|
||||
ipcRenderer.send(ipcTypes.audit.toMain.browseForFile);
|
||||
};
|
||||
return (
|
||||
<div style={{ height: "100%" }}>
|
||||
<ReportingDatesMolecule />
|
||||
<Button onClick={handleBrowseForFile}>Add Path</Button>;
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import JobsPage from "../jobs/jobs.page";
|
||||
import ReportingPage from "../reporting/reporting.page";
|
||||
import ScanPage from "../scan/scan.page";
|
||||
import SettingsPage from "../settings/settings.page";
|
||||
import AuditPage from "../audit/audit.page";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop });
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
@@ -29,19 +30,16 @@ export function RoutesPage({ bodyshop }) {
|
||||
|
||||
return (
|
||||
<Layout style={{ background: "#fff", height: "100vh" }} hasSider>
|
||||
<Layout.Sider
|
||||
style={{ background: "#fff" }}
|
||||
collapsible
|
||||
defaultCollapsed="true"
|
||||
>
|
||||
<Layout.Sider style={{ background: "#fff" }} collapsible defaultCollapsed="true">
|
||||
<SiderMenuOrganism />
|
||||
</Layout.Sider>
|
||||
<Layout style={{ background: "#fff" }}>
|
||||
<Layout.Content style={{ marginLeft: "1rem", height: "100%" }}>
|
||||
<NotificationModalOrganism />
|
||||
<NotificationModalOrganism />
|
||||
<Routes>
|
||||
<Route exact path="/settings" element={<SettingsPage />} />
|
||||
<Route exact path="/reporting" element={<ReportingPage />} />
|
||||
<Route exact path="/audit" element={<AuditPage />} />
|
||||
<Route exact path="/scan" element={<ScanPage />} />
|
||||
<Route exact path="/" element={<JobsPage />} />
|
||||
</Routes>
|
||||
|
||||
@@ -9,22 +9,17 @@ import apolloLogger from "apollo-link-logger";
|
||||
import { auth } from "../firebase/firebase.utils";
|
||||
import { SentryLink } from "apollo-link-sentry";
|
||||
|
||||
const errorLink = onError(
|
||||
({ graphQLErrors, networkError, operation, forward }) => {
|
||||
if (graphQLErrors)
|
||||
graphQLErrors.forEach(({ message, locations, path }) =>
|
||||
console.log(
|
||||
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
|
||||
)
|
||||
);
|
||||
if (networkError)
|
||||
console.log(`[Network error]: ${JSON.stringify(networkError)}`);
|
||||
console.log(operation.getContext());
|
||||
}
|
||||
);
|
||||
const errorLink = onError(({ graphQLErrors, networkError, operation, forward }) => {
|
||||
if (graphQLErrors)
|
||||
graphQLErrors.forEach(({ message, locations, path }) =>
|
||||
console.log(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`)
|
||||
);
|
||||
if (networkError) console.log(`[Network error]: ${JSON.stringify(networkError)}`);
|
||||
console.log(operation.getContext());
|
||||
});
|
||||
|
||||
const httpLink = new HttpLink({
|
||||
uri: import.meta.env.VITE_APP_GRAPHQL_ENDPOINT,
|
||||
uri: import.meta.env.VITE_APP_GRAPHQL_ENDPOINT
|
||||
});
|
||||
|
||||
const wsLink = new WebSocketLink({
|
||||
@@ -33,25 +28,23 @@ const wsLink = new WebSocketLink({
|
||||
lazy: true,
|
||||
reconnect: true,
|
||||
connectionParams: async () => {
|
||||
const token =
|
||||
auth.currentUser && (await auth.currentUser.getIdToken(true));
|
||||
const token = auth.currentUser && (await auth.currentUser.getIdToken(true));
|
||||
if (token) {
|
||||
return {
|
||||
headers: {
|
||||
authorization: token ? `Bearer ${token}` : "",
|
||||
},
|
||||
authorization: token ? `Bearer ${token}` : ""
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const subscriptionMiddleware = {
|
||||
applyMiddleware: async (options, next) => {
|
||||
options.authToken =
|
||||
auth.currentUser && (await auth.currentUser.getIdToken(true));
|
||||
options.authToken = auth.currentUser && (await auth.currentUser.getIdToken(true));
|
||||
next();
|
||||
},
|
||||
}
|
||||
};
|
||||
wsLink.subscriptionClient.use([subscriptionMiddleware]);
|
||||
|
||||
@@ -67,10 +60,7 @@ const link = new HttpLink.split(
|
||||
// "##",
|
||||
// query
|
||||
// );
|
||||
return (
|
||||
definition.kind === "OperationDefinition" &&
|
||||
definition.operation === "subscription"
|
||||
);
|
||||
return definition.kind === "OperationDefinition" && definition.operation === "subscription";
|
||||
},
|
||||
wsLink,
|
||||
httpLink
|
||||
@@ -84,8 +74,8 @@ const authLink = setContext((_, { headers }) => {
|
||||
return {
|
||||
headers: {
|
||||
...headers,
|
||||
authorization: token ? `Bearer ${token}` : "",
|
||||
},
|
||||
authorization: token ? `Bearer ${token}` : ""
|
||||
}
|
||||
};
|
||||
} else {
|
||||
console.log("We have no authorization header.");
|
||||
@@ -99,24 +89,22 @@ const retryLink = new RetryLink({
|
||||
delay: {
|
||||
initial: 500,
|
||||
max: 5,
|
||||
jitter: true,
|
||||
jitter: true
|
||||
},
|
||||
attempts: {
|
||||
max: 5,
|
||||
retryIf: (error, _operation) => !!error,
|
||||
},
|
||||
retryIf: (error, _operation) => !!error
|
||||
}
|
||||
});
|
||||
|
||||
const sentryLink = new SentryLink();
|
||||
|
||||
const middlewares = [];
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
if (import.meta.env.DEV) {
|
||||
middlewares.push(apolloLogger);
|
||||
}
|
||||
|
||||
middlewares.push(
|
||||
sentryLink.concat(retryLink.concat(errorLink.concat(authLink.concat(link))))
|
||||
);
|
||||
middlewares.push(sentryLink.concat(retryLink.concat(errorLink.concat(authLink.concat(link)))));
|
||||
|
||||
const cache = new InMemoryCache({});
|
||||
|
||||
@@ -126,10 +114,10 @@ export default new ApolloClient({
|
||||
connectToDevTools: process.env.NODE_ENV !== "production",
|
||||
defaultOptions: {
|
||||
query: {
|
||||
fetchPolicy: "network-only",
|
||||
fetchPolicy: "network-only"
|
||||
},
|
||||
watchQuery: {
|
||||
fetchPolicy: "network-only",
|
||||
},
|
||||
},
|
||||
fetchPolicy: "network-only"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -34,4 +34,4 @@ ReactDOM.createRoot(document.getElementById("root")).render(
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
);
|
||||
console.log("Connecting to endpoint: ", process.env.VITE_APP_GRAPHQL_ENDPOINT);
|
||||
console.log("Connecting to endpoint: ", import.meta.env.VITE_APP_GRAPHQL_ENDPOINT);
|
||||
|
||||
@@ -65,6 +65,11 @@
|
||||
"getPolling": "filewatcher__getPolling"
|
||||
}
|
||||
},
|
||||
|
||||
"audit": {
|
||||
"toMain": { "browseForFile": "audit__browseForFile" },
|
||||
"toRenderer": { "auditClaimsArray": "audit__filepath" }
|
||||
},
|
||||
"estimate": {
|
||||
"toRenderer": {
|
||||
"estimateDecodeStart": "estimatedecode__start",
|
||||
|
||||
@@ -5,8 +5,9 @@ import {
|
||||
setUpdateAvailable,
|
||||
setUpdateProgress,
|
||||
setWatchedPaths,
|
||||
setWatcherStatus,
|
||||
setWatcherStatus
|
||||
} from "../redux/application/application.actions";
|
||||
import { calculateAudit } from "../redux/reporting/reporting.actions";
|
||||
import { setScanEstimateList } from "../redux/scan/scan.actions";
|
||||
import { store } from "../redux/store";
|
||||
import { signOutStart } from "../redux/user/user.actions";
|
||||
@@ -18,96 +19,65 @@ ipcRenderer.on("test-toRenderer", (event, obj) => {
|
||||
console.log("test-toRenderer", obj);
|
||||
});
|
||||
|
||||
ipcRenderer.on(
|
||||
ipcTypes.fileWatcher.toRenderer.filepathsList,
|
||||
(event, obj) => {
|
||||
store.dispatch(setWatchedPaths(obj));
|
||||
}
|
||||
);
|
||||
ipcRenderer.on(ipcTypes.fileWatcher.toRenderer.filepathsList, (event, obj) => {
|
||||
store.dispatch(setWatchedPaths(obj));
|
||||
});
|
||||
|
||||
//Filewatcher Status Section
|
||||
ipcRenderer.on(
|
||||
ipcTypes.fileWatcher.toRenderer.startSuccess,
|
||||
(event, obj) => {
|
||||
store.dispatch(setWatcherStatus("Started"));
|
||||
}
|
||||
);
|
||||
ipcRenderer.on(
|
||||
ipcTypes.fileWatcher.toRenderer.stopSuccess,
|
||||
(event, obj) => {
|
||||
store.dispatch(setWatcherStatus("Stopped"));
|
||||
}
|
||||
);
|
||||
ipcRenderer.on(ipcTypes.fileWatcher.toRenderer.startSuccess, (event, obj) => {
|
||||
store.dispatch(setWatcherStatus("Started"));
|
||||
});
|
||||
ipcRenderer.on(ipcTypes.fileWatcher.toRenderer.stopSuccess, (event, obj) => {
|
||||
store.dispatch(setWatcherStatus("Stopped"));
|
||||
});
|
||||
ipcRenderer.on(ipcTypes.fileWatcher.toRenderer.error, (event, obj) => {
|
||||
store.dispatch(setWatcherStatus(obj));
|
||||
});
|
||||
|
||||
//Estimate Section
|
||||
ipcRenderer.on(
|
||||
ipcTypes.estimate.toRenderer.getCloseDate,
|
||||
async (event, { filepath, clm_no }) => {
|
||||
const close_date = await GetR4PDateWithClaim(clm_no);
|
||||
ipcRenderer.send(ipcTypes.app.toMain.importJob, {
|
||||
filepath,
|
||||
close_date,
|
||||
});
|
||||
}
|
||||
);
|
||||
ipcRenderer.on(ipcTypes.estimate.toRenderer.getCloseDate, async (event, { filepath, clm_no }) => {
|
||||
const close_date = await GetR4PDateWithClaim(clm_no);
|
||||
ipcRenderer.send(ipcTypes.app.toMain.importJob, {
|
||||
filepath,
|
||||
close_date
|
||||
});
|
||||
});
|
||||
|
||||
ipcRenderer.on(
|
||||
ipcTypes.estimate.toRenderer.estimateDecodeSuccess,
|
||||
async (event, obj) => {
|
||||
await UpsertEstimate(obj);
|
||||
}
|
||||
);
|
||||
ipcRenderer.on(ipcTypes.estimate.toRenderer.estimateDecodeSuccess, async (event, obj) => {
|
||||
await UpsertEstimate(obj);
|
||||
});
|
||||
|
||||
ipcRenderer.on(ipcTypes.store.response, (event, obj) => {
|
||||
store.dispatch(setSettings(obj));
|
||||
});
|
||||
|
||||
//FileScan Section
|
||||
ipcRenderer.on(
|
||||
ipcTypes.fileScan.toRenderer.scanFilePathsResponse,
|
||||
async (event, listOfEstimates) => {
|
||||
store.dispatch(setScanEstimateList(listOfEstimates));
|
||||
}
|
||||
);
|
||||
ipcRenderer.on(ipcTypes.fileScan.toRenderer.scanFilePathsResponse, async (event, listOfEstimates) => {
|
||||
store.dispatch(setScanEstimateList(listOfEstimates));
|
||||
});
|
||||
|
||||
ipcRenderer.on(
|
||||
ipcTypes.app.toRenderer.updateAvailable,
|
||||
async (event, updateInfo) => {
|
||||
store.dispatch(setUpdateAvailable(updateInfo));
|
||||
}
|
||||
);
|
||||
ipcRenderer.on(ipcTypes.app.toRenderer.updateAvailable, async (event, updateInfo) => {
|
||||
store.dispatch(setUpdateAvailable(updateInfo));
|
||||
});
|
||||
|
||||
ipcRenderer.on(
|
||||
ipcTypes.app.toRenderer.downloadProgress,
|
||||
async (event, progress) => {
|
||||
store.dispatch(setUpdateProgress(progress));
|
||||
}
|
||||
);
|
||||
ipcRenderer.on(ipcTypes.app.toRenderer.downloadProgress, async (event, progress) => {
|
||||
store.dispatch(setUpdateProgress(progress));
|
||||
});
|
||||
|
||||
ipcRenderer.on(
|
||||
ipcTypes.app.toRenderer.signOut,
|
||||
async (event, progress) => {
|
||||
store.dispatch(signOutStart());
|
||||
}
|
||||
);
|
||||
ipcRenderer.on(ipcTypes.app.toRenderer.signOut, async (event, progress) => {
|
||||
store.dispatch(signOutStart());
|
||||
});
|
||||
|
||||
ipcRenderer.on(
|
||||
ipcTypes.app.toRenderer.setReleaseNotes,
|
||||
async (event, releaseNotes) => {
|
||||
store.dispatch(setReleaseNotes(releaseNotes));
|
||||
}
|
||||
);
|
||||
ipcRenderer.on(ipcTypes.app.toRenderer.setReleaseNotes, async (event, releaseNotes) => {
|
||||
store.dispatch(setReleaseNotes(releaseNotes));
|
||||
});
|
||||
|
||||
ipcRenderer.on(
|
||||
ipcTypes.app.toRenderer.appVersion,
|
||||
async (event, appversion) => {
|
||||
window.$crisp.push([
|
||||
"set",
|
||||
"session:data",
|
||||
[[["rps-version", appversion]]],
|
||||
]);
|
||||
}
|
||||
);
|
||||
ipcRenderer.on(ipcTypes.app.toRenderer.appVersion, async (event, appversion) => {
|
||||
window.$crisp.push(["set", "session:data", [[["rps-version", appversion]]]]);
|
||||
});
|
||||
|
||||
//HAndle Autdit
|
||||
|
||||
ipcRenderer.on(ipcTypes.audit.toRenderer.auditClaimsArray, async (event, claimsArray) => {
|
||||
store.dispatch(calculateAudit(claimsArray));
|
||||
});
|
||||
|
||||
@@ -2,27 +2,35 @@ import ReportingActionTypes from "./reporting.types";
|
||||
|
||||
export const queryReportingData = ({ startDate, endDate }) => ({
|
||||
type: ReportingActionTypes.QUERY_REPORTING_DATA,
|
||||
payload: { startDate, endDate },
|
||||
payload: { startDate, endDate }
|
||||
});
|
||||
|
||||
export const setReportingData = (data) => ({
|
||||
type: ReportingActionTypes.SET_REPORTING_DATA,
|
||||
payload: data,
|
||||
payload: data
|
||||
});
|
||||
|
||||
export const calculateScorecard = (data) => ({
|
||||
type: ReportingActionTypes.CALCULATE_SCORE_CARD,
|
||||
payload: data,
|
||||
payload: data
|
||||
});
|
||||
export const setScoreCard = (data) => ({
|
||||
type: ReportingActionTypes.SET_SCORE_CARD,
|
||||
payload: data,
|
||||
payload: data
|
||||
});
|
||||
export const setReportingError = (data) => ({
|
||||
type: ReportingActionTypes.SET_REPORTING_ERROR,
|
||||
payload: data,
|
||||
payload: data
|
||||
});
|
||||
export const toggleGroupVerified = (jobId) => ({
|
||||
type: ReportingActionTypes.TOGGLE_GROUP_VERIFIED,
|
||||
payload: jobId,
|
||||
payload: jobId
|
||||
});
|
||||
export const calculateAudit = (claimsArrayFromAudit) => ({
|
||||
type: ReportingActionTypes.CALCULATE_AUDIT,
|
||||
payload: claimsArrayFromAudit
|
||||
});
|
||||
export const setAuditResults = (auditResults) => ({
|
||||
type: ReportingActionTypes.SET_AUDIT_RESULTS,
|
||||
payload: auditResults
|
||||
});
|
||||
|
||||
@@ -4,34 +4,23 @@ import { all, call, put, select, takeLatest } from "redux-saga/effects";
|
||||
import client from "../../graphql/GraphQLClient";
|
||||
import { REPORTING_GET_JOBS } from "../../graphql/reporting.queries";
|
||||
import ipcTypes from "../../ipc.types";
|
||||
import {
|
||||
CalculateJobRpsDollars,
|
||||
CalculateJobRpsPc,
|
||||
} from "../../util/CalculateJobRps";
|
||||
import { CalculateJobRpsDollars, CalculateJobRpsPc } from "../../util/CalculateJobRps";
|
||||
import GetJobTarget from "../../util/GetJobTarget";
|
||||
import {
|
||||
calculateScorecard,
|
||||
setReportingData,
|
||||
setScoreCard,
|
||||
setReportingError,
|
||||
} from "./reporting.actions";
|
||||
import { calculateScorecard, setReportingData, setScoreCard, setReportingError } from "./reporting.actions";
|
||||
import ReportingApplicationTypes from "./reporting.types";
|
||||
|
||||
const { log, ipcRenderer } = window;
|
||||
|
||||
export function* onQueryReportData() {
|
||||
yield takeLatest(
|
||||
ReportingApplicationTypes.QUERY_REPORTING_DATA,
|
||||
queryReportingData
|
||||
);
|
||||
yield takeLatest(ReportingApplicationTypes.QUERY_REPORTING_DATA, queryReportingData);
|
||||
}
|
||||
export function* queryReportingData({ payload: { startDate, endDate } }) {
|
||||
const result = yield client.query({
|
||||
query: REPORTING_GET_JOBS,
|
||||
variables: {
|
||||
startDate: startDate.format("YYYY-MM-DD"),
|
||||
endDate: endDate.format("YYYY-MM-DD"),
|
||||
},
|
||||
endDate: endDate.format("YYYY-MM-DD")
|
||||
}
|
||||
});
|
||||
if (result.errors) {
|
||||
log.error("Error fetching report data.", result.errors);
|
||||
@@ -42,25 +31,66 @@ export function* queryReportingData({ payload: { startDate, endDate } }) {
|
||||
}
|
||||
|
||||
export function* onSetReportData() {
|
||||
yield takeLatest(
|
||||
ReportingApplicationTypes.SET_REPORTING_DATA,
|
||||
handleSetReportData
|
||||
);
|
||||
yield takeLatest(ReportingApplicationTypes.SET_REPORTING_DATA, handleSetReportData);
|
||||
}
|
||||
export function* handleSetReportData({ payload: jobs }) {
|
||||
// yield put(calculateScorecard(jobs));
|
||||
}
|
||||
export function* onCalculateAudit() {
|
||||
yield takeLatest(ReportingApplicationTypes.CALCULATE_AUDIT, handleCalculateAudit);
|
||||
}
|
||||
export function* handleCalculateAudit({ payload: claimsArrayFromAudit }) {
|
||||
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 = {};
|
||||
claimsArrayFromAudit.forEach((claim) => {
|
||||
const cleansedClaimNo = claim.clm_no.replace(/^0+/, "").trim();
|
||||
claimsArrayHashObject[cleansedClaimNo] = claim;
|
||||
});
|
||||
|
||||
const JobsThatDontMatch = [];
|
||||
|
||||
rpsJobs.forEach((rpsJob) => {
|
||||
const matchingAuditJob = claimsArrayHashObject[rpsJob.clm_no];
|
||||
|
||||
if (Math.abs(rpsJob.expectedRpsDollars.getAmount() / 100 - matchingAuditJob.expected_rps_dollars) > 0.01) {
|
||||
let styles =
|
||||
"font-weight: bold; font-size: 22px;color: yellow; 6px 6px 0 rgb(226,91,14) , 9px 9px 0 rgb(245,221,8) , 12px 12px 0 rgb(5,148,68) ";
|
||||
console.log(
|
||||
"%c %s",
|
||||
styles,
|
||||
`Expected Savings Mismatch for ${rpsJob.clm_no} || ${rpsJob.expectedRpsDollars.toFormat()} >> ${
|
||||
matchingAuditJob.expected_rps_dollars
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
if (Math.abs(rpsJob.jobRpsDollars.getAmount() / 100 - matchingAuditJob.actual_rps_dollars) > 0.01) {
|
||||
let styles =
|
||||
"font-weight: bold; font-size: 22px;color: red; 6px 6px 0 rgb(226,91,14) , 9px 9px 0 rgb(245,221,8) , 12px 12px 0 rgb(5,148,68) ";
|
||||
console.log(
|
||||
"%c %s",
|
||||
styles,
|
||||
`Actual Savings Mismatch for ${rpsJob.clm_no} || ${rpsJob.jobRpsDollars.toFormat()} >> ${
|
||||
matchingAuditJob.actual_rps_dollars
|
||||
}`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function* onCalculateScoreCard() {
|
||||
yield takeLatest(
|
||||
ReportingApplicationTypes.CALCULATE_SCORE_CARD,
|
||||
handleCalculateScoreCard
|
||||
);
|
||||
yield takeLatest(ReportingApplicationTypes.CALCULATE_SCORE_CARD, handleCalculateScoreCard);
|
||||
}
|
||||
export function* handleCalculateScoreCard({ payload: jobs }) {
|
||||
try {
|
||||
ipcRenderer.send(ipcTypes.app.toMain.track, {
|
||||
event: "CALCULATE_SCORECARD",
|
||||
event: "CALCULATE_SCORECARD"
|
||||
});
|
||||
|
||||
const targets = yield select((state) => state.user.bodyshop.targets);
|
||||
@@ -77,7 +107,7 @@ export function* handleCalculateScoreCard({ payload: jobs }) {
|
||||
yield put(
|
||||
setReportingError({
|
||||
message: "There is an issue with the following jobs.",
|
||||
jobs: [...jobsWithNoGroup],
|
||||
jobs: [...jobsWithNoGroup]
|
||||
})
|
||||
);
|
||||
return;
|
||||
@@ -92,44 +122,28 @@ export function* handleCalculateScoreCard({ payload: jobs }) {
|
||||
allJobsSumActPrice: Dinero(),
|
||||
currentRpsPc: 0,
|
||||
targetRpsPc: 0,
|
||||
scatterChart: _.sortBy(
|
||||
groups,
|
||||
[(group) => group.toLowerCase()],
|
||||
["desc"]
|
||||
).reduce((acc, val) => {
|
||||
scatterChart: _.sortBy(groups, [(group) => group.toLowerCase()], ["desc"]).reduce((acc, val) => {
|
||||
return { ...acc, [val]: [] };
|
||||
}, {}),
|
||||
}, {})
|
||||
};
|
||||
|
||||
//Get the RPS on a per job basis.
|
||||
jobs = jobs.map((job) => {
|
||||
const { actPriceSum, jobRpsDollars } = CalculateJobRpsDollars(job, true);
|
||||
const { dbPriceSum, jobRpsPc } = CalculateJobRpsPc(
|
||||
job,
|
||||
jobRpsDollars,
|
||||
true
|
||||
);
|
||||
const jobTarget = GetJobTarget({group:job.group, v_age:job.v_age, targets,close_date: job.close_date});
|
||||
scoreCard.shopRpsTotalDollars = scoreCard.shopRpsTotalDollars.add(
|
||||
jobRpsDollars
|
||||
);
|
||||
const { dbPriceSum, jobRpsPc } = CalculateJobRpsPc(job, jobRpsDollars, true);
|
||||
const jobTarget = GetJobTarget({ group: job.group, v_age: job.v_age, targets, close_date: job.close_date });
|
||||
scoreCard.shopRpsTotalDollars = scoreCard.shopRpsTotalDollars.add(jobRpsDollars);
|
||||
const expectedRpsDollars = dbPriceSum.percentage(jobTarget * 100);
|
||||
scoreCard.shopRpsExpectedDollars = scoreCard.shopRpsExpectedDollars.add(
|
||||
expectedRpsDollars
|
||||
);
|
||||
scoreCard.shopRpsExpectedDollars = scoreCard.shopRpsExpectedDollars.add(expectedRpsDollars);
|
||||
|
||||
scoreCard.allJobsSumDbPrice = scoreCard.allJobsSumDbPrice.add(dbPriceSum);
|
||||
scoreCard.allJobsSumActPrice = scoreCard.allJobsSumActPrice.add(
|
||||
actPriceSum
|
||||
);
|
||||
scoreCard.allJobsSumActPrice = scoreCard.allJobsSumActPrice.add(actPriceSum);
|
||||
|
||||
const deviationPc = Math.round((jobRpsPc - jobTarget) * 1000) / 10;
|
||||
|
||||
scoreCard.scatterChart[job.group].push({
|
||||
deviationPc: isNaN(deviationPc) ? -100 : deviationPc,
|
||||
deviationDollars: (
|
||||
jobRpsDollars.subtract(expectedRpsDollars).getAmount() / 100
|
||||
).toFixed(2),
|
||||
deviationDollars: (jobRpsDollars.subtract(expectedRpsDollars).getAmount() / 100).toFixed(2),
|
||||
age: job.v_age,
|
||||
dbPriceSum,
|
||||
dbPriceSumAmt: dbPriceSum.getAmount() / 100,
|
||||
@@ -138,7 +152,7 @@ export function* handleCalculateScoreCard({ payload: jobs }) {
|
||||
vehicle: `${job.v_model_yr} ${job.v_makedesc} ${job.v_model} (${job.v_type}) - ${job.group}`,
|
||||
clm_no: job.clm_no,
|
||||
jobRpsDollars,
|
||||
jobRpsPc: isNaN(jobRpsPc) ? -1 : jobRpsPc,
|
||||
jobRpsPc: isNaN(jobRpsPc) ? -1 : jobRpsPc
|
||||
});
|
||||
|
||||
//sum db price * percentage expected.
|
||||
@@ -149,20 +163,14 @@ export function* handleCalculateScoreCard({ payload: jobs }) {
|
||||
dbPriceSum,
|
||||
jobRpsPc,
|
||||
jobTarget,
|
||||
expectedRpsDollars,
|
||||
expectedRpsDollars
|
||||
};
|
||||
});
|
||||
|
||||
scoreCard.varianceDollars = scoreCard.shopRpsTotalDollars.subtract(
|
||||
scoreCard.shopRpsExpectedDollars
|
||||
);
|
||||
scoreCard.varianceDollars = scoreCard.shopRpsTotalDollars.subtract(scoreCard.shopRpsExpectedDollars);
|
||||
|
||||
scoreCard.currentRpsPc =
|
||||
scoreCard.shopRpsTotalDollars.getAmount() /
|
||||
scoreCard.allJobsSumDbPrice.getAmount();
|
||||
scoreCard.targetRpsPc =
|
||||
scoreCard.shopRpsExpectedDollars.getAmount() /
|
||||
scoreCard.allJobsSumDbPrice.getAmount();
|
||||
scoreCard.currentRpsPc = scoreCard.shopRpsTotalDollars.getAmount() / scoreCard.allJobsSumDbPrice.getAmount();
|
||||
scoreCard.targetRpsPc = scoreCard.shopRpsExpectedDollars.getAmount() / scoreCard.allJobsSumDbPrice.getAmount();
|
||||
|
||||
scoreCard.variancePc = scoreCard.currentRpsPc - scoreCard.targetRpsPc;
|
||||
//Set the data.
|
||||
@@ -171,16 +179,12 @@ export function* handleCalculateScoreCard({ payload: jobs }) {
|
||||
} catch (error) {
|
||||
ipcRenderer.send(ipcTypes.app.toMain.track, {
|
||||
event: "CALCULATE_SCORE_CARD_ERROR",
|
||||
error: error,
|
||||
error: error
|
||||
});
|
||||
yield put(setReportingError({ message: error, jobs: [] }));
|
||||
}
|
||||
}
|
||||
|
||||
export function* reportingSagas() {
|
||||
yield all([
|
||||
call(onQueryReportData),
|
||||
call(onSetReportData),
|
||||
call(onCalculateScoreCard),
|
||||
]);
|
||||
yield all([call(onQueryReportData), call(onSetReportData), call(onCalculateScoreCard), call(onCalculateAudit)]);
|
||||
}
|
||||
|
||||
@@ -5,5 +5,7 @@ const ReportingActionTypes = {
|
||||
SET_SCORE_CARD: "SET_SCORE_CARD",
|
||||
SET_REPORTING_ERROR: "SET_REPORTING_ERROR",
|
||||
TOGGLE_GROUP_VERIFIED: "TOGGLE_GROUP_VERIFIED",
|
||||
CALCULATE_AUDIT: "CALCULATE_AUDIT",
|
||||
SET_AUDIT_RESULTS: "SET_AUDIT_RESULTS"
|
||||
};
|
||||
export default ReportingActionTypes;
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
import { message } from "antd";
|
||||
import dayjs from '../../util/day.js';
|
||||
import dayjs from "../../util/day.js";
|
||||
//import LogRocket from "logrocket";
|
||||
import * as Sentry from "@sentry/electron";
|
||||
import { sendPasswordResetEmail, signInWithEmailAndPassword, signOut } from "firebase/auth";
|
||||
import { all, call, delay, put, takeLatest } from "redux-saga/effects";
|
||||
import {
|
||||
auth,
|
||||
getCurrentUser,
|
||||
updateCurrentUser,
|
||||
} from "../../firebase/firebase.utils";
|
||||
import { auth, getCurrentUser, updateCurrentUser } from "../../firebase/firebase.utils";
|
||||
import client from "../../graphql/GraphQLClient";
|
||||
import {
|
||||
QUERY_BODYSHOP,
|
||||
QUERY_NOTIFICATIONS,
|
||||
} from "../../graphql/bodyshop.queries";
|
||||
import { UPSERT_USER } from "../../graphql/user.queries";
|
||||
import { QUERY_BODYSHOP, QUERY_NOTIFICATIONS } from "../../graphql/bodyshop.queries";
|
||||
import ipcTypes from "../../ipc.types";
|
||||
import {
|
||||
checkForNotification,
|
||||
@@ -27,7 +20,7 @@ import {
|
||||
signOutFailure,
|
||||
signOutSuccess,
|
||||
unauthorizedUser,
|
||||
updateUserDetailsSuccess,
|
||||
updateUserDetailsSuccess
|
||||
} from "./user.actions";
|
||||
import UserActionTypes from "./user.types";
|
||||
|
||||
@@ -40,31 +33,29 @@ export function* signInWithEmail({ payload: { email, password } }) {
|
||||
try {
|
||||
ipcRenderer.send(ipcTypes.app.toMain.track, {
|
||||
event: "SIGN_IN_ATTEMPT",
|
||||
email: email,
|
||||
email: email
|
||||
});
|
||||
const { user } = yield auth.signInWithEmailAndPassword(email, password);
|
||||
const { user } = yield signInWithEmailAndPassword(auth, email, password);
|
||||
|
||||
const result = yield client.mutate({
|
||||
mutation: UPSERT_USER,
|
||||
variables: { authEmail: user.email, authToken: user.uid },
|
||||
});
|
||||
if (!result.errors) {
|
||||
// const result = yield client.mutate({
|
||||
// mutation: UPSERT_USER,
|
||||
// variables: { authEmail: user.email, authToken: user.uid },
|
||||
// });
|
||||
if (user) {
|
||||
yield put(
|
||||
signInSuccess({
|
||||
uid: user.uid,
|
||||
email: user.email,
|
||||
displayName: user.displayName,
|
||||
photoURL: user.photoURL,
|
||||
authorized: true,
|
||||
authorized: true
|
||||
})
|
||||
);
|
||||
} else {
|
||||
yield put(signInFailure(JSON.stringify(result.errors)));
|
||||
yield put(signInFailure());
|
||||
}
|
||||
} catch (error) {
|
||||
yield put(
|
||||
signInFailure({ ...error, messagePretty: ErrorFormatter(error.code) })
|
||||
);
|
||||
yield put(signInFailure({ ...error, messagePretty: ErrorFormatter(error.code) }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +77,7 @@ export function* isUserAuthenticated() {
|
||||
email: user.email,
|
||||
displayName: user.displayName,
|
||||
photoURL: user.photoURL,
|
||||
authorized: true,
|
||||
authorized: true
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
@@ -101,10 +92,10 @@ export function* onSignOutStart() {
|
||||
export function* signOutStart() {
|
||||
try {
|
||||
ipcRenderer.send(ipcTypes.app.toMain.track, {
|
||||
event: "SIGN_OUT",
|
||||
event: "SIGN_OUT"
|
||||
});
|
||||
ipcRenderer.send(ipcTypes.fileWatcher.toMain.stop);
|
||||
yield auth.signOut();
|
||||
yield signOut(auth);
|
||||
yield put(signOutSuccess());
|
||||
localStorage.removeItem("token");
|
||||
} catch (error) {
|
||||
@@ -140,37 +131,26 @@ export function* signInSuccessSaga({ payload }) {
|
||||
|
||||
ipcRenderer.send(ipcTypes.app.toMain.track, {
|
||||
event: "SIGN_IN_SUCCESS",
|
||||
email: payload.email,
|
||||
email: payload.email
|
||||
});
|
||||
const shop = yield client.query({ query: QUERY_BODYSHOP });
|
||||
if (shop.data.bodyshops.length > 0) {
|
||||
yield put(setBodyshop(shop.data.bodyshops[0]));
|
||||
ipcRenderer.send(
|
||||
ipcTypes.app.toMain.setAcceptableInsCoNm,
|
||||
shop.data.bodyshops[0].accepted_ins_co
|
||||
);
|
||||
ipcRenderer.send(ipcTypes.app.toMain.setAcceptableInsCoNm, shop.data.bodyshops[0].accepted_ins_co);
|
||||
ipcRenderer.send(ipcTypes.fileWatcher.toMain.start, {
|
||||
startup: true,
|
||||
startup: true
|
||||
});
|
||||
yield put(checkForNotification());
|
||||
//Check for notifications, and continue to check.
|
||||
window.$crisp.push([
|
||||
"set",
|
||||
"user:company",
|
||||
[shop.data.bodyshops[0].shopname],
|
||||
]);
|
||||
window.$crisp.push([
|
||||
"set",
|
||||
"user:nickname",
|
||||
[payload.displayName || payload.email],
|
||||
]);
|
||||
window.$crisp.push(["set", "user:company", [shop.data.bodyshops[0].shopname]]);
|
||||
window.$crisp.push(["set", "user:nickname", [payload.displayName || payload.email]]);
|
||||
window.$crisp.push(["set", "user:email", [payload.email]]);
|
||||
window.$crisp.push(["set", "session:segments", [["rps-user"]]]);
|
||||
ipcRenderer.send(ipcTypes.app.toMain.getAppVersion);
|
||||
|
||||
Sentry.setUser({
|
||||
email: payload.email,
|
||||
username: payload.displayName || payload.email,
|
||||
username: payload.displayName || payload.email
|
||||
});
|
||||
} else {
|
||||
console.log("No bodyshop has been associated.");
|
||||
@@ -188,17 +168,14 @@ export function* signInSuccessSaga({ payload }) {
|
||||
}
|
||||
|
||||
export function* onCheckForNotification() {
|
||||
yield takeLatest(
|
||||
UserActionTypes.CHECK_FOR_NOTIFICATION,
|
||||
checkForNotificationSaga
|
||||
);
|
||||
yield takeLatest(UserActionTypes.CHECK_FOR_NOTIFICATION, checkForNotificationSaga);
|
||||
}
|
||||
export function* checkForNotificationSaga() {
|
||||
const {
|
||||
data: { notifications },
|
||||
data: { notifications }
|
||||
} = yield client.query({
|
||||
query: QUERY_NOTIFICATIONS,
|
||||
variables: { now: dayjs() },
|
||||
variables: { now: dayjs() }
|
||||
});
|
||||
|
||||
if (notifications) {
|
||||
@@ -209,18 +186,15 @@ export function* checkForNotificationSaga() {
|
||||
}
|
||||
|
||||
export function* onSendPasswordResetStart() {
|
||||
yield takeLatest(
|
||||
UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START,
|
||||
sendPasswordResetEmail
|
||||
);
|
||||
yield takeLatest(UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START, sendPasswordResetEmailFunc);
|
||||
}
|
||||
export function* sendPasswordResetEmail({ payload }) {
|
||||
export function* sendPasswordResetEmailFunc({ payload }) {
|
||||
try {
|
||||
ipcRenderer.send(ipcTypes.app.toMain.track, {
|
||||
event: "RESET_PASSWORD",
|
||||
email: payload,
|
||||
email: payload
|
||||
});
|
||||
yield auth.sendPasswordResetEmail(payload);
|
||||
yield sendPasswordResetEmail(auth, payload);
|
||||
|
||||
yield put(sendPasswordResetSuccess());
|
||||
message.success("Password reset sent succesfully.");
|
||||
@@ -236,7 +210,7 @@ export function* userSagas() {
|
||||
call(onUpdateUserDetails),
|
||||
call(onSignInSuccess),
|
||||
call(onSendPasswordResetStart),
|
||||
call(onCheckForNotification),
|
||||
call(onCheckForNotification)
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ export function WhichRulesetToApply(close_date) {
|
||||
(r) =>
|
||||
DateMoment.isSameOrAfter(r.range[0]) && DateMoment.isBefore(r.range[1])
|
||||
);
|
||||
console.log("Using ruleset:", newRuleSet);
|
||||
//console.log("Using ruleset:", newRuleSet);
|
||||
|
||||
return newRuleSet?.title;
|
||||
}
|
||||
Reference in New Issue
Block a user