Check for existing scrub results & add beta terms.
This commit is contained in:
@@ -203,5 +203,10 @@
|
||||
"title": "Release Notes for 1.4.2-beta.4",
|
||||
"date": "09/16/2025",
|
||||
"notes": "New Features:\r\n* Added part quantity toggle to shop setup. This will use the part quantity from the job lines when calculating RPS instead of assuming 1 per MPI guidelines.\r\n\r\nImprovements:\r\n* Updated ES integration to use newly specified API keys."
|
||||
},
|
||||
"1.5.0": {
|
||||
"title": "Release Notes for 1.5.0",
|
||||
"date": "10/14/2025",
|
||||
"notes": "New Features:\r\n* Direct integration to Estimate Scrubber is now available in Public Beta. Existing customers should automatically have access to this feature. If you encounter any issues, please reach out to support.\r\n* Added a quantity toggle to use line quantity instead of assuming 1 per MPI guidelines for RPS calculations.\r\n* Added date shortcuts to the report page for ease of use.\r\n* Added a floating shortcut to run 1 month and 3 month reports.\r\n\r\nImprovements:\r\n* Added better indicators for quantities and negative RPS values."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"productName": "ImEX RPS",
|
||||
"author": "ImEX Systems Inc. <support@thinkimex.com>",
|
||||
"description": "ImEX RPS",
|
||||
"version": "1.4.2-beta.8",
|
||||
"version": "1.5.0-alpha.1",
|
||||
"main": "electron/main.js",
|
||||
"homepage": "./",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Button, message } from "antd";
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import { Button, message, Tooltip } from "antd";
|
||||
import { useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_JOB_ESTIMATE_SCRUBBER } from "../../../graphql/jobs.queries";
|
||||
import ipcTypes from "../../../ipc.types";
|
||||
import { selectBodyshop } from "../../../redux/user/user.selectors";
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import { QUERY_JOB_ESTIMATE_SCRUBBER } from "../../../graphql/jobs.queries";
|
||||
import _ from "lodash";
|
||||
const { ipcRenderer } = window;
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -61,9 +60,17 @@ export function EstimateScrubberButton({ bodyshop, jobid, job }) {
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
const buttonDisabled = job?.g_bett_amt == null;
|
||||
|
||||
return buttonDisabled ? (
|
||||
<Tooltip title="Additional information is required to scrub this estimate. Please reimport the estimate to enable scrubbing.">
|
||||
<Button disabled={job?.g_bett_amt == null} loading={loading}>
|
||||
Scrub Estimate with Estimate Scrubber (BETA)
|
||||
</Button>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Button onClick={handleScrub} disabled={job?.g_bett_amt == null} loading={loading}>
|
||||
Scrub Estimate with Estimate Scrubber
|
||||
Scrub Estimate with Estimate Scrubber (BETA)
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ const { Title, Text, Link } = Typography;
|
||||
|
||||
export function EstimateScrubberResults({ bodyshop, jobid, job, esResults }) {
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const buttonDisabled = job?.g_bett_amt == null;
|
||||
|
||||
// Filter items based on search text
|
||||
const filteredItems = esResults?.items
|
||||
@@ -91,9 +92,13 @@ export function EstimateScrubberResults({ bodyshop, jobid, job, esResults }) {
|
||||
if (!esResults?.items?.length || job?.id !== esResults?.jobid) {
|
||||
return (
|
||||
<Result
|
||||
status={"info"}
|
||||
title="Estimate not yet scrubbed."
|
||||
subTitle="Run the estimate scrubber to see results here."
|
||||
status={buttonDisabled ? "error" : "info"}
|
||||
title={buttonDisabled ? "Unable to scrub." : "Estimate Not Scrubbed"}
|
||||
subTitle={
|
||||
buttonDisabled
|
||||
? "Additional information is required to scrub this estimate. Please reimport the estimate to enable scrubbing."
|
||||
: "Run the estimate scrubber to see results here."
|
||||
}
|
||||
extra={<EstimateScrubberButton key="es" jobid={job ? job.id : null} job={job ? job : null} />}
|
||||
/>
|
||||
|
||||
@@ -133,91 +138,94 @@ export function EstimateScrubberResults({ bodyshop, jobid, job, esResults }) {
|
||||
|
||||
return (
|
||||
<div style={{ padding: "16px" }}>
|
||||
<Space direction="vertical" size="large" style={{ width: "100%" }}>
|
||||
<Space>
|
||||
<Input
|
||||
placeholder="Search"
|
||||
prefix={<SearchOutlined />}
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
allowClear
|
||||
style={{ maxWidth: 400 }}
|
||||
/>
|
||||
{searchText && (
|
||||
<div style={{ marginTop: "8px" }}>
|
||||
<Text type="secondary">
|
||||
Showing {filteredItems.length} of {esResults.items.length} items
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
{Object.keys(groupedItems).length === 0 ? (
|
||||
<Result title="Estimate Scrubber did not find any issues." status="success" />
|
||||
) : (
|
||||
<Space direction="vertical" size="large" style={{ width: "100%" }}>
|
||||
<Space>
|
||||
<Input
|
||||
placeholder="Search"
|
||||
prefix={<SearchOutlined />}
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
allowClear
|
||||
style={{ maxWidth: 400 }}
|
||||
/>
|
||||
{searchText && (
|
||||
<div style={{ marginTop: "8px" }}>
|
||||
<Text type="secondary">
|
||||
Showing {filteredItems.length} of {esResults.items.length} items
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button
|
||||
style={{ float: "right" }}
|
||||
type="primary"
|
||||
href={esResults?.pdfUrl}
|
||||
target="_blank"
|
||||
disabled={!esResults?.pdfUrl}
|
||||
>
|
||||
View PDF
|
||||
</Button>
|
||||
<Button
|
||||
style={{ float: "right" }}
|
||||
type="link"
|
||||
href={esResults?.reportIssueUrl}
|
||||
target="_blank"
|
||||
disabled={!esResults?.reportIssueUrl}
|
||||
>
|
||||
Report Scrubbing Issue
|
||||
</Button>
|
||||
<Button
|
||||
style={{ float: "right" }}
|
||||
type="primary"
|
||||
href={esResults?.pdfUrl}
|
||||
target="_blank"
|
||||
disabled={!esResults?.pdfUrl}
|
||||
>
|
||||
View PDF
|
||||
</Button>
|
||||
<Button
|
||||
style={{ float: "right" }}
|
||||
type="link"
|
||||
href={esResults?.reportIssueUrl}
|
||||
target="_blank"
|
||||
disabled={!esResults?.reportIssueUrl}
|
||||
>
|
||||
Report Scrubbing Issue
|
||||
</Button>
|
||||
</Space>
|
||||
<Space wrap size={"small"}>
|
||||
{Object.entries(groupedItems).map(([category, items]) => {
|
||||
const config = categoryConfig[category] || { color: "default" };
|
||||
return (
|
||||
<Tag key={category} color={config.color}>
|
||||
{category}: {items.length}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
|
||||
<Collapse defaultActiveKey={sortedCategories} expandIconPosition="right" size="large">
|
||||
{sortedCategories.map((category) => {
|
||||
const items = groupedItems[category];
|
||||
const config = categoryConfig[category] || { color: "default", icon: "📄" };
|
||||
|
||||
return (
|
||||
<Panel
|
||||
header={
|
||||
<Space>
|
||||
{/* <span style={{ fontSize: "18px" }}>{config.icon}</span> */}
|
||||
<Text strong>{category}</Text>
|
||||
<Badge
|
||||
count={items.length}
|
||||
style={{
|
||||
backgroundColor:
|
||||
config.color === "blue" ? "#1890ff" : config.color === "orange" ? "#fa8c16" : "#52c41a"
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
}
|
||||
key={category}
|
||||
>
|
||||
<Table
|
||||
dataSource={items}
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
size="middle"
|
||||
rowKey={(record, index) => `${category}-${index}`}
|
||||
style={{ marginTop: "12px" }}
|
||||
scroll={{}}
|
||||
/>
|
||||
</Panel>
|
||||
);
|
||||
})}
|
||||
</Collapse>
|
||||
</Space>
|
||||
<Space wrap size={"small"}>
|
||||
{Object.entries(groupedItems).map(([category, items]) => {
|
||||
const config = categoryConfig[category] || { color: "default" };
|
||||
return (
|
||||
<Tag key={category} color={config.color}>
|
||||
{category}: {items.length}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
|
||||
{/* Grouped Results */}
|
||||
<Collapse defaultActiveKey={sortedCategories} expandIconPosition="right" size="large">
|
||||
{sortedCategories.map((category) => {
|
||||
const items = groupedItems[category];
|
||||
const config = categoryConfig[category] || { color: "default", icon: "📄" };
|
||||
|
||||
return (
|
||||
<Panel
|
||||
header={
|
||||
<Space>
|
||||
{/* <span style={{ fontSize: "18px" }}>{config.icon}</span> */}
|
||||
<Text strong>{category}</Text>
|
||||
<Badge
|
||||
count={items.length}
|
||||
style={{
|
||||
backgroundColor:
|
||||
config.color === "blue" ? "#1890ff" : config.color === "orange" ? "#fa8c16" : "#52c41a"
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
}
|
||||
key={category}
|
||||
>
|
||||
<Table
|
||||
dataSource={items}
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
size="middle"
|
||||
rowKey={(record, index) => `${category}-${index}`}
|
||||
style={{ marginTop: "12px" }}
|
||||
scroll={{}}
|
||||
/>
|
||||
</Panel>
|
||||
);
|
||||
})}
|
||||
</Collapse>
|
||||
</Space>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Card, Result } from "antd";
|
||||
import { Badge, Card, Result } from "antd";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_JOB_BY_PK } from "../../../graphql/jobs.queries";
|
||||
import { setSelectedJobTargetPc } from "../../../redux/application/application.actions";
|
||||
import { selectSelectedJobId } from "../../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../../redux/user/user.selectors";
|
||||
import ErrorResultAtom from "../../atoms/error-result/error-result.atom";
|
||||
import JobsPartsGraphAtom from "../../atoms/jobs-parts-graph/jobs-parts-graph.atom";
|
||||
import EstimateScrubberButton from "../../molecules/estimate-scrubber-button/estimate-scrubber-button.molecule";
|
||||
import EstimateScrubberResultsMolecule from "../../molecules/estimate-scruber-results/estimate-scrubber-results.molecule";
|
||||
import JobsDetailDescriptionMolecule from "../../molecules/jobs-detail-description/jobs-detail-description.molecule";
|
||||
import JobsLinesTableMolecule from "../../molecules/jobs-lines-table/jobs-lines-table.molecule";
|
||||
import JobsTargetsStatsMolecule from "../../molecules/jobs-targets-stats/jobs-targets-stats.molecule";
|
||||
import "./jobs-detail.organism.styles.scss";
|
||||
import { selectBodyshop } from "../../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -87,9 +86,11 @@ export function JobsDetailOrganism({ bodyshop, selectedJobId, setSelectedJobTarg
|
||||
<JobsLinesTableMolecule loading={loading} job={data ? data.jobs_by_pk : {}} />
|
||||
</Card>
|
||||
{bodyshop.es_api_key && (
|
||||
<Card id="es-results-card" title="Estimate Scrubber Results" extra={[]}>
|
||||
<EstimateScrubberResultsMolecule loading={loading} job={data ? data.jobs_by_pk : {}} />
|
||||
</Card>
|
||||
<Badge.Ribbon text="BETA" color="red">
|
||||
<Card id="es-results-card" title="Estimate Scrubber Results" extra={[]}>
|
||||
<EstimateScrubberResultsMolecule loading={loading} job={data ? data.jobs_by_pk : {}} />
|
||||
</Card>
|
||||
</Badge.Ribbon>
|
||||
)}
|
||||
<Card title="Parts Breakdown">
|
||||
<div
|
||||
|
||||
@@ -4,14 +4,16 @@ import _ from "lodash";
|
||||
import client from "../graphql/GraphQLClient";
|
||||
import { INSERT_NEW_JOB, QUERY_CLOSE_DATE_BY_CLM_NO, QUERY_JOB_BY_CLM_NO, UPDATE_JOB } from "../graphql/jobs.queries";
|
||||
import { QUERY_GROUPS_BY_MAKE_TYPE } from "../graphql/veh_group.queries";
|
||||
import ipcTypes from "../ipc.types";
|
||||
import { clearEsResults } from "../redux/application/application.actions.js";
|
||||
import { store } from "../redux/store";
|
||||
import TrucksList from "./trucks.json";
|
||||
import { WhichRulesetToApply } from "../util/constants.js";
|
||||
import dayjs from "../util/day.js";
|
||||
import CargoVanList from "./cargovans.json";
|
||||
import PassengerVanList from "./passengervans.json";
|
||||
import SuvList from "./suvs.json";
|
||||
import ipcTypes from "../ipc.types";
|
||||
import dayjs from "../util/day.js";
|
||||
import { WhichRulesetToApply } from "../util/constants.js";
|
||||
import TrucksList from "./trucks.json";
|
||||
|
||||
const { logger } = window;
|
||||
const { ipcRenderer } = window;
|
||||
|
||||
@@ -46,6 +48,7 @@ export async function GetR4PDateWithClaim(clm_no) {
|
||||
|
||||
export async function UpsertEstimate(job) {
|
||||
const shopId = store.getState().user.bodyshop.id;
|
||||
|
||||
//logger.info("Beginning Upserting job from Renderer.");
|
||||
ipcRenderer.send(ipcTypes.app.toMain.log.info, "Beginning Upserting job from Renderer.");
|
||||
const existingJobs = await client.query({
|
||||
@@ -88,6 +91,12 @@ export async function UpsertEstimate(job) {
|
||||
//This will now only preserve theg roup if it didn't require a re-import. If it did, reset the group.
|
||||
}
|
||||
|
||||
const currentScrubbedjobId = store.getState().application.esResults.jobid;
|
||||
|
||||
if (currentScrubbedjobId && currentScrubbedjobId === existingJobs.data.jobs[0].id) {
|
||||
store.dispatch(clearEsResults());
|
||||
}
|
||||
|
||||
logger.info("Attemping to update job.");
|
||||
await client.mutate({
|
||||
mutation: UPDATE_JOB,
|
||||
|
||||
@@ -61,3 +61,8 @@ export const setScrubResults = (scrubResults) => ({
|
||||
type: ApplicationActionTypes.SET_ES_RESULTS,
|
||||
payload: scrubResults
|
||||
});
|
||||
|
||||
|
||||
export const clearEsResults = () => ({
|
||||
type: ApplicationActionTypes.CLEAR_ES_RESULTS
|
||||
});
|
||||
@@ -75,6 +75,8 @@ const applicationReducer = (state = INITIAL_STATE, action) => {
|
||||
return { ...state, releaseNotes: action.payload };
|
||||
case ApplicationActionTypes.SET_ES_RESULTS:
|
||||
return { ...state, esResults: action.payload };
|
||||
case ApplicationActionTypes.CLEAR_ES_RESULTS:
|
||||
return { ...state, esResults: { jobid: null, identified_items: [] } };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ const ApplicationActionTypes = {
|
||||
SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE",
|
||||
SET_UPDATE_PROGRESS: "SET_UPDATE_PROGRESS",
|
||||
SET_RELEASE_NOTES: "SET_RELEASE_NOTES",
|
||||
SET_ES_RESULTS: "SET_ES_RESULTS"
|
||||
SET_ES_RESULTS: "SET_ES_RESULTS",
|
||||
CLEAR_ES_RESULTS: "CLEAR_ES_RESULTS"
|
||||
};
|
||||
export default ApplicationActionTypes;
|
||||
|
||||
Reference in New Issue
Block a user