Begin Audit Functionality testing.

This commit is contained in:
Patrick Fic
2024-04-18 14:25:41 -07:00
parent 60fa62fa77
commit cf14111a73
17 changed files with 2838 additions and 650 deletions

View File

@@ -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": {}
}

View 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);
}
});

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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",

View File

@@ -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>
),
},
)
}
]}
/>
);

View 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>
);
}

View File

@@ -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>

View File

@@ -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"
}
}
});

View File

@@ -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);

View File

@@ -65,6 +65,11 @@
"getPolling": "filewatcher__getPolling"
}
},
"audit": {
"toMain": { "browseForFile": "audit__browseForFile" },
"toRenderer": { "auditClaimsArray": "audit__filepath" }
},
"estimate": {
"toRenderer": {
"estimateDecodeStart": "estimatedecode__start",

View File

@@ -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));
});

View File

@@ -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
});

View File

@@ -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)]);
}

View File

@@ -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;

View File

@@ -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)
]);
}

View File

@@ -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;
}