Compare commits

...

40 Commits

Author SHA1 Message Date
Dave Richer
f28653546b Merged in feature/IO-2916-Remove-Beta-Switch-Legacy (pull request #1692)
feature/IO-2916-Remove-Beta-Switch-Legacy - Remove Beta Switch
2024-09-10 17:29:42 +00:00
Dave Richer
e742917876 feature/IO-2916-Remove-Beta-Switch-LEGACY - Remove cookie
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-10 13:16:46 -04:00
Dave Richer
04a193ef37 feature/IO-2916-Remove-Beta-Switch-LEGACY - Remove cookie
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-10 13:11:23 -04:00
Dave Richer
cf258e9a12 feature/IO-2916-Remove-Beta-Switch-Legacy - Remove Beta Switch
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-09 23:45:56 -04:00
Dave Richer
a230e15a66 Merged in release/2024-08-23-LEGACY (pull request #1652)
Release/2024 08 23 LEGACY
2024-08-22 14:44:41 +00:00
Allan Carr
011cb1b349 Merged in feature/IO-2887-Returnfrombill-Parts-Drawer-LEGACY (pull request #1638)
IO-2887 Null out BillData if returnfrombill is not available
2024-08-20 23:16:45 +00:00
Allan Carr
9b2cdddb88 IO-2887 Null out BillData if returnfrombill is not available
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-20 16:17:04 -07:00
Allan Carr
c71db5133d Merged in feature/IO-2887-Returnfrombill-Parts-Drawer-LEGACY (pull request #1636)
IO-2887 Returnfrombill Parts Drawer Legacy
2024-08-20 22:46:36 +00:00
Allan Carr
ee733434e6 IO-2887 Returnfrombill Parts Drawer Legacy
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-20 15:45:21 -07:00
Dave Richer
c2050e7e26 Merged in release/2024-08-16-LEGACY (pull request #1626)
Release/2024 08 16 LEGACY

Approved-by: Allan Carr
2024-08-16 23:31:21 +00:00
Allan Carr
c0096a0dec Merged in feature/IO-2884-Production-List-Filter-LEGACY (pull request #1621)
IO-2884 Production List Filter LEGACY

Approved-by: Dave Richer
2024-08-16 16:16:29 +00:00
Allan Carr
41e40b9165 IO-2884 Production List Filter LEGACY
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-16 09:16:32 -07:00
Dave Richer
1e566c82cd Merged in feature/IO-2878-Enhance-Beta-Switch-legacy (pull request #1608)
- Improve handle beta code (AIO Version)
2024-08-15 15:21:28 +00:00
Dave Richer
d45be60668 - Improve handle beta code (AIO Version)
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-15 11:19:26 -04:00
Allan Carr
62d5511337 Merged in feature/IO-2876-OpenSearch-Sorters-LEGACY (pull request #1604)
IO-2876 OpenSearch Sorters LEGACY

Approved-by: Dave Richer
2024-08-14 19:37:26 +00:00
Allan Carr
ac37074666 IO-2876 OpenSearch Sorters LEGACY
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-14 11:47:34 -07:00
Dave Richer
9467c57c8f Merged in feature/IO-2878-Enhance-Beta-Switch-legacy (pull request #1600)
- Improve handle beta code (AIO Version)
2024-08-14 17:41:59 +00:00
Dave Richer
87f591ce1b - Improve handle beta code (AIO Version)
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-14 13:33:27 -04:00
Allan Carr
c3a950088e Merged in release/2024-08-16-LEGACY (pull request #1592)
Release/2024 08 16 LEGACY

Approved-by: Dave Richer
2024-08-12 23:54:09 +00:00
Allan Carr
704e35e0b0 Merged in feature/IO-2564-LEGACY-Row-Expander-Drawers (pull request #1589)
IO-2564 LEGACY Row Expander Drawers
2024-08-12 22:17:22 +00:00
Allan Carr
d616d204d0 Merged in feature/IO-2861-LEGACY-Disable-Vendor-and-Invoice-#-on-Edit-of-InHouse (pull request #1590)
IO-2861 LEGACY Disable Editing of InHouse Invoice for Vendor and Invoice Number
2024-08-12 22:16:24 +00:00
Allan Carr
8eae657bb3 IO-2564 LEGACY Row Expander Drawers
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-12 15:04:42 -07:00
Allan Carr
492cee29e7 IO-2861 LEGACY Disable Editing of InHouse Invoice for Vendor and Invoice Number
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-12 12:36:31 -07:00
Dave Richer
b4cd016008 Merged in release/2024-05-31 (pull request #1455)
IO-2792 Resolve bill receive of credit memo and noline
2024-06-01 17:21:19 +00:00
Patrick Fic
7595cbf11d IO-2792 Resolve bill receive of credit memo and noline 2024-05-29 14:10:32 -07:00
Dave Richer
786f2f212c Merged in release/2024-05-24 (pull request #1448)
Release/2024 05 24
2024-05-24 20:44:00 +00:00
Patrick Fic
bdfbe52244 Remove API from master deployment. 2024-05-24 10:18:58 -07:00
Patrick Fic
0ca75298d8 Merge branch 'release/2024-05-17' into release/2024-05-24 2024-05-24 10:17:42 -07:00
Dave Richer
1ffad4a8b0 Progress Commit
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-05-14 12:37:33 -04:00
Dave Richer
cac7df66e1 Fix issue with global search 2024-05-14 12:19:58 -04:00
Patrick Fic
cb50ca7897 Merged in release/2024-04-26 (pull request #1439)
Release/2024 04 26
2024-05-03 21:35:26 +00:00
Allan Carr
a4f4f45251 Merged in feature/IO-2717-Paint-Material-from-Scale-for-CDK (pull request #1438)
IO-2717 CDK Use Scale Data for Paint Materials Cost
2024-05-02 05:53:35 +00:00
Allan Carr
acc91abc0c IO-2717 CDK Use Scale Data for Paint Materials Cost
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-05-01 13:26:24 -07:00
Patrick Fic
d6d6ced7a4 CC modifications. 2024-05-01 11:47:42 -07:00
Patrick Fic
17928741e3 Merge branch 'feature/IO-2766-intellipay-postback-refactor' into release/2024-04-26 2024-04-25 16:44:55 -07:00
Patrick Fic
a6b825ffdf Move intellipay to server side processing. 2024-04-25 16:43:49 -07:00
Allan Carr
f2914868e2 Merged in feature/IO-2761-Actual-Complete-in-Vehicle-Owner-Details (pull request #1434)
IO-2761 Actual Completion in Vehicle and Owners Job Details lists

Approved-by: Dave Richer
2024-04-23 17:40:10 +00:00
Allan Carr
561f0313f9 Merged in feature/IO-2762-Return-From-Bill-Reference (pull request #1435)
IO-2762 Return from Bill Reference in Parts Return Drawer

Approved-by: Dave Richer
2024-04-23 17:39:27 +00:00
Allan Carr
02a49efbea IO-2762 Return from Bill Reference in Parts Return Drawer
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-22 14:14:54 -07:00
Allan Carr
4fb9c37c0d IO-2761 Actual Completion in Vehicle and Owners Job Details lists
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-22 09:15:59 -07:00
38 changed files with 1455 additions and 1104 deletions

View File

@@ -134,10 +134,6 @@ jobs:
workflows: workflows:
deploy_and_build: deploy_and_build:
jobs: jobs:
- api-deploy:
filters:
branches:
only: master
- app-build: - app-build:
filters: filters:
branches: branches:

View File

@@ -1,4 +1,4 @@
<babeledit_project version="1.2" be_version="2.7.1"> <babeledit_project be_version="2.7.1" version="1.2">
<!-- <!--
BabelEdit project file BabelEdit project file
@@ -3540,6 +3540,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>nobilllines</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>noneselected</name> <name>noneselected</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -3624,6 +3645,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>returnfrombill</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>savewithdiscrepancy</name> <name>savewithdiscrepancy</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -13786,6 +13828,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>unavailable</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children> </children>
</folder_node> </folder_node>
<folder_node> <folder_node>
@@ -14476,6 +14539,27 @@
<folder_node> <folder_node>
<name>titles</name> <name>titles</name>
<children> <children>
<concept_node>
<name>joblifecycle</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>labhours</name> <name>labhours</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -18101,6 +18185,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>media</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>message</name> <name>message</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -19992,6 +20097,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>human_readable</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>percentage</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>relative_end</name> <name>relative_end</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -20055,6 +20202,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>status</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>status_count</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>value</name> <name>value</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -20081,6 +20270,27 @@
<folder_node> <folder_node>
<name>content</name> <name>content</name>
<children> <children>
<concept_node>
<name>calculated_based_on</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>current_status_accumulated_time</name> <name>current_status_accumulated_time</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -20123,6 +20333,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>joblifecycle</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>jobs_in_since</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>legend_title</name> <name>legend_title</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -20319,6 +20571,53 @@
</concept_node> </concept_node>
</children> </children>
</folder_node> </folder_node>
<folder_node>
<name>titles</name>
<children>
<concept_node>
<name>dashboard</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>top_durations</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children> </children>
</folder_node> </folder_node>
<folder_node> <folder_node>
@@ -20531,6 +20830,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>hint</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>payer</name> <name>payer</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -31013,6 +31333,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>labor_hrs</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>labor_rates_subtotal</name> <name>labor_rates_subtotal</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -39283,6 +39624,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>paymentupdate</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>stripe</name> <name>stripe</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -45374,6 +45736,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>job_lifecycle_date_detail</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>job_lifecycle_date_summary</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>jobs_completed_not_invoiced</name> <name>jobs_completed_not_invoiced</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -16,42 +16,28 @@ import TechPageContainer from "../pages/tech/tech.page.container";
import { setOnline } from "../redux/application/application.actions"; import { setOnline } from "../redux/application/application.actions";
import { selectOnline } from "../redux/application/application.selectors"; import { selectOnline } from "../redux/application/application.selectors";
import { checkUserSession } from "../redux/user/user.actions"; import { checkUserSession } from "../redux/user/user.actions";
import { import { selectBodyshop, selectCurrentUser } from "../redux/user/user.selectors";
selectBodyshop,
selectCurrentUser,
} from "../redux/user/user.selectors";
import PrivateRoute from "../utils/private-route"; import PrivateRoute from "../utils/private-route";
import "./App.styles.scss"; import "./App.styles.scss";
import handleBeta from "../utils/handleBeta";
const ResetPassword = lazy(() => const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component"));
import("../pages/reset-password/reset-password.component")
);
const ManagePage = lazy(() => import("../pages/manage/manage.page.container")); const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
const SignInPage = lazy(() => import("../pages/sign-in/sign-in.page")); const SignInPage = lazy(() => import("../pages/sign-in/sign-in.page"));
const CsiPage = lazy(() => import("../pages/csi/csi.container.page")); const CsiPage = lazy(() => import("../pages/csi/csi.container.page"));
const MobilePaymentContainer = lazy(() => const MobilePaymentContainer = lazy(() => import("../pages/mobile-payment/mobile-payment.container"));
import("../pages/mobile-payment/mobile-payment.container")
);
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
online: selectOnline, online: selectOnline,
bodyshop: selectBodyshop, bodyshop: selectBodyshop
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
checkUserSession: () => dispatch(checkUserSession()), checkUserSession: () => dispatch(checkUserSession()),
setOnline: (isOnline) => dispatch(setOnline(isOnline)), setOnline: (isOnline) => dispatch(setOnline(isOnline))
}); });
export function App({ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline }) {
bodyshop,
checkUserSession,
currentUser,
online,
setOnline,
}) {
const client = useClient(); const client = useClient();
useEffect(() => { useEffect(() => {
@@ -108,8 +94,6 @@ export function App({
/> />
); );
handleBeta();
return ( return (
<Switch> <Switch>
<Suspense fallback={<LoadingSpinner message="ImEX Online" />}> <Suspense fallback={<LoadingSpinner message="ImEX Online" />}>
@@ -129,32 +113,16 @@ export function App({
<Route exact path="/disclaimer" component={DisclaimerPage} /> <Route exact path="/disclaimer" component={DisclaimerPage} />
</ErrorBoundary> </ErrorBoundary>
<ErrorBoundary> <ErrorBoundary>
<Route <Route exact path="/mp/:paymentIs" component={MobilePaymentContainer} />
exact
path="/mp/:paymentIs"
component={MobilePaymentContainer}
/>
</ErrorBoundary> </ErrorBoundary>
<ErrorBoundary> <ErrorBoundary>
<PrivateRoute <PrivateRoute isAuthorized={currentUser.authorized} path="/manage" component={ManagePage} />
isAuthorized={currentUser.authorized}
path="/manage"
component={ManagePage}
/>
</ErrorBoundary> </ErrorBoundary>
<ErrorBoundary> <ErrorBoundary>
<PrivateRoute <PrivateRoute isAuthorized={currentUser.authorized} path="/tech" component={TechPageContainer} />
isAuthorized={currentUser.authorized}
path="/tech"
component={TechPageContainer}
/>
</ErrorBoundary> </ErrorBoundary>
<ErrorBoundary> <ErrorBoundary>
<PrivateRoute <PrivateRoute isAuthorized={currentUser.authorized} path="/edit" component={DocumentEditorContainer} />
isAuthorized={currentUser.authorized}
path="/edit"
component={DocumentEditorContainer}
/>
</ErrorBoundary> </ErrorBoundary>
</Suspense> </Suspense>
</Switch> </Switch>

View File

@@ -164,6 +164,7 @@ export function BillDetailEditcontainer({
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>; if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
const exported = data && data.bills_by_pk && data.bills_by_pk.exported; const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
const isinhouse = data && data.bills_by_pk && data.bills_by_pk.isinhouse;
return ( return (
<> <>
@@ -207,7 +208,7 @@ export function BillDetailEditcontainer({
initialValues={transformData(data)} initialValues={transformData(data)}
layout="vertical" layout="vertical"
> >
<BillFormContainer form={form} billEdit disabled={exported} /> <BillFormContainer form={form} billEdit disabled={exported} disableInHouse={isinhouse}/>
<Divider orientation="left">{t("general.labels.media")}</Divider> <Divider orientation="left">{t("general.labels.media")}</Divider>
{bodyshop.uselocalmediaserver ? ( {bodyshop.uselocalmediaserver ? (
<JobsDocumentsLocalGallery <JobsDocumentsLocalGallery

View File

@@ -47,6 +47,7 @@ export function BillFormComponent({
loadLines, loadLines,
billEdit, billEdit,
disableInvNumber, disableInvNumber,
disableInHouse,
job, job,
loadOutstandingReturns, loadOutstandingReturns,
loadInventory, loadInventory,
@@ -198,7 +199,7 @@ export function BillFormComponent({
]} ]}
> >
<VendorSearchSelect <VendorSearchSelect
disabled={disabled} disabled={disabled || disableInHouse}
options={vendorAutoCompleteOptions} options={vendorAutoCompleteOptions}
preferredMake={preferredMake} preferredMake={preferredMake}
onSelect={handleVendorSelect} onSelect={handleVendorSelect}
@@ -271,7 +272,7 @@ export function BillFormComponent({
}), }),
]} ]}
> >
<Input disabled={disabled || disableInvNumber} /> <Input disabled={disabled || disableInvNumber || disableInHouse} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("bills.fields.date")} label={t("bills.fields.date")}

View File

@@ -22,6 +22,7 @@ export function BillFormContainer({
billEdit, billEdit,
disabled, disabled,
disableInvNumber, disableInvNumber,
disableInHouse
}) { }) {
const { Simple_Inventory } = useTreatments( const { Simple_Inventory } = useTreatments(
["Simple_Inventory"], ["Simple_Inventory"],
@@ -57,6 +58,7 @@ export function BillFormContainer({
job={lineData ? lineData.jobs_by_pk : null} job={lineData ? lineData.jobs_by_pk : null}
responsibilityCenters={bodyshop.md_responsibility_centers || null} responsibilityCenters={bodyshop.md_responsibility_centers || null}
disableInvNumber={disableInvNumber} disableInvNumber={disableInvNumber}
disableInHouse={disableInHouse}
loadOutstandingReturns={loadOutstandingReturns} loadOutstandingReturns={loadOutstandingReturns}
loadInventory={loadInventory} loadInventory={loadInventory}
preferredMake={lineData ? lineData.jobs_by_pk.v_make_desc : null} preferredMake={lineData ? lineData.jobs_by_pk.v_make_desc : null}

View File

@@ -13,7 +13,6 @@ import {
notification, notification,
} from "antd"; } from "antd";
import axios from "axios"; import axios from "axios";
import moment from "moment";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -22,7 +21,6 @@ import {
INSERT_PAYMENT_RESPONSE, INSERT_PAYMENT_RESPONSE,
QUERY_RO_AND_OWNER_BY_JOB_PKS, QUERY_RO_AND_OWNER_BY_JOB_PKS,
} from "../../graphql/payment_response.queries"; } from "../../graphql/payment_response.queries";
import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries";
import { insertAuditTrail } from "../../redux/application/application.actions"; import { insertAuditTrail } from "../../redux/application/application.actions";
import { toggleModalVisible } from "../../redux/modals/modals.actions"; import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectCardPayment } from "../../redux/modals/modals.selectors"; import { selectCardPayment } from "../../redux/modals/modals.selectors";
@@ -48,12 +46,12 @@ const CardPaymentModalComponent = ({
toggleModalVisible, toggleModalVisible,
insertAuditTrail, insertAuditTrail,
}) => { }) => {
const { context } = cardPaymentModal; const { context, actions } = cardPaymentModal;
const [form] = Form.useForm(); const [form] = Form.useForm();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [insertPayment] = useMutation(INSERT_NEW_PAYMENT); // const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE); const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
const { t } = useTranslation(); const { t } = useTranslation();
@@ -65,7 +63,6 @@ const CardPaymentModalComponent = ({
} }
); );
console.log("🚀 ~ file: card-payment-modal.component..jsx:61 ~ data:", data);
//Initialize the intellipay window. //Initialize the intellipay window.
const SetIntellipayCallbackFunctions = () => { const SetIntellipayCallbackFunctions = () => {
console.log("*** Set IntelliPay callback functions."); console.log("*** Set IntelliPay callback functions.");
@@ -74,16 +71,20 @@ const CardPaymentModalComponent = ({
}); });
window.intellipay.runOnApproval(async function (response) { window.intellipay.runOnApproval(async function (response) {
console.warn("*** Running On Approval Script ***"); //2024-04-25: Nothing is going to happen here anymore. We'll completely rely on the callback.
form.setFieldValue("paymentResponse", response); //Add a slight delay to allow the refetch to properly get the data.
form.submit(); setTimeout(() => {
if (actions && actions.refetch && typeof actions.refetch === "function")
actions.refetch();
setLoading(false);
toggleModalVisible();
}, 750);
}); });
window.intellipay.runOnNonApproval(async function (response) { window.intellipay.runOnNonApproval(async function (response) {
// Mutate unsuccessful payment // Mutate unsuccessful payment
const { payments } = form.getFieldsValue(); const { payments } = form.getFieldsValue();
await insertPaymentResponse({ await insertPaymentResponse({
variables: { variables: {
paymentResponse: payments.map((payment) => ({ paymentResponse: payments.map((payment) => ({
@@ -108,50 +109,9 @@ const CardPaymentModalComponent = ({
}); });
}; };
const handleFinish = async (values) => {
try {
await insertPayment({
variables: {
paymentInput: values.payments.map((payment) => ({
amount: payment.amount,
transactionid: (values.paymentResponse.paymentid || "").toString(),
payer: t("payments.labels.customer"),
type: values.paymentResponse.cardbrand,
jobid: payment.jobid,
date: moment(Date.now()),
payment_responses: {
data: [
{
amount: payment.amount,
bodyshopid: bodyshop.id,
jobid: payment.jobid,
declinereason: values.paymentResponse.declinereason,
ext_paymentid: values.paymentResponse.paymentid.toString(),
successful: true,
response: values.paymentResponse,
},
],
},
})),
},
refetchQueries: ["GET_JOB_BY_PK"],
});
toggleModalVisible();
} catch (error) {
console.error(error);
notification.open({
type: "error",
message: t("payments.errors.inserting", { error: error.message }),
});
} finally {
setLoading(false);
}
};
const handleIntelliPayCharge = async () => { const handleIntelliPayCharge = async () => {
setLoading(true); setLoading(true);
//Validate //Validate
try { try {
await form.validateFields(); await form.validateFields();
@@ -164,6 +124,7 @@ const CardPaymentModalComponent = ({
const response = await axios.post("/intellipay/lightbox_credentials", { const response = await axios.post("/intellipay/lightbox_credentials", {
bodyshop, bodyshop,
refresh: !!window.intellipay, refresh: !!window.intellipay,
paymentSplitMeta: form.getFieldsValue(),
}); });
if (window.intellipay) { if (window.intellipay) {
@@ -192,7 +153,6 @@ const CardPaymentModalComponent = ({
<Card title="Card Payment"> <Card title="Card Payment">
<Spin spinning={loading}> <Spin spinning={loading}>
<Form <Form
onFinish={handleFinish}
form={form} form={form}
layout="vertical" layout="vertical"
initialValues={{ initialValues={{
@@ -273,23 +233,14 @@ const CardPaymentModalComponent = ({
} }
> >
{() => { {() => {
console.log("Updating the owner info section.");
//If all of the job ids have been fileld in, then query and update the IP field. //If all of the job ids have been fileld in, then query and update the IP field.
const { payments } = form.getFieldsValue(); const { payments } = form.getFieldsValue();
if ( if (
payments?.length > 0 && payments?.length > 0 &&
payments?.filter((p) => p?.jobid).length === payments?.length payments?.filter((p) => p?.jobid).length === payments?.length
) { ) {
console.log("**Calling refetch.");
refetch({ jobids: payments.map((p) => p.jobid) }); refetch({ jobids: payments.map((p) => p.jobid) });
} }
console.log(
"Acc info",
data,
payments && data && data.jobs.length > 0
? data.jobs.map((j) => j.ro_number).join(", ")
: null
);
return ( return (
<> <>
<Input <Input
@@ -344,6 +295,13 @@ const CardPaymentModalComponent = ({
value={totalAmountToCharge?.toFixed(2)} value={totalAmountToCharge?.toFixed(2)}
hidden hidden
/> />
<Input
className="ipayfield"
data-ipayname="comment"
//type="hidden"
value={btoa(JSON.stringify(payments))}
hidden
/>
<Button <Button
type="primary" type="primary"
// data-ipayname="submit" // data-ipayname="submit"
@@ -358,11 +316,6 @@ const CardPaymentModalComponent = ({
); );
}} }}
</Form.Item> </Form.Item>
{/* Lightbox payment response when it is completed */}
<Form.Item name="paymentResponse" hidden>
<Input type="hidden" />
</Form.Item>
</Form> </Form>
</Spin> </Spin>
</Card> </Card>

View File

@@ -37,6 +37,9 @@ const CourtesyCarStatusComponent = ({ value, onChange }, ref) => {
<Option value="courtesycars.status.leasereturn"> <Option value="courtesycars.status.leasereturn">
{t("courtesycars.status.leasereturn")} {t("courtesycars.status.leasereturn")}
</Option> </Option>
<Option value="courtesycars.status.unavailable">
{t("courtesycars.status.unavailable")}
</Option>
</Select> </Select>
); );
}; };

View File

@@ -77,6 +77,10 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
text: t("courtesycars.status.leasereturn"), text: t("courtesycars.status.leasereturn"),
value: "courtesycars.status.leasereturn", value: "courtesycars.status.leasereturn",
}, },
{
text: t("courtesycars.status.unavailable"),
value: "courtesycars.status.unavailable",
},
], ],
onFilter: (value, record) => record.status === value, onFilter: (value, record) => record.status === value,
sortOrder: sortOrder:

View File

@@ -3,16 +3,13 @@ import axios from "axios";
import _ from "lodash"; import _ from "lodash";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link, useHistory } from "react-router-dom"; import { Link } from "react-router-dom";
import PhoneNumberFormatter from "../../utils/PhoneFormatter"; import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import OwnerNameDisplay, { import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
OwnerNameDisplayFunction,
} from "../owner-name-display/owner-name-display.component";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component"; import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
export default function GlobalSearchOs() { export default function GlobalSearchOs() {
const { t } = useTranslation(); const { t } = useTranslation();
const history = useHistory();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [data, setData] = useState(false); const [data, setData] = useState(false);
@@ -21,7 +18,7 @@ export default function GlobalSearchOs() {
try { try {
setLoading(true); setLoading(true);
const searchData = await axios.post("/search", { const searchData = await axios.post("/search", {
search: v, search: v
}); });
const resultsByType = { const resultsByType = {
@@ -29,7 +26,7 @@ export default function GlobalSearchOs() {
jobs: [], jobs: [],
bills: [], bills: [],
owners: [], owners: [],
vehicles: [], vehicles: []
}; };
searchData.data.hits.hits.forEach((hit) => { searchData.data.hits.hits.forEach((hit) => {
@@ -50,16 +47,14 @@ export default function GlobalSearchOs() {
<span> <span>
<OwnerNameDisplay ownerObject={job} /> <OwnerNameDisplay ownerObject={job} />
</span> </span>
<span>{`${job.v_model_yr || ""} ${ <span>{`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`}</span>
job.v_make_desc || ""
} ${job.v_model_desc || ""}`}</span>
<span>{`${job.clm_no || ""}`}</span> <span>{`${job.clm_no || ""}`}</span>
<span>{`${job.plate_no || ""}`}</span> <span>{`${job.plate_no || ""}`}</span>
</Space> </Space>
</Link> </Link>
), )
}; };
}), })
}, },
{ {
label: renderTitle(t("menus.header.search.owners")), label: renderTitle(t("menus.header.search.owners")),
@@ -69,53 +64,39 @@ export default function GlobalSearchOs() {
value: OwnerNameDisplayFunction(owner), value: OwnerNameDisplayFunction(owner),
label: ( label: (
<Link to={`/manage/owners/${owner.id}`}> <Link to={`/manage/owners/${owner.id}`}>
<Space <Space size="small" split={<Divider type="vertical" />} wrap>
size="small"
split={<Divider type="vertical" />}
wrap
>
<span> <span>
<OwnerNameDisplay ownerObject={owner} /> <OwnerNameDisplay ownerObject={owner} />
</span> </span>
<PhoneNumberFormatter> <PhoneNumberFormatter>{owner.ownr_ph1}</PhoneNumberFormatter>
{owner.ownr_ph1} <PhoneNumberFormatter>{owner.ownr_ph2}</PhoneNumberFormatter>
</PhoneNumberFormatter>
<PhoneNumberFormatter>
{owner.ownr_ph2}
</PhoneNumberFormatter>
</Space> </Space>
</Link> </Link>
), )
}; };
}), })
}, },
{ {
label: renderTitle(t("menus.header.search.vehicles")), label: renderTitle(t("menus.header.search.vehicles")),
options: resultsByType.vehicles.map((vehicle) => { options: resultsByType.vehicles.map((vehicle) => {
return { return {
key: vehicle.id, key: vehicle.id,
value: `${vehicle.v_model_yr || ""} ${ value: `${vehicle.v_model_yr || ""} ${vehicle.v_make_desc || ""} ${vehicle.v_model_desc || ""}`,
vehicle.v_make_desc || ""
} ${vehicle.v_model_desc || ""}`,
label: ( label: (
<Link to={`/manage/vehicles/${vehicle.id}`}> <Link to={`/manage/vehicles/${vehicle.id}`}>
<Space size="small" split={<Divider type="vertical" />}> <Space size="small" split={<Divider type="vertical" />}>
<span> <span>
{`${vehicle.v_model_yr || ""} ${ {`${vehicle.v_model_yr || ""} ${vehicle.v_make_desc || ""} ${vehicle.v_model_desc || ""}`}
vehicle.v_make_desc || ""
} ${vehicle.v_model_desc || ""}`}
</span> </span>
<span>{vehicle.plate_no || ""}</span> <span>{vehicle.plate_no || ""}</span>
<span> <span>
<VehicleVinDisplay> <VehicleVinDisplay>{vehicle.v_vin || ""}</VehicleVinDisplay>
{vehicle.v_vin || ""}
</VehicleVinDisplay>
</span> </span>
</Space> </Space>
</Link> </Link>
), )
}; };
}), })
}, },
{ {
label: renderTitle(t("menus.header.search.payments")), label: renderTitle(t("menus.header.search.payments")),
@@ -133,9 +114,9 @@ export default function GlobalSearchOs() {
<span>{payment.transactionid || ""}</span> <span>{payment.transactionid || ""}</span>
</Space> </Space>
</Link> </Link>
), )
}; };
}), })
}, },
{ {
label: renderTitle(t("menus.header.search.bills")), label: renderTitle(t("menus.header.search.bills")),
@@ -151,10 +132,10 @@ export default function GlobalSearchOs() {
<span>{bill.date}</span> <span>{bill.date}</span>
</Space> </Space>
</Link> </Link>
), )
}; };
}), })
}, }
// { // {
// label: renderTitle(t("menus.header.search.phonebook")), // label: renderTitle(t("menus.header.search.phonebook")),
// options: resultsByType.search_phonebook.map((pb) => { // options: resultsByType.search_phonebook.map((pb) => {
@@ -196,15 +177,7 @@ export default function GlobalSearchOs() {
}; };
return ( return (
<AutoComplete <AutoComplete options={data} onSearch={handleSearch} defaultActiveFirstOption onClear={() => setData([])}>
options={data}
onSearch={handleSearch}
defaultActiveFirstOption
onSelect={(val, opt) => {
history.push(opt.label.props.to);
}}
onClear={() => setData([])}
>
<Input.Search <Input.Search
size="large" size="large"
placeholder={t("general.labels.globalsearch")} placeholder={t("general.labels.globalsearch")}

View File

@@ -3,28 +3,19 @@ import { AutoComplete, Divider, Input, Space } from "antd";
import _ from "lodash"; import _ from "lodash";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link, useHistory } from "react-router-dom"; import { Link } from "react-router-dom";
import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries"; import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries";
import PhoneNumberFormatter from "../../utils/PhoneFormatter"; import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import OwnerNameDisplay, { import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
OwnerNameDisplayFunction,
} from "../owner-name-display/owner-name-display.component";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component"; import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
export default function GlobalSearch() { export default function GlobalSearch() {
const { t } = useTranslation(); const { t } = useTranslation();
const history = useHistory(); const [callSearch, { loading, error, data }] = useLazyQuery(GLOBAL_SEARCH_QUERY);
const [callSearch, { loading, error, data }] =
useLazyQuery(GLOBAL_SEARCH_QUERY);
const executeSearch = (v) => { const executeSearch = (v) => {
if ( if (v && v.variables.search && v.variables.search !== "" && v.variables.search.length >= 3) callSearch(v);
v &&
v.variables.search &&
v.variables.search !== "" &&
v.variables.search.length >= 3
)
callSearch(v);
}; };
const debouncedExecuteSearch = _.debounce(executeSearch, 750); const debouncedExecuteSearch = _.debounce(executeSearch, 750);
@@ -53,15 +44,13 @@ export default function GlobalSearch() {
<span> <span>
<OwnerNameDisplay ownerObject={job} /> <OwnerNameDisplay ownerObject={job} />
</span> </span>
<span>{`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${ <span>{`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`}</span>
job.v_model_desc || ""
}`}</span>
<span>{`${job.clm_no || ""}`}</span> <span>{`${job.clm_no || ""}`}</span>
</Space> </Space>
</Link> </Link>
), )
}; };
}), })
}, },
{ {
label: renderTitle(t("menus.header.search.owners")), label: renderTitle(t("menus.header.search.owners")),
@@ -75,45 +64,35 @@ export default function GlobalSearch() {
<span> <span>
<OwnerNameDisplay ownerObject={owner} /> <OwnerNameDisplay ownerObject={owner} />
</span> </span>
<PhoneNumberFormatter> <PhoneNumberFormatter>{owner.ownr_ph1}</PhoneNumberFormatter>
{owner.ownr_ph1} <PhoneNumberFormatter>{owner.ownr_ph2}</PhoneNumberFormatter>
</PhoneNumberFormatter>
<PhoneNumberFormatter>
{owner.ownr_ph2}
</PhoneNumberFormatter>
</Space> </Space>
</Link> </Link>
), )
}; };
}), })
}, },
{ {
label: renderTitle(t("menus.header.search.vehicles")), label: renderTitle(t("menus.header.search.vehicles")),
options: data.search_vehicles.map((vehicle) => { options: data.search_vehicles.map((vehicle) => {
return { return {
key: vehicle.id, key: vehicle.id,
value: `${vehicle.v_model_yr || ""} ${ value: `${vehicle.v_model_yr || ""} ${vehicle.v_make_desc || ""} ${vehicle.v_model_desc || ""}`,
vehicle.v_make_desc || ""
} ${vehicle.v_model_desc || ""}`,
label: ( label: (
<Link to={`/manage/vehicles/${vehicle.id}`}> <Link to={`/manage/vehicles/${vehicle.id}`}>
<Space size="small" split={<Divider type="vertical" />}> <Space size="small" split={<Divider type="vertical" />}>
<span> <span>
{`${vehicle.v_model_yr || ""} ${ {`${vehicle.v_model_yr || ""} ${vehicle.v_make_desc || ""} ${vehicle.v_model_desc || ""}`}
vehicle.v_make_desc || ""
} ${vehicle.v_model_desc || ""}`}
</span> </span>
<span>{vehicle.plate_no || ""}</span> <span>{vehicle.plate_no || ""}</span>
<span> <span>
<VehicleVinDisplay> <VehicleVinDisplay>{vehicle.v_vin || ""}</VehicleVinDisplay>
{vehicle.v_vin || ""}
</VehicleVinDisplay>
</span> </span>
</Space> </Space>
</Link> </Link>
), )
}; };
}), })
}, },
{ {
label: renderTitle(t("menus.header.search.payments")), label: renderTitle(t("menus.header.search.payments")),
@@ -131,9 +110,9 @@ export default function GlobalSearch() {
<span>{payment.transactionid || ""}</span> <span>{payment.transactionid || ""}</span>
</Space> </Space>
</Link> </Link>
), )
}; };
}), })
}, },
{ {
label: renderTitle(t("menus.header.search.bills")), label: renderTitle(t("menus.header.search.bills")),
@@ -149,46 +128,35 @@ export default function GlobalSearch() {
<span>{bill.date}</span> <span>{bill.date}</span>
</Space> </Space>
</Link> </Link>
), )
}; };
}), })
}, },
{ {
label: renderTitle(t("menus.header.search.phonebook")), label: renderTitle(t("menus.header.search.phonebook")),
options: data.search_phonebook.map((pb) => { options: data.search_phonebook.map((pb) => {
return { return {
key: pb.id, key: pb.id,
value: `${pb.firstname || ""} ${pb.lastname || ""} ${ value: `${pb.firstname || ""} ${pb.lastname || ""} ${pb.company || ""}`,
pb.company || ""
}`,
label: ( label: (
<Link to={`/manage/phonebook?phonebookentry=${pb.id}`}> <Link to={`/manage/phonebook?phonebookentry=${pb.id}`}>
<Space size="small" split={<Divider type="vertical" />}> <Space size="small" split={<Divider type="vertical" />}>
<span>{`${pb.firstname || ""} ${pb.lastname || ""} ${ <span>{`${pb.firstname || ""} ${pb.lastname || ""} ${pb.company || ""}`}</span>
pb.company || ""
}`}</span>
<PhoneNumberFormatter>{pb.phone1}</PhoneNumberFormatter> <PhoneNumberFormatter>{pb.phone1}</PhoneNumberFormatter>
<span>{pb.email}</span> <span>{pb.email}</span>
</Space> </Space>
</Link> </Link>
), )
}; };
}), })
}, }
] ]
: []; : [];
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error" />;
return ( return (
<AutoComplete <AutoComplete options={options} onSearch={handleSearch} defaultActiveFirstOption>
options={options}
onSearch={handleSearch}
defaultActiveFirstOption
onSelect={(val, opt) => {
history.push(opt.label.props.to);
}}
>
<Input.Search <Input.Search
size="large" size="large"
placeholder={t("general.labels.globalsearch")} placeholder={t("general.labels.globalsearch")}

View File

@@ -11,9 +11,8 @@ import Icon, {
FileAddFilled, FileAddFilled,
FileAddOutlined, FileAddOutlined,
FileFilled, FileFilled,
//GlobalOutlined,
HomeFilled, HomeFilled,
ImportOutlined, InfoCircleOutlined, ImportOutlined,
LineChartOutlined, LineChartOutlined,
PaperClipOutlined, PaperClipOutlined,
PhoneOutlined, PhoneOutlined,
@@ -23,56 +22,39 @@ import Icon, {
TeamOutlined, TeamOutlined,
ToolFilled, ToolFilled,
UnorderedListOutlined, UnorderedListOutlined,
UserOutlined, UserOutlined
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react"; import { useTreatments } from "@splitsoftware/splitio-react";
import {Layout, Menu, Switch, Tooltip} from "antd"; import { Layout, Menu } from "antd";
import React, {useEffect, useState} from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { BsKanban } from "react-icons/bs"; import { BsKanban } from "react-icons/bs";
import { import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar } from "react-icons/fa";
FaCalendarAlt,
FaCarCrash,
FaCreditCard,
FaFileInvoiceDollar,
} from "react-icons/fa";
import { GiPayMoney, GiPlayerTime, GiSettingsKnobs } from "react-icons/gi"; import { GiPayMoney, GiPlayerTime, GiSettingsKnobs } from "react-icons/gi";
import { IoBusinessOutline } from "react-icons/io5"; import { IoBusinessOutline } from "react-icons/io5";
import { RiSurveyLine } from "react-icons/ri"; import { RiSurveyLine } from "react-icons/ri";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { import { selectRecentItems, selectSelectedHeader } from "../../redux/application/application.selectors";
selectRecentItems,
selectSelectedHeader,
} from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { signOutStart } from "../../redux/user/user.actions"; import { signOutStart } from "../../redux/user/user.actions";
import { import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import {handleBeta, setBeta, checkBeta} from "../../utils/handleBeta";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
recentItems: selectRecentItems, recentItems: selectRecentItems,
selectedHeader: selectSelectedHeader, selectedHeader: selectSelectedHeader,
bodyshop: selectBodyshop, bodyshop: selectBodyshop
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBillEnterContext: (context) => setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })),
dispatch(setModalContext({ context: context, modal: "billEnter" })), setTimeTicketContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicket" })),
setTimeTicketContext: (context) => setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })),
dispatch(setModalContext({ context: context, modal: "timeTicket" })), setReportCenterContext: (context) => dispatch(setModalContext({ context: context, modal: "reportCenter" })),
setPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "payment" })),
setReportCenterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "reportCenter" })),
signOutStart: () => dispatch(signOutStart()), signOutStart: () => dispatch(signOutStart()),
setCardPaymentContext: (context) => setCardPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "cardPayment" }))
dispatch(setModalContext({ context: context, modal: "cardPayment" })),
}); });
function Header({ function Header({
@@ -86,37 +68,26 @@ function Header({
setPaymentContext, setPaymentContext,
setReportCenterContext, setReportCenterContext,
recentItems, recentItems,
setCardPaymentContext, setCardPaymentContext
}) { }) {
const { Simple_Inventory } = useTreatments( const { Simple_Inventory } = useTreatments(["Simple_Inventory"], {}, bodyshop && bodyshop.imexshopid);
["Simple_Inventory"], const { DmsAp } = useTreatments(["DmsAp"], {}, bodyshop && bodyshop.imexshopid);
{}, const { ImEXPay } = useTreatments(["ImEXPay"], {}, bodyshop && bodyshop.imexshopid);
bodyshop && bodyshop.imexshopid
);
const { DmsAp } = useTreatments(
["DmsAp"],
{},
bodyshop && bodyshop.imexshopid
);
const { ImEXPay } = useTreatments(
["ImEXPay"],
{},
bodyshop && bodyshop.imexshopid
);
const [betaSwitch, setBetaSwitch] = useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
useEffect(() => { const deleteBetaCookie = () => {
const isBeta = checkBeta(); const cookieExists = document.cookie.split("; ").some((row) => row.startsWith(`betaSwitchImex=`));
setBetaSwitch(isBeta); if (cookieExists) {
}, []); const domain = window.location.hostname.split(".").slice(-2).join(".");
document.cookie = `betaSwitchImex=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.${domain}`;
console.log(`betaSwitchImex cookie deleted`);
} else {
console.log(`betaSwitchImex cookie does not exist`);
}
};
const betaSwitchChange = (checked) => { deleteBetaCookie();
setBeta(checked);
setBetaSwitch(checked);
handleBeta();
}
return ( return (
<Layout.Header> <Layout.Header>
@@ -134,11 +105,7 @@ function Header({
<Menu.Item key="schedule" icon={<Icon component={FaCalendarAlt} />}> <Menu.Item key="schedule" icon={<Icon component={FaCalendarAlt} />}>
<Link to="/manage/schedule">{t("menus.header.schedule")}</Link> <Link to="/manage/schedule">{t("menus.header.schedule")}</Link>
</Menu.Item> </Menu.Item>
<Menu.SubMenu <Menu.SubMenu key="jobssubmenu" icon={<Icon component={FaCarCrash} />} title={t("menus.header.jobs")}>
key="jobssubmenu"
icon={<Icon component={FaCarCrash} />}
title={t("menus.header.jobs")}
>
<Menu.Item key="activejobs" icon={<FileFilled />}> <Menu.Item key="activejobs" icon={<FileFilled />}>
<Link to="/manage/jobs">{t("menus.header.activejobs")}</Link> <Link to="/manage/jobs">{t("menus.header.activejobs")}</Link>
</Menu.Item> </Menu.Item>
@@ -149,9 +116,7 @@ function Header({
<Link to="/manage/partsqueue">{t("menus.header.parts-queue")}</Link> <Link to="/manage/partsqueue">{t("menus.header.parts-queue")}</Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="availablejobs" icon={<ImportOutlined />}> <Menu.Item key="availablejobs" icon={<ImportOutlined />}>
<Link to="/manage/available"> <Link to="/manage/available">{t("menus.header.availablejobs")}</Link>
{t("menus.header.availablejobs")}
</Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="newjob" icon={<FileAddOutlined />}> <Menu.Item key="newjob" icon={<FileAddOutlined />}>
<Link to="/manage/jobs/new">{t("menus.header.newjob")}</Link> <Link to="/manage/jobs/new">{t("menus.header.newjob")}</Link>
@@ -162,25 +127,17 @@ function Header({
</Menu.Item> </Menu.Item>
<Menu.Divider key="div2" /> <Menu.Divider key="div2" />
<Menu.Item key="productionlist" icon={<ScheduleOutlined />}> <Menu.Item key="productionlist" icon={<ScheduleOutlined />}>
<Link to="/manage/production/list"> <Link to="/manage/production/list">{t("menus.header.productionlist")}</Link>
{t("menus.header.productionlist")}
</Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="productionboard" icon={<Icon component={BsKanban} />}> <Menu.Item key="productionboard" icon={<Icon component={BsKanban} />}>
<Link to="/manage/production/board"> <Link to="/manage/production/board">{t("menus.header.productionboard")}</Link>
{t("menus.header.productionboard")}
</Link>
</Menu.Item> </Menu.Item>
<Menu.Divider key="div3" /> <Menu.Divider key="div3" />
<Menu.Item key="scoreboard" icon={<LineChartOutlined />}> <Menu.Item key="scoreboard" icon={<LineChartOutlined />}>
<Link to="/manage/scoreboard">{t("menus.header.scoreboard")}</Link> <Link to="/manage/scoreboard">{t("menus.header.scoreboard")}</Link>
</Menu.Item> </Menu.Item>
</Menu.SubMenu> </Menu.SubMenu>
<Menu.SubMenu <Menu.SubMenu key="customers" icon={<UserOutlined />} title={t("menus.header.customers")}>
key="customers"
icon={<UserOutlined />}
title={t("menus.header.customers")}
>
<Menu.Item key="owners" icon={<TeamOutlined />}> <Menu.Item key="owners" icon={<TeamOutlined />}>
<Link to="/manage/owners">{t("menus.header.owners")}</Link> <Link to="/manage/owners">{t("menus.header.owners")}</Link>
</Menu.Item> </Menu.Item>
@@ -188,36 +145,19 @@ function Header({
<Link to="/manage/vehicles">{t("menus.header.vehicles")}</Link> <Link to="/manage/vehicles">{t("menus.header.vehicles")}</Link>
</Menu.Item> </Menu.Item>
</Menu.SubMenu> </Menu.SubMenu>
<Menu.SubMenu <Menu.SubMenu key="ccs" icon={<CarFilled />} title={t("menus.header.courtesycars")}>
key="ccs"
icon={<CarFilled />}
title={t("menus.header.courtesycars")}
>
<Menu.Item key="courtesycarsall" icon={<CarFilled />}> <Menu.Item key="courtesycarsall" icon={<CarFilled />}>
<Link to="/manage/courtesycars"> <Link to="/manage/courtesycars">{t("menus.header.courtesycars-all")}</Link>
{t("menus.header.courtesycars-all")}
</Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="contracts" icon={<FileFilled />}> <Menu.Item key="contracts" icon={<FileFilled />}>
<Link to="/manage/courtesycars/contracts"> <Link to="/manage/courtesycars/contracts">{t("menus.header.courtesycars-contracts")}</Link>
{t("menus.header.courtesycars-contracts")}
</Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="newcontract" icon={<FileAddFilled />}> <Menu.Item key="newcontract" icon={<FileAddFilled />}>
<Link to="/manage/courtesycars/contracts/new"> <Link to="/manage/courtesycars/contracts/new">{t("menus.header.courtesycars-newcontract")}</Link>
{t("menus.header.courtesycars-newcontract")}
</Link>
</Menu.Item> </Menu.Item>
</Menu.SubMenu> </Menu.SubMenu>
<Menu.SubMenu <Menu.SubMenu key="accounting" icon={<DollarCircleFilled />} title={t("menus.header.accounting")}>
key="accounting" <Menu.Item key="bills" icon={<Icon component={FaFileInvoiceDollar} />}>
icon={<DollarCircleFilled />}
title={t("menus.header.accounting")}
>
<Menu.Item
key="bills"
icon={<Icon component={FaFileInvoiceDollar} />}
>
<Link to="/manage/bills">{t("menus.header.bills")}</Link> <Link to="/manage/bills">{t("menus.header.bills")}</Link>
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
@@ -226,7 +166,7 @@ function Header({
onClick={() => { onClick={() => {
setBillEnterContext({ setBillEnterContext({
actions: {}, actions: {},
context: {}, context: {}
}); });
}} }}
> >
@@ -235,13 +175,8 @@ function Header({
{Simple_Inventory.treatment === "on" && ( {Simple_Inventory.treatment === "on" && (
<> <>
<Menu.Divider key="div4" /> <Menu.Divider key="div4" />
<Menu.Item <Menu.Item key="inventory" icon={<Icon component={FaFileInvoiceDollar} />}>
key="inventory" <Link to="/manage/inventory">{t("menus.header.inventory")}</Link>
icon={<Icon component={FaFileInvoiceDollar} />}
>
<Link to="/manage/inventory">
{t("menus.header.inventory")}
</Link>
</Menu.Item> </Menu.Item>
</> </>
)} )}
@@ -254,7 +189,7 @@ function Header({
onClick={() => { onClick={() => {
setPaymentContext({ setPaymentContext({
actions: {}, actions: {},
context: null, context: null
}); });
}} }}
icon={<Icon component={FaCreditCard} />} icon={<Icon component={FaCreditCard} />}
@@ -267,7 +202,7 @@ function Header({
onClick={() => { onClick={() => {
setCardPaymentContext({ setCardPaymentContext({
actions: {}, actions: {},
context: {}, context: {}
}); });
}} }}
icon={<Icon component={FaCreditCard} />} icon={<Icon component={FaCreditCard} />}
@@ -277,9 +212,7 @@ function Header({
)} )}
<Menu.Divider key="div5" /> <Menu.Divider key="div5" />
<Menu.Item key="timetickets" icon={<FieldTimeOutlined />}> <Menu.Item key="timetickets" icon={<FieldTimeOutlined />}>
<Link to="/manage/timetickets"> <Link to="/manage/timetickets">{t("menus.header.timetickets")}</Link>
{t("menus.header.timetickets")}
</Link>
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
key="entertimetickets" key="entertimetickets"
@@ -290,49 +223,31 @@ function Header({
context: { context: {
created_by: currentUser.displayName created_by: currentUser.displayName
? currentUser.email.concat(" | ", currentUser.displayName) ? currentUser.email.concat(" | ", currentUser.displayName)
: currentUser.email, : currentUser.email
}, }
}); });
}} }}
> >
{t("menus.header.entertimeticket")} {t("menus.header.entertimeticket")}
</Menu.Item> </Menu.Item>
<Menu.Divider key="div6" /> <Menu.Divider key="div6" />
<Menu.SubMenu <Menu.SubMenu key="accountingexport" title={t("menus.header.export")} icon={<ExportOutlined />}>
key="accountingexport"
title={t("menus.header.export")}
icon={<ExportOutlined />}
>
<Menu.Item key="receivables"> <Menu.Item key="receivables">
<Link to="/manage/accounting/receivables"> <Link to="/manage/accounting/receivables">{t("menus.header.accounting-receivables")}</Link>
{t("menus.header.accounting-receivables")}
</Link>
</Menu.Item> </Menu.Item>
{(!( {(!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber)) ||
(bodyshop && bodyshop.cdk_dealerid) ||
(bodyshop && bodyshop.pbs_serialnumber)
) ||
DmsAp.treatment === "on") && ( DmsAp.treatment === "on") && (
<Menu.Item key="payables"> <Menu.Item key="payables">
<Link to="/manage/accounting/payables"> <Link to="/manage/accounting/payables">{t("menus.header.accounting-payables")}</Link>
{t("menus.header.accounting-payables")}
</Link>
</Menu.Item> </Menu.Item>
)} )}
{!( {!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber)) && (
(bodyshop && bodyshop.cdk_dealerid) ||
(bodyshop && bodyshop.pbs_serialnumber)
) && (
<Menu.Item key="payments"> <Menu.Item key="payments">
<Link to="/manage/accounting/payments"> <Link to="/manage/accounting/payments">{t("menus.header.accounting-payments")}</Link>
{t("menus.header.accounting-payments")}
</Link>
</Menu.Item> </Menu.Item>
)} )}
<Menu.Item key="export-logs"> <Menu.Item key="export-logs">
<Link to="/manage/accounting/exportlogs"> <Link to="/manage/accounting/exportlogs">{t("menus.header.export-logs")}</Link>
{t("menus.header.export-logs")}
</Link>
</Menu.Item> </Menu.Item>
</Menu.SubMenu> </Menu.SubMenu>
</Menu.SubMenu> </Menu.SubMenu>
@@ -340,19 +255,11 @@ function Header({
<Link to="/manage/phonebook">{t("menus.header.phonebook")}</Link> <Link to="/manage/phonebook">{t("menus.header.phonebook")}</Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="temporarydocs" icon={<PaperClipOutlined />}> <Menu.Item key="temporarydocs" icon={<PaperClipOutlined />}>
<Link to="/manage/temporarydocs"> <Link to="/manage/temporarydocs">{t("menus.header.temporarydocs")}</Link>
{t("menus.header.temporarydocs")}
</Link>
</Menu.Item> </Menu.Item>
<Menu.SubMenu <Menu.SubMenu key="shopsubmenu" title={t("menus.header.shop")} icon={<SettingOutlined />}>
key="shopsubmenu"
title={t("menus.header.shop")}
icon={<SettingOutlined />}
>
<Menu.Item key="shop" icon={<Icon component={GiSettingsKnobs} />}> <Menu.Item key="shop" icon={<Icon component={GiSettingsKnobs} />}>
<Link to="/manage/shop?tab=info"> <Link to="/manage/shop?tab=info">{t("menus.header.shop_config")}</Link>
{t("menus.header.shop_config")}
</Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="dashboard" icon={<DashboardFilled />}> <Menu.Item key="dashboard" icon={<DashboardFilled />}>
<Link to="/manage/dashboard">{t("menus.header.dashboard")}</Link> <Link to="/manage/dashboard">{t("menus.header.dashboard")}</Link>
@@ -363,32 +270,20 @@ function Header({
onClick={() => { onClick={() => {
setReportCenterContext({ setReportCenterContext({
actions: {}, actions: {},
context: {}, context: {}
}); });
}} }}
> >
{t("menus.header.reportcenter")} {t("menus.header.reportcenter")}
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item key="shop-vendors" icon={<Icon component={IoBusinessOutline} />}>
key="shop-vendors" <Link to="/manage/shop/vendors">{t("menus.header.shop_vendors")}</Link>
icon={<Icon component={IoBusinessOutline} />}
>
<Link to="/manage/shop/vendors">
{t("menus.header.shop_vendors")}
</Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="shop-csi" icon={<Icon component={RiSurveyLine} />}> <Menu.Item key="shop-csi" icon={<Icon component={RiSurveyLine} />}>
<Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link> <Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link>
</Menu.Item> </Menu.Item>
</Menu.SubMenu> </Menu.SubMenu>
<Menu.SubMenu <Menu.SubMenu key="user" title={currentUser.displayName || currentUser.email || t("general.labels.unknown")}>
key="user"
title={
currentUser.displayName ||
currentUser.email ||
t("general.labels.unknown")
}
>
<Menu.Item key="signout" danger onClick={() => signOutStart()}> <Menu.Item key="signout" danger onClick={() => signOutStart()}>
{t("user.actions.signout")} {t("user.actions.signout")}
</Menu.Item> </Menu.Item>
@@ -444,17 +339,6 @@ function Header({
</Menu.Item> </Menu.Item>
))} ))}
</Menu.SubMenu> </Menu.SubMenu>
<Menu.Item style={{marginLeft: 'auto'}} key="profile">
<Tooltip title="A more modern ImEX Online is ready for you to try! You can switch back at any time.">
<InfoCircleOutlined/>
<span style={{marginRight: 8}}>Try the new ImEX Online</span>
<Switch
checked={betaSwitch}
onChange={betaSwitchChange}
/>
</Tooltip>
</Menu.Item>
</Menu> </Menu>
</Layout.Header> </Layout.Header>
); );

View File

@@ -2,13 +2,25 @@ import { useQuery } from "@apollo/client";
import { Col, Row, Skeleton, Timeline, Typography } from "antd"; import { Col, Row, Skeleton, Timeline, Typography } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { GET_JOB_LINE_ORDERS } from "../../graphql/jobs.queries"; import { GET_JOB_LINE_ORDERS } from "../../graphql/jobs.queries";
import { selectTechnician } from "../../redux/tech/tech.selectors.js";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import BillDetailEditcontainer from "../bill-detail-edit/bill-detail-edit.container.jsx";
export default function JobLinesExpander({ jobline, jobid }) { const mapStateToProps = createStructuredSelector({
technician: selectTechnician,
});
const mapDispatchToProps = (dispatch) => ({});
export default connect(mapStateToProps, mapDispatchToProps)(JobLinesExpander);
export function JobLinesExpander({ jobline, jobid, technician }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { loading, error, data } = useQuery(GET_JOB_LINE_ORDERS, { const { loading, error, data } = useQuery(GET_JOB_LINE_ORDERS, {
fetchPolicy: "network-only", fetchPolicy: "network-only",
@@ -33,11 +45,15 @@ export default function JobLinesExpander({ jobline, jobid }) {
<Timeline.Item key={line.id}> <Timeline.Item key={line.id}>
<Row wrap> <Row wrap>
<Col span={4}> <Col span={4}>
<Link {!technician ? (
to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`} <Link
> to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`}
{line.parts_order.order_number} >
</Link> {line.parts_order.order_number}
</Link>
) : (
`${line.parts_order.order_number}`
)}
</Col> </Col>
<Col span={4}> <Col span={4}>
<DateFormatter>{line.parts_order.order_date}</DateFormatter> <DateFormatter>{line.parts_order.order_date}</DateFormatter>
@@ -63,17 +79,22 @@ export default function JobLinesExpander({ jobline, jobid }) {
</Col> </Col>
<Col md={24} lg={12}> <Col md={24} lg={12}>
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title> <Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
<BillDetailEditcontainer />
<Timeline> <Timeline>
{data.billlines.length > 0 ? ( {data.billlines.length > 0 ? (
data.billlines.map((line) => ( data.billlines.map((line) => (
<Timeline.Item key={line.id}> <Timeline.Item key={line.id}>
<Row wrap> <Row wrap>
<Col span={4}> <Col span={4}>
<Link {!technician ? (
to={`/manage/jobs/${jobid}?tab=partssublet&billid=${line.bill.id}`} <Link
> to={`/manage/jobs/${jobid}?tab=partssublet&billid=${line.bill.id}`}
{line.bill.invoice_number} >
</Link> {line.bill.invoice_number}
</Link>
) : (
`${line.bill.invoice_number}`
)}
</Col> </Col>
<Col span={4}> <Col span={4}>
<span> <span>
@@ -95,9 +116,7 @@ export default function JobLinesExpander({ jobline, jobid }) {
</Timeline.Item> </Timeline.Item>
)) ))
) : ( ) : (
<Timeline.Item> <Timeline.Item>{t("bills.labels.nobilllines")}</Timeline.Item>
{t("bills.labels.nobilllines")}
</Timeline.Item>
)} )}
</Timeline> </Timeline>
</Col> </Col>

View File

@@ -1,12 +1,12 @@
import { import {
DeleteFilled, DeleteFilled,
EditFilled,
FilterFilled, FilterFilled,
HomeOutlined,
MinusCircleTwoTone,
PlusCircleTwoTone,
SyncOutlined, SyncOutlined,
WarningFilled, WarningFilled,
EditFilled,
PlusCircleTwoTone,
MinusCircleTwoTone,
HomeOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { import {
@@ -20,6 +20,8 @@ import {
Tag, Tag,
} from "antd"; } from "antd";
import axios from "axios"; import axios from "axios";
import _ from "lodash";
import moment from "moment";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -28,23 +30,19 @@ import { DELETE_JOB_LINE_BY_PK } from "../../graphql/jobs-lines.queries";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectTechnician } from "../../redux/tech/tech.selectors"; import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { onlyUnique } from "../../utils/arrayHelper"; import { onlyUnique } from "../../utils/arrayHelper";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import JobCreateIOU from "../job-create-iou/job-create-iou.component";
import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component";
import JobLineLocationPopup from "../job-line-location-popup/job-line-location-popup.component"; import JobLineLocationPopup from "../job-line-location-popup/job-line-location-popup.component";
import JobLineNotePopup from "../job-line-note-popup/job-line-note-popup.component"; import JobLineNotePopup from "../job-line-note-popup/job-line-note-popup.component";
import JobLineStatusPopup from "../job-line-status-popup/job-line-status-popup.component"; import JobLineStatusPopup from "../job-line-status-popup/job-line-status-popup.component";
import JobLinesBillRefernece from "../job-lines-bill-reference/job-lines-bill-reference.component"; import JobLinesBillRefernece from "../job-lines-bill-reference/job-lines-bill-reference.component";
// import AllocationsAssignmentContainer from "../allocations-assignment/allocations-assignment.container"; import PartsOrderDrawer from "../parts-order-list-table/parts-order-list-table-drawer.component";
// import AllocationsBulkAssignmentContainer from "../allocations-bulk-assignment/allocations-bulk-assignment.container";
// import AllocationsEmployeeLabelContainer from "../allocations-employee-label/allocations-employee-label.container";
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container"; import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
import _ from "lodash";
import JobCreateIOU from "../job-create-iou/job-create-iou.component";
import JobLinesExpander from "./job-lines-expander.component"; import JobLinesExpander from "./job-lines-expander.component";
import { selectBodyshop } from "../../redux/user/user.selectors";
import moment from "moment";
import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -57,6 +55,8 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(setModalContext({ context: context, modal: "jobLineEdit" })), dispatch(setModalContext({ context: context, modal: "jobLineEdit" })),
setPartsOrderContext: (context) => setPartsOrderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsOrder" })), dispatch(setModalContext({ context: context, modal: "partsOrder" })),
setPartsReceiveContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsReceive" })),
setBillEnterContext: (context) => setBillEnterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "billEnter" })), dispatch(setModalContext({ context: context, modal: "billEnter" })),
}); });
@@ -66,6 +66,7 @@ export function JobLinesComponent({
jobRO, jobRO,
technician, technician,
setPartsOrderContext, setPartsOrderContext,
setPartsReceiveContext,
loading, loading,
refetch, refetch,
jobLines, jobLines,
@@ -74,6 +75,8 @@ export function JobLinesComponent({
setJobLineEditContext, setJobLineEditContext,
form, form,
setBillEnterContext, setBillEnterContext,
billsQuery,
handlePartsOrderOnRowClick,
}) { }) {
const [deleteJobLine] = useMutation(DELETE_JOB_LINE_BY_PK); const [deleteJobLine] = useMutation(DELETE_JOB_LINE_BY_PK);
@@ -341,7 +344,7 @@ export function JobLinesComponent({
key: "actions", key: "actions",
render: (text, record) => ( render: (text, record) => (
<Space> <Space>
{(record.manual_line || jobIsPrivate) && ( {(record.manual_line || jobIsPrivate) && !technician && (
<> <>
<Button <Button
disabled={jobRO} disabled={jobRO}
@@ -424,6 +427,14 @@ export function JobLinesComponent({
return ( return (
<div> <div>
<PartsOrderModalContainer /> <PartsOrderModalContainer />
{!technician && (
<PartsOrderDrawer
job={job}
billsQuery={billsQuery}
handleOnRowClick={handlePartsOrderOnRowClick}
setPartsReceiveContext={setPartsReceiveContext}
/>
)}
<PageHeader <PageHeader
title={t("jobs.labels.estimatelines")} title={t("jobs.labels.estimatelines")}
extra={ extra={

View File

@@ -1,6 +1,15 @@
import React, { useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
import JobLinesComponent from "./job-lines.component"; import JobLinesComponent from "./job-lines.component";
function JobLinesContainer({ job, joblines, refetch, form, ...rest }) { function JobLinesContainer({
job,
joblines,
billsQuery,
handleBillOnRowClick,
handlePartsOrderOnRowClick,
refetch,
form,
...rest
}) {
const [searchText, setSearchText] = useState(""); const [searchText, setSearchText] = useState("");
const jobLines = useMemo(() => { const jobLines = useMemo(() => {
@@ -37,6 +46,9 @@ function JobLinesContainer({ job, joblines, refetch, form, ...rest }) {
<JobLinesComponent <JobLinesComponent
refetch={refetch} refetch={refetch}
jobLines={jobLines} jobLines={jobLines}
billsQuery={billsQuery}
handleBillOnRowClick={handleBillOnRowClick}
handlePartsOrderOnRowClick={handlePartsOrderOnRowClick}
setSearchText={setSearchText} setSearchText={setSearchText}
job={job} job={job}
form={form} form={form}

View File

@@ -23,14 +23,18 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries"; import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries";
import { insertAuditTrail } from "../../redux/application/application.actions"; import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings"; import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser technician: selectTechnician,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation, type }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation, type })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export default connect( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
@@ -41,6 +45,7 @@ export function JobLineConvertToLabor({
jobline, jobline,
job, job,
insertAuditTrail, insertAuditTrail,
technician,
...otherBtnProps ...otherBtnProps
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -222,7 +227,7 @@ export function JobLineConvertToLabor({
return ( return (
<> <>
{children} {children}
{jobline.act_price !== 0 && ( {jobline.act_price !== 0 && !technician && (
<Popover <Popover
disabled={jobline.convertedtolbr} disabled={jobline.convertedtolbr}
content={overlay} content={overlay}

View File

@@ -304,7 +304,7 @@ export function JobsDetailHeaderActions({
disabled={!job.converted} disabled={!job.converted}
onClick={() => { onClick={() => {
setCardPaymentContext({ setCardPaymentContext({
actions: {}, actions: { refetch },
context: { jobid: job.id }, context: { jobid: job.id },
}); });
}} }}

View File

@@ -1,44 +1,12 @@
import { useQuery } from "@apollo/client";
import queryString from "query-string";
import React from "react"; import React from "react";
import { useHistory, useLocation } from "react-router-dom";
import { QUERY_BILLS_BY_JOBID } from "../../graphql/bills.queries";
import JobsDetailPliComponent from "./jobs-detail-pli.component"; import JobsDetailPliComponent from "./jobs-detail-pli.component";
export default function JobsDetailPliContainer({ job }) { export default function JobsDetailPliContainer({
const billsQuery = useQuery(QUERY_BILLS_BY_JOBID, { job,
variables: { jobid: job.id }, billsQuery,
fetchPolicy: "network-only", handleBillOnRowClick,
nextFetchPolicy: "network-only", handlePartsOrderOnRowClick,
}); }) {
const search = queryString.parse(useLocation().search);
const history = useHistory();
const handleBillOnRowClick = (record) => {
if (record) {
if (record.id) {
search.billid = record.id;
history.push({ search: queryString.stringify(search) });
}
} else {
delete search.billid;
history.push({ search: queryString.stringify(search) });
}
};
const handlePartsOrderOnRowClick = (record) => {
if (record) {
if (record.id) {
search.partsorderid = record.id;
history.push({ search: queryString.stringify(search) });
}
} else {
delete search.partsorderid;
history.push({ search: queryString.stringify(search) });
}
};
return ( return (
<JobsDetailPliComponent <JobsDetailPliComponent
job={job} job={job}

View File

@@ -11,6 +11,7 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { pageLimit } from "../../utils/config"; import { pageLimit } from "../../utils/config";
import { alphaSort, statusSort } from "../../utils/sorters";
import useLocalStorage from "../../utils/useLocalStorage"; import useLocalStorage from "../../utils/useLocalStorage";
import StartChatButton from "../chat-open-button/chat-open-button.component"; import StartChatButton from "../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
@@ -36,7 +37,10 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.ro_number"), title: t("jobs.fields.ro_number"),
dataIndex: "ro_number", dataIndex: "ro_number",
key: "ro_number", key: "ro_number",
sorter: true, //(a, b) => alphaSort(a.ro_number, b.ro_number), sorter: search?.search
? (a, b) =>
parseInt((a.ro_number || "0").replace(/\D/g, "")) - parseInt((b.ro_number || "0").replace(/\D/g, ""))
: true,
sortOrder: sortcolumn === "ro_number" && sortorder, sortOrder: sortcolumn === "ro_number" && sortorder,
render: (text, record) => ( render: (text, record) => (
<Link to={"/manage/jobs/" + record.id}> <Link to={"/manage/jobs/" + record.id}>
@@ -50,7 +54,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
key: "ownr_ln", key: "ownr_ln",
ellipsis: true, ellipsis: true,
//sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), //sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
//sortOrder: sortcolumn === "ownr_ln" && sortorder, //sortOrder: sortcolumn === "ownr_ln" && sortorder,
render: (text, record) => { render: (text, record) => {
return record.ownerid ? ( return record.ownerid ? (
@@ -68,7 +71,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.ownr_ph1"), title: t("jobs.fields.ownr_ph1"),
dataIndex: "ownr_ph1", dataIndex: "ownr_ph1",
key: "ownr_ph1", key: "ownr_ph1",
ellipsis: true, ellipsis: true,
render: (text, record) => ( render: (text, record) => (
<StartChatButton phone={record.ownr_ph1} jobid={record.id} /> <StartChatButton phone={record.ownr_ph1} jobid={record.id} />
@@ -78,7 +80,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.ownr_ph2"), title: t("jobs.fields.ownr_ph2"),
dataIndex: "ownr_ph2", dataIndex: "ownr_ph2",
key: "ownr_ph2", key: "ownr_ph2",
ellipsis: true, ellipsis: true,
render: (text, record) => ( render: (text, record) => (
<StartChatButton phone={record.ownr_ph2} jobid={record.id} /> <StartChatButton phone={record.ownr_ph2} jobid={record.id} />
@@ -88,9 +89,8 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.status"), title: t("jobs.fields.status"),
dataIndex: "status", dataIndex: "status",
key: "status", key: "status",
ellipsis: true, ellipsis: true,
sorter: true, // (a, b) => alphaSort(a.status, b.status), sorter: search?.search ? (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.active_statuses) : true,
sortOrder: sortcolumn === "status" && sortorder, sortOrder: sortcolumn === "status" && sortorder,
render: (text, record) => { render: (text, record) => {
return record.status || t("general.labels.na"); return record.status || t("general.labels.na");
@@ -106,7 +106,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.vehicle"), title: t("jobs.fields.vehicle"),
dataIndex: "vehicle", dataIndex: "vehicle",
key: "vehicle", key: "vehicle",
ellipsis: true, ellipsis: true,
render: (text, record) => { render: (text, record) => {
return record.vehicleid ? ( return record.vehicleid ? (
@@ -127,7 +126,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
dataIndex: "plate_no", dataIndex: "plate_no",
key: "plate_no", key: "plate_no",
ellipsis: true, ellipsis: true,
sorter: true, //(a, b) => alphaSort(a.plate_no, b.plate_no), sorter: search?.search ? (a, b) => alphaSort(a.plate_no, b.plate_no) : true,
sortOrder: sortcolumn === "plate_no" && sortorder, sortOrder: sortcolumn === "plate_no" && sortorder,
render: (text, record) => { render: (text, record) => {
return record.plate_no ? record.plate_no : ""; return record.plate_no ? record.plate_no : "";
@@ -138,7 +137,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
dataIndex: "clm_no", dataIndex: "clm_no",
key: "clm_no", key: "clm_no",
ellipsis: true, ellipsis: true,
sorter: true, //(a, b) => alphaSort(a.clm_no, b.clm_no), sorter: search?.search ? (a, b) => alphaSort(a.clm_no, b.clm_no) : true,
sortOrder: sortcolumn === "clm_no" && sortorder, sortOrder: sortcolumn === "clm_no" && sortorder,
render: (text, record) => render: (text, record) =>
`${record.clm_no || ""}${ `${record.clm_no || ""}${
@@ -156,7 +155,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
dataIndex: "clm_total", dataIndex: "clm_total",
key: "clm_total", key: "clm_total",
sorter: true, //(a, b) => a.clm_total - b.clm_total, sorter: search?.search ? (a, b) => a.clm_total - b.clm_total : true,
sortOrder: sortcolumn === "clm_total" && sortorder, sortOrder: sortcolumn === "clm_total" && sortorder,
render: (text, record) => { render: (text, record) => {
return record.clm_total ? ( return record.clm_total ? (
@@ -170,7 +169,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.owner_owing"), title: t("jobs.fields.owner_owing"),
dataIndex: "owner_owing", dataIndex: "owner_owing",
key: "owner_owing", key: "owner_owing",
render: (text, record) => ( render: (text, record) => (
<CurrencyFormatter>{record.owner_owing}</CurrencyFormatter> <CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
), ),

View File

@@ -6,7 +6,8 @@ import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort, statusSort } from "../../utils/sorters"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
import OwnerDetailUpdateJobsComponent from "../owner-detail-update-jobs/owner-detail-update-jobs.component"; import OwnerDetailUpdateJobsComponent from "../owner-detail-update-jobs/owner-detail-update-jobs.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -86,7 +87,18 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
})), })),
onFilter: (value, record) => value.includes(record.status), onFilter: (value, record) => value.includes(record.status),
}, },
{
title: t("jobs.fields.actual_completion"),
dataIndex: "actual_completion",
key: "actual_completion",
render: (text, record) => (
<DateTimeFormatter>{record.actual_completion}</DateTimeFormatter>
),
sorter: (a, b) => dateSort(a.actual_completion, b.actual_completion),
sortOrder:
state.sortedInfo.columnKey === "actual_completion" &&
state.sortedInfo.order,
},
{ {
title: t("jobs.fields.clm_total"), title: t("jobs.fields.clm_total"),
dataIndex: "clm_total", dataIndex: "clm_total",

View File

@@ -0,0 +1,416 @@
import { DeleteFilled } from "@ant-design/icons";
import { useLazyQuery, useMutation } from "@apollo/client";
import {
Button,
Drawer,
Grid,
PageHeader,
Popconfirm,
Space,
Table,
} from "antd";
import queryString from "query-string";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { QUERY_BILL_BY_PK } from "../../graphql/bills.queries";
import { DELETE_PARTS_ORDER } from "../../graphql/parts-orders.queries";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort } from "../../utils/sorters";
import DataLabel from "../data-label/data-label.component";
import FeatureWrapperComponent from "../feature-wrapper/feature-wrapper.component";
import PartsOrderBackorderEta from "../parts-order-backorder-eta/parts-order-backorder-eta.component";
import PartsOrderCmReceived from "../parts-order-cm-received/parts-order-cm-received.component";
import PartsOrderDeleteLine from "../parts-order-delete-line/parts-order-delete-line.component";
import PartsOrderLineBackorderButton from "../parts-order-line-backorder-button/parts-order-line-backorder-button.component";
import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container";
import PrintWrapper from "../print-wrapper/print-wrapper.component";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBillEnterContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "billEnter",
})
),
setPartsReceiveContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "partsReceive",
})
),
});
export function PartsOrderListTableDrawerComponent({
setBillEnterContext,
bodyshop,
jobRO,
job,
billsQuery,
handleOnRowClick,
setPartsReceiveContext,
}) {
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1])
.slice(-1)[0];
const bpoints = {
xs: "100%",
sm: "100%",
md: "100%",
lg: "75%",
xl: "75%",
xxl: "65%",
};
const drawerPercentage = selectedBreakpoint
? bpoints[selectedBreakpoint[0]]
: "100%";
const responsibilityCenters = bodyshop.md_responsibility_centers;
const Templates = TemplateList("partsorder", { job });
const { t } = useTranslation();
const [state, setState] = useState({
sortedInfo: {},
});
const [billData, setBillData] = useState(null);
const search = queryString.parse(useLocation().search);
const selectedpartsorder = search.partsorderid;
const [billQuery] = useLazyQuery(QUERY_BILL_BY_PK);
const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER);
const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : [];
const { refetch } = billsQuery;
const selectedPartsOrderRecord = parts_orders.find(
(r) => r.id === selectedpartsorder
);
useEffect(() => {
const fetchData = async () => {
if (selectedPartsOrderRecord?.returnfrombill) {
try {
const { data } = await billQuery({
variables: { billid: selectedPartsOrderRecord.returnfrombill },
});
setBillData(data);
} catch (error) {
console.error("Error fetching bill data:", error);
}
} else setBillData(null);
};
fetchData();
}, [selectedPartsOrderRecord, billQuery]);
const recordActions = (record) => (
<Space direction="horizontal" wrap>
<Button
disabled={
jobRO ||
record.return ||
record.vendor.id === bodyshop.inhousevendorid
}
onClick={() => {
logImEXEvent("parts_order_receive_bill");
setPartsReceiveContext({
actions: { refetch: refetch },
context: {
jobId: job.id,
job: job,
partsorderlines: record.parts_order_lines.map((pol) => ({
joblineid: pol.job_line_id,
id: pol.id,
line_desc: pol.line_desc,
quantity: pol.quantity,
act_price: pol.act_price,
oem_partno: pol.oem_partno,
})),
},
});
}}
>
{t("parts_orders.actions.receive")}
</Button>
<Popconfirm
title={t("parts_orders.labels.confirmdelete")}
disabled={jobRO}
onConfirm={async () => {
//Delete the parts return.!
await deletePartsOrder({
variables: { partsOrderId: record.id },
update(cache) {
cache.modify({
fields: {
parts_orders(existingPartsOrders, { readField }) {
return existingPartsOrders.filter(
(billref) => record.id !== readField("id", billref)
);
},
},
});
},
});
}}
>
<Button disabled={jobRO}>
<DeleteFilled />
</Button>
</Popconfirm>
<FeatureWrapperComponent featureName="bills" noauth={() => null}>
<Button
disabled={
(jobRO ? !record.return : jobRO) ||
record.vendor.id === bodyshop.inhousevendorid
}
onClick={() => {
logImEXEvent("parts_order_receive_bill");
setBillEnterContext({
actions: { refetch: refetch },
context: {
job: job,
bill: {
vendorid: record.vendor.id,
is_credit_memo: record.return,
billlines: record.parts_order_lines.map((pol) => ({
joblineid: pol.job_line_id || "noline",
line_desc: pol.line_desc,
quantity: pol.quantity,
actual_price: pol.act_price,
cost_center: pol.jobline?.part_type
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
? pol.jobline.part_type !== "PAE"
? pol.jobline.part_type
: null
: responsibilityCenters.defaults &&
(responsibilityCenters.defaults.costs[
pol.jobline.part_type
] ||
null)
: null,
})),
},
},
});
}}
>
{t("parts_orders.actions.receivebill")}
</Button>
</FeatureWrapperComponent>
<PrintWrapper
templateObject={{
name: record.return
? Templates.parts_return_slip.key
: Templates.parts_order.key,
variables: { id: record.id },
}}
messageObject={{
subject: record.return
? Templates.parts_return_slip.subject
: Templates.parts_order.subject,
to: record.vendor.email,
}}
id={job.id}
/>
</Space>
);
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const rowExpander = (record) => {
const columns = [
{
title: t("parts_orders.fields.line_desc"),
dataIndex: "line_desc",
key: "line_desc",
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
sortOrder:
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
},
{
title: t("parts_orders.fields.quantity"),
dataIndex: "quantity",
key: "quantity",
sorter: (a, b) => a.quantity - b.quantity,
sortOrder:
state.sortedInfo.columnKey === "quantity" && state.sortedInfo.order,
},
{
title: t("parts_orders.fields.act_price"),
dataIndex: "act_price",
key: "act_price",
sorter: (a, b) => a.act_price - b.act_price,
sortOrder:
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
render: (text, record) => (
<CurrencyFormatter>{record.act_price}</CurrencyFormatter>
),
},
...(selectedPartsOrderRecord && selectedPartsOrderRecord.return
? [
{
title: t("parts_orders.fields.cost"),
dataIndex: "cost",
key: "cost",
sorter: (a, b) => a.cost - b.cost,
sortOrder:
state.sortedInfo.columnKey === "cost" && state.sortedInfo.order,
render: (text, record) => (
<CurrencyFormatter>{record.cost}</CurrencyFormatter>
),
},
]
: []),
{
title: t("parts_orders.fields.part_type"),
dataIndex: "part_type",
key: "part_type",
render: (text, record) =>
record.part_type
? t(`joblines.fields.part_types.${record.part_type}`)
: null,
},
{
title: t("parts_orders.fields.oem_partno"),
dataIndex: "oem_partno",
key: "oem_partno",
sorter: (a, b) => alphaSort(a.oem_partno, b.oem_partno),
sortOrder:
state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order,
},
{
title: t("parts_orders.fields.line_remarks"),
dataIndex: "line_remarks",
key: "line_remarks",
},
{
title: t("parts_orders.fields.status"),
dataIndex: "status",
key: "status",
},
...(selectedPartsOrderRecord && selectedPartsOrderRecord.return
? [
{
title: t("parts_orders.fields.cm_received"),
dataIndex: "cm_received",
key: "cm_received",
render: (text, record) => (
<PartsOrderCmReceived
orderLineId={record.id}
checked={record.cm_received}
partsorderid={selectedPartsOrderRecord.id}
/>
),
},
]
: []),
{
title: t("parts_orders.fields.backordered_on"),
dataIndex: "backordered_on",
key: "backordered_on",
render: (text, record) => <DateFormatter>{text}</DateFormatter>,
},
{
title: t("parts_orders.fields.backordered_eta"),
dataIndex: "backordered_eta",
key: "backordered_eta",
render: (text, record) => (
<PartsOrderBackorderEta
backordered_eta={record.backordered_eta}
disabled={jobRO}
partsOrderStatus={record.status}
partsLineId={record.id}
jobLineId={record.job_line_id}
/>
),
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
render: (text, record) => (
<Space wrap>
<PartsOrderDeleteLine
disabled={jobRO}
partsOrderStatus={record.status}
partsLineId={record.id}
partsOrderId={selectedpartsorder}
jobLineId={record.job_line_id}
/>
<PartsOrderLineBackorderButton
disabled={jobRO}
partsOrderStatus={record.status}
partsLineId={record.id}
jobLineId={record.job_line_id}
/>
</Space>
),
},
];
return (
<div>
<PageHeader
title={
billData
? `${record.vendor.name} - ${record.order_number} - ${t(
"bills.labels.returnfrombill"
)}: ${billData.bills_by_pk.invoice_number}`
: `${record.vendor.name} - ${record.order_number}`
}
extra={recordActions(record)}
/>
<Table
scroll={{
x: true, //y: "50rem"
}}
columns={columns}
rowKey="id"
dataSource={record.parts_order_lines}
onChange={handleTableChange}
/>
<DataLabel label={t("parts_orders.fields.comments")}>
<div style={{ whiteSpace: "pre" }}>{record.comments}</div>
</DataLabel>
</div>
);
};
return (
<div>
<PartsReceiveModalContainer />
<Drawer
placement="right"
onClose={() => handleOnRowClick(null)}
open={selectedpartsorder}
closable
width={drawerPercentage}
>
{selectedPartsOrderRecord && rowExpander(selectedPartsOrderRecord)}
</Drawer>
</div>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(PartsOrderListTableDrawerComponent);

View File

@@ -1,39 +1,21 @@
import { DeleteFilled, EyeFilled, SyncOutlined } from "@ant-design/icons"; import { DeleteFilled, EyeFilled, SyncOutlined } from "@ant-design/icons";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { import { Button, Card, Checkbox, Input, Popconfirm, Space, Table } from "antd";
Button,
Card,
Checkbox,
Drawer,
Grid,
Input,
PageHeader,
Popconfirm,
Space,
Table,
} from "antd";
import queryString from "query-string";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { DELETE_PARTS_ORDER } from "../../graphql/parts-orders.queries"; import { DELETE_PARTS_ORDER } from "../../graphql/parts-orders.queries";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import DataLabel from "../data-label/data-label.component"; import { alphaSort } from "../../utils/sorters";
import PartsOrderBackorderEta from "../parts-order-backorder-eta/parts-order-backorder-eta.component";
import PartsOrderCmReceived from "../parts-order-cm-received/parts-order-cm-received.component";
import PartsOrderDeleteLine from "../parts-order-delete-line/parts-order-delete-line.component";
import PartsOrderLineBackorderButton from "../parts-order-line-backorder-button/parts-order-line-backorder-button.component";
import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container"; import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container";
import PrintWrapper from "../print-wrapper/print-wrapper.component"; import PrintWrapper from "../print-wrapper/print-wrapper.component";
import PartsOrderDrawer from "./parts-order-list-table-drawer.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
@@ -56,21 +38,6 @@ export function PartsOrderListTableComponent({
handleOnRowClick, handleOnRowClick,
setPartsReceiveContext, setPartsReceiveContext,
}) { }) {
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1])
.slice(-1)[0];
const bpoints = {
xs: "100%",
sm: "100%",
md: "100%",
lg: "75%",
xl: "75%",
xxl: "65%",
};
const drawerPercentage = selectedBreakpoint
? bpoints[selectedBreakpoint[0]]
: "100%";
const responsibilityCenters = bodyshop.md_responsibility_centers; const responsibilityCenters = bodyshop.md_responsibility_centers;
const Templates = TemplateList("partsorder", { job }); const Templates = TemplateList("partsorder", { job });
@@ -78,10 +45,8 @@ export function PartsOrderListTableComponent({
const [state, setState] = useState({ const [state, setState] = useState({
sortedInfo: {}, sortedInfo: {},
}); });
const search = queryString.parse(useLocation().search);
const selectedpartsorder = search.partsorderid;
const [searchText, setSearchText] = useState("");
const [searchText, setSearchText] = useState("");
const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER); const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER);
const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : []; const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : [];
@@ -90,7 +55,11 @@ export function PartsOrderListTableComponent({
const recordActions = (record, showView = false) => ( const recordActions = (record, showView = false) => (
<Space wrap> <Space wrap>
{showView && ( {showView && (
<Button onClick={() => handleOnRowClick(record)}> <Button
onClick={() => {
handleOnRowClick(record);
}}
>
<EyeFilled /> <EyeFilled />
</Button> </Button>
)} )}
@@ -166,7 +135,7 @@ export function PartsOrderListTableComponent({
is_credit_memo: record.return, is_credit_memo: record.return,
billlines: record.parts_order_lines.map((pol) => { billlines: record.parts_order_lines.map((pol) => {
return { return {
joblineid: pol.job_line_id, joblineid: pol.job_line_id || "noline",
line_desc: pol.line_desc, line_desc: pol.line_desc,
quantity: pol.quantity, quantity: pol.quantity,
@@ -277,164 +246,6 @@ export function PartsOrderListTableComponent({
setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
}; };
const selectedPartsOrderRecord = parts_orders.find(
(r) => r.id === selectedpartsorder
);
const rowExpander = (record) => {
const columns = [
{
title: t("parts_orders.fields.line_desc"),
dataIndex: "line_desc",
key: "line_desc",
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
sortOrder:
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
},
{
title: t("parts_orders.fields.quantity"),
dataIndex: "quantity",
key: "quantity",
sorter: (a, b) => a.quantity - b.quantity,
sortOrder:
state.sortedInfo.columnKey === "quantity" && state.sortedInfo.order,
},
{
title: t("parts_orders.fields.act_price"),
dataIndex: "act_price",
key: "act_price",
sorter: (a, b) => a.act_price - b.act_price,
sortOrder:
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
render: (text, record) => (
<CurrencyFormatter>{record.act_price}</CurrencyFormatter>
),
},
...(selectedPartsOrderRecord && selectedPartsOrderRecord.return
? [
{
title: t("parts_orders.fields.cost"),
dataIndex: "cost",
key: "cost",
sorter: (a, b) => a.cost - b.cost,
sortOrder:
state.sortedInfo.columnKey === "cost" && state.sortedInfo.order,
render: (text, record) => (
<CurrencyFormatter>{record.cost}</CurrencyFormatter>
),
},
]
: []),
{
title: t("parts_orders.fields.part_type"),
dataIndex: "part_type",
key: "part_type",
render: (text, record) =>
record.part_type
? t(`joblines.fields.part_types.${record.part_type}`)
: null,
},
{
title: t("parts_orders.fields.oem_partno"),
dataIndex: "oem_partno",
key: "oem_partno",
sorter: (a, b) => alphaSort(a.oem_partno, b.oem_partno),
sortOrder:
state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order,
},
{
title: t("parts_orders.fields.line_remarks"),
dataIndex: "line_remarks",
key: "line_remarks",
},
{
title: t("parts_orders.fields.status"),
dataIndex: "status",
key: "status",
},
...(selectedPartsOrderRecord && selectedPartsOrderRecord.return
? [
{
title: t("parts_orders.fields.cm_received"),
dataIndex: "cm_received",
key: "cm_received",
render: (text, record) => (
<PartsOrderCmReceived
orderLineId={record.id}
checked={record.cm_received}
partsorderid={selectedPartsOrderRecord.id}
/>
),
},
]
: []),
{
title: t("parts_orders.fields.backordered_on"),
dataIndex: "backordered_on",
key: "backordered_on",
render: (text, record) => <DateFormatter>{text}</DateFormatter>,
},
{
title: t("parts_orders.fields.backordered_eta"),
dataIndex: "backordered_eta",
key: "backordered_eta",
render: (text, record) => (
<PartsOrderBackorderEta
backordered_eta={record.backordered_eta}
disabled={jobRO}
partsOrderStatus={record.status}
partsLineId={record.id}
jobLineId={record.job_line_id}
/>
),
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
render: (text, record) => (
<Space wrap>
<PartsOrderDeleteLine
disabled={jobRO}
partsOrderStatus={record.status}
partsLineId={record.id}
partsOrderId={selectedpartsorder}
jobLineId={record.job_line_id}
/>
<PartsOrderLineBackorderButton
disabled={jobRO}
partsOrderStatus={record.status}
partsLineId={record.id}
jobLineId={record.job_line_id}
/>
</Space>
),
},
];
return (
<div>
<PageHeader
title={record && `${record.vendor.name} - ${record.order_number}`}
extra={recordActions(record)}
/>
<Table
scroll={{
x: true, //y: "50rem"
}}
columns={columns}
rowKey="id"
dataSource={record.parts_order_lines}
/>
<DataLabel label={t("parts_orders.fields.comments")}>
<div style={{ whiteSpace: "pre" }}>{record.comments}</div>
</DataLabel>
</div>
);
};
const filteredPartsOrders = parts_orders const filteredPartsOrders = parts_orders
? searchText === "" ? searchText === ""
? parts_orders ? parts_orders
@@ -470,15 +281,12 @@ export function PartsOrderListTableComponent({
} }
> >
<PartsReceiveModalContainer /> <PartsReceiveModalContainer />
<Drawer <PartsOrderDrawer
placement="right" job={job}
onClose={() => handleOnRowClick(null)} billsQuery={billsQuery}
visible={selectedpartsorder} handleOnRowClick={handleOnRowClick}
closable setPartsReceiveContext={setPartsReceiveContext}
width={drawerPercentage} />
>
{selectedPartsOrderRecord && rowExpander(selectedPartsOrderRecord)}
</Drawer>
<Table <Table
loading={billsQuery.loading} loading={billsQuery.loading}
scroll={{ scroll={{

View File

@@ -139,8 +139,8 @@ const PaymentExpandedRowComponent = ({ record, bodyshop }) => {
contentStyle={{ fontWeight: "600" }} contentStyle={{ fontWeight: "600" }}
column={4} column={4}
> >
<Descriptions.Item label={t("job_payments.titles.payer")}> <Descriptions.Item label={t("job_payments.titles.hint")}>
{record.payer} {payment_response?.response?.methodhint}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.payername")}> <Descriptions.Item label={t("job_payments.titles.payername")}>
{payment_response?.response?.nameOnCard ?? ""} {payment_response?.response?.nameOnCard ?? ""}
@@ -155,7 +155,7 @@ const PaymentExpandedRowComponent = ({ record, bodyshop }) => {
{record.transactionid} {record.transactionid}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.paymentid")}> <Descriptions.Item label={t("job_payments.titles.paymentid")}>
{payment_response?.response?.paymentreferenceid ?? ""} {payment_response?.ext_paymentid ?? ""}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.paymenttype")}> <Descriptions.Item label={t("job_payments.titles.paymenttype")}>
{record.type} {record.type}

View File

@@ -9,9 +9,7 @@ import { onlyUnique } from "../../utils/arrayHelper";
import { alphaSort, dateSort, statusSort } from "../../utils/sorters"; import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
import JobAltTransportChange from "../job-at-change/job-at-change.component"; import JobAltTransportChange from "../job-at-change/job-at-change.component";
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component"; import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
import OwnerNameDisplay, { import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
OwnerNameDisplayFunction,
} from "../owner-name-display/owner-name-display.component";
import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component"; import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component";
import ProductionListColumnAlert from "./production-list-columns.alert.component"; import ProductionListColumnAlert from "./production-list-columns.alert.component";
import ProductionListColumnBodyPriority from "./production-list-columns.bodypriority.component"; import ProductionListColumnBodyPriority from "./production-list-columns.bodypriority.component";
@@ -34,11 +32,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
dataIndex: "viewdetail", dataIndex: "viewdetail",
key: "viewdetail", key: "viewdetail",
ellipsis: true, ellipsis: true,
render: (text, record) => ( render: (text, record) => <Link to={{ search: `?selected=${record.id}` }}>{i18n.t("general.labels.view")}</Link>
<Link to={{ search: `?selected=${record.id}` }}>
{i18n.t("general.labels.view")}
</Link>
),
}, },
{ {
title: i18n.t("jobs.fields.ro_number"), title: i18n.t("jobs.fields.ro_number"),
@@ -46,23 +40,18 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "ro_number", key: "ro_number",
ellipsis: true, ellipsis: true,
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder: sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
render: (text, record) => render: (text, record) =>
technician ? ( technician ? (
<Link to={`/tech/joblookup?selected=${record.id}`}> <Link to={`/tech/joblookup?selected=${record.id}`}>
{record.ro_number} {record.ro_number}
{record.suspended && ( {record.suspended && <PauseCircleOutlined style={{ color: "orangered" }} />}
<PauseCircleOutlined style={{ color: "orangered" }} />
)}
</Link> </Link>
) : ( ) : (
<Link to={`/manage/jobs/${record.id}`}> <Link to={`/manage/jobs/${record.id}`}>
<Space> <Space>
{record.ro_number} {record.ro_number}
{record.suspended && ( {record.suspended && <PauseCircleOutlined style={{ color: "orangered" }} />}
<PauseCircleOutlined style={{ color: "orangered" }} />
)}
{record.iouparent && ( {record.iouparent && (
<Tooltip title={i18n.t("jobs.labels.iou")}> <Tooltip title={i18n.t("jobs.labels.iou")}>
<BranchesOutlined style={{ color: "orangered" }} /> <BranchesOutlined style={{ color: "orangered" }} />
@@ -70,7 +59,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
)} )}
</Space> </Space>
</Link> </Link>
), )
}, },
{ {
title: i18n.t("jobs.fields.owner"), title: i18n.t("jobs.fields.owner"),
@@ -85,10 +74,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
<OwnerNameDisplay ownerObject={record} /> <OwnerNameDisplay ownerObject={record} />
</Link> </Link>
), ),
sorter: (a, b) => sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)), sortOrder: state.sortedInfo.columnKey === "ownr" && state.sortedInfo.order
sortOrder:
state.sortedInfo.columnKey === "ownr" && state.sortedInfo.order,
}, },
{ {
title: i18n.t("jobs.fields.vehicle"), title: i18n.t("jobs.fields.vehicle"),
@@ -97,13 +84,10 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
ellipsis: true, ellipsis: true,
sorter: (a, b) => sorter: (a, b) =>
alphaSort( alphaSort(
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${ `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${a.v_model_desc || ""}`,
a.v_model_desc || ""
}`,
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}` `${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
), ),
sortOrder: sortOrder: state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
render: (text, record) => render: (text, record) =>
technician ? ( technician ? (
<>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ <>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
@@ -115,7 +99,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
} ${record.v_make_desc || ""} ${record.v_model_desc || ""} ${ } ${record.v_make_desc || ""} ${record.v_model_desc || ""} ${
record.v_color || "" record.v_color || ""
} ${record.plate_no || ""}`}</Link> } ${record.plate_no || ""}`}</Link>
), )
}, },
{ {
title: i18n.t("jobs.fields.actual_in"), title: i18n.t("jobs.fields.actual_in"),
@@ -123,11 +107,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "actual_in", key: "actual_in",
ellipsis: true, ellipsis: true,
sorter: (a, b) => dateSort(a.actual_in, b.actual_in), sorter: (a, b) => dateSort(a.actual_in, b.actual_in),
sortOrder: sortOrder: state.sortedInfo.columnKey === "actual_in" && state.sortedInfo.order,
state.sortedInfo.columnKey === "actual_in" && state.sortedInfo.order, render: (text, record) => <ProductionListDate record={record} field="actual_in" time />
render: (text, record) => (
<ProductionListDate record={record} field="actual_in" time />
),
}, },
{ {
title: i18n.t("jobs.fields.actual_in") + " (HH:MM)", title: i18n.t("jobs.fields.actual_in") + " (HH:MM)",
@@ -135,28 +116,16 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "actual_in_time", key: "actual_in_time",
ellipsis: true, ellipsis: true,
render: (text, record) => ( render: (text, record) => <TimeFormatter>{record.actual_in}</TimeFormatter>
<TimeFormatter>{record.actual_in}</TimeFormatter>
),
}, },
{ {
title: i18n.t("jobs.fields.scheduled_completion"), title: i18n.t("jobs.fields.scheduled_completion"),
dataIndex: "scheduled_completion", dataIndex: "scheduled_completion",
key: "scheduled_completion", key: "scheduled_completion",
ellipsis: true, ellipsis: true,
sorter: (a, b) => sorter: (a, b) => dateSort(a.scheduled_completion, b.scheduled_completion),
dateSort(a.scheduled_completion, b.scheduled_completion), sortOrder: state.sortedInfo.columnKey === "scheduled_completion" && state.sortedInfo.order,
sortOrder: render: (text, record) => <ProductionListDate record={record} field="scheduled_completion" pastIndicator time />
state.sortedInfo.columnKey === "scheduled_completion" &&
state.sortedInfo.order,
render: (text, record) => (
<ProductionListDate
record={record}
field="scheduled_completion"
pastIndicator
time
/>
),
}, },
{ {
title: i18n.t("jobs.fields.scheduled_completion") + " (HH:MM)", title: i18n.t("jobs.fields.scheduled_completion") + " (HH:MM)",
@@ -164,9 +133,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "scheduled_completion_time", key: "scheduled_completion_time",
ellipsis: true, ellipsis: true,
render: (text, record) => ( render: (text, record) => <TimeFormatter>{record.scheduled_completion}</TimeFormatter>
<TimeFormatter>{record.scheduled_completion}</TimeFormatter>
),
}, },
{ {
title: i18n.t("jobs.fields.date_last_contacted"), title: i18n.t("jobs.fields.date_last_contacted"),
@@ -174,10 +141,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "date_last_contacted", key: "date_last_contacted",
ellipsis: true, ellipsis: true,
sorter: (a, b) => dateSort(a.date_last_contacted, b.date_last_contacted), sorter: (a, b) => dateSort(a.date_last_contacted, b.date_last_contacted),
sortOrder: sortOrder: state.sortedInfo.columnKey === "date_last_contacted" && state.sortedInfo.order,
state.sortedInfo.columnKey === "date_last_contacted" && render: (text, record) => <ProductionListLastContacted record={record} />
state.sortedInfo.order,
render: (text, record) => <ProductionListLastContacted record={record} />,
}, },
{ {
title: i18n.t("jobs.fields.date_next_contact"), title: i18n.t("jobs.fields.date_next_contact"),
@@ -185,17 +150,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "date_next_contact", key: "date_next_contact",
ellipsis: true, ellipsis: true,
sorter: (a, b) => dateSort(a.date_next_contact, b.date_next_contact), sorter: (a, b) => dateSort(a.date_next_contact, b.date_next_contact),
sortOrder: sortOrder: state.sortedInfo.columnKey === "date_next_contact" && state.sortedInfo.order,
state.sortedInfo.columnKey === "date_next_contact" && render: (text, record) => <ProductionListDate record={record} field="date_next_contact" pastIndicator time />
state.sortedInfo.order,
render: (text, record) => (
<ProductionListDate
record={record}
field="date_next_contact"
pastIndicator
time
/>
),
}, },
{ {
title: i18n.t("jobs.fields.scheduled_delivery"), title: i18n.t("jobs.fields.scheduled_delivery"),
@@ -203,17 +159,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "scheduled_delivery", key: "scheduled_delivery",
ellipsis: true, ellipsis: true,
sorter: (a, b) => dateSort(a.scheduled_delivery, b.scheduled_delivery), sorter: (a, b) => dateSort(a.scheduled_delivery, b.scheduled_delivery),
sortOrder: sortOrder: state.sortedInfo.columnKey === "scheduled_delivery" && state.sortedInfo.order,
state.sortedInfo.columnKey === "scheduled_delivery" && render: (text, record) => <ProductionListDate record={record} field="scheduled_delivery" pastIndicator time />
state.sortedInfo.order,
render: (text, record) => (
<ProductionListDate
record={record}
field="scheduled_delivery"
pastIndicator
time
/>
),
}, },
{ {
title: i18n.t("jobs.fields.scheduled_delivery") + " (HH:MM)", title: i18n.t("jobs.fields.scheduled_delivery") + " (HH:MM)",
@@ -221,9 +168,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "scheduled_delivery_time", key: "scheduled_delivery_time",
ellipsis: true, ellipsis: true,
render: (text, record) => ( render: (text, record) => <TimeFormatter>{record.scheduled_delivery}</TimeFormatter>
<TimeFormatter>{record.scheduled_delivery}</TimeFormatter>
),
}, },
{ {
title: i18n.t("jobs.fields.ins_co_nm"), title: i18n.t("jobs.fields.ins_co_nm"),
@@ -231,8 +176,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "ins_co_nm", key: "ins_co_nm",
ellipsis: true, ellipsis: true,
sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm), sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm),
sortOrder: sortOrder: state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order
state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order,
}, },
{ {
title: i18n.t("jobs.fields.clm_no"), title: i18n.t("jobs.fields.clm_no"),
@@ -240,8 +184,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "clm_no", key: "clm_no",
ellipsis: true, ellipsis: true,
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder: sortOrder: state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
}, },
{ {
title: i18n.t("jobs.fields.clm_total"), title: i18n.t("jobs.fields.clm_total"),
@@ -249,11 +192,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "clm_total", key: "clm_total",
ellipsis: true, ellipsis: true,
sorter: (a, b) => a.clm_total - b.clm_total, sorter: (a, b) => a.clm_total - b.clm_total,
sortOrder: sortOrder: state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order, render: (text, record) => <CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
render: (text, record) => (
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
),
}, },
{ {
title: i18n.t("jobs.fields.owner_owing"), title: i18n.t("jobs.fields.owner_owing"),
@@ -261,49 +201,36 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "owner_owing", key: "owner_owing",
ellipsis: true, ellipsis: true,
sorter: (a, b) => a.owner_owing - b.owner_owing, sorter: (a, b) => a.owner_owing - b.owner_owing,
sortOrder: sortOrder: state.sortedInfo.columnKey === "owner_owing" && state.sortedInfo.order,
state.sortedInfo.columnKey === "owner_owing" && state.sortedInfo.order, render: (text, record) => <CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
render: (text, record) => (
<CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
),
}, },
{ {
title: i18n.t("jobs.fields.ownr_ph1"), title: i18n.t("jobs.fields.ownr_ph1"),
dataIndex: "ownr_ph1", dataIndex: "ownr_ph1",
key: "ownr_ph1", key: "ownr_ph1",
ellipsis: true, ellipsis: true,
render: (text, record) => ( render: (text, record) => <PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>
<PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>
),
}, },
{ {
title: i18n.t("jobs.fields.ownr_ph2"), title: i18n.t("jobs.fields.ownr_ph2"),
dataIndex: "ownr_ph2", dataIndex: "ownr_ph2",
key: "ownr_ph2", key: "ownr_ph2",
ellipsis: true, ellipsis: true,
render: (text, record) => ( render: (text, record) => <PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>
<PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>
),
}, },
{ {
title: i18n.t("jobs.fields.specialcoveragepolicy"), title: i18n.t("jobs.fields.specialcoveragepolicy"),
dataIndex: "special_coverage_policy", dataIndex: "special_coverage_policy",
key: "special_coverage_policy", key: "special_coverage_policy",
ellipsis: true, ellipsis: true,
sorter: (a, b) => sorter: (a, b) => Number(a.special_coverage_policy) - Number(b.special_coverage_policy),
Number(a.special_coverage_policy) - Number(b.special_coverage_policy), sortOrder: state.sortedInfo.columnKey === "special_coverage_policy" && state.sortedInfo.order,
sortOrder:
state.sortedInfo.columnKey === "special_coverage_policy" &&
state.sortedInfo.order,
filters: [ filters: [
{ text: "True", value: true }, { text: "True", value: true },
{ text: "False", value: false }, { text: "False", value: false }
], ],
onFilter: (value, record) => onFilter: (value, record) => value === record.special_coverage_policy,
value.includes(record.special_coverage_policy), render: (text, record) => <Checkbox checked={record.special_coverage_policy} />
render: (text, record) => (
<Checkbox checked={record.special_coverage_policy} />
),
}, },
{ {
@@ -312,15 +239,13 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "alt_transport", key: "alt_transport",
ellipsis: true, ellipsis: true,
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport), sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
sortOrder: sortOrder: state.sortedInfo.columnKey === "alt_transport" && state.sortedInfo.order,
state.sortedInfo.columnKey === "alt_transport" &&
state.sortedInfo.order,
filters: filters:
(bodyshop && (bodyshop &&
bodyshop.appt_alt_transport.map((s) => { bodyshop.appt_alt_transport.map((s) => {
return { return {
text: s, text: s,
value: [s], value: [s]
}; };
})) || })) ||
[], [],
@@ -330,7 +255,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
{record.alt_transport} {record.alt_transport}
<JobAltTransportChange job={record} /> <JobAltTransportChange job={record} />
</div> </div>
), )
}, },
{ {
title: i18n.t("jobs.fields.status"), title: i18n.t("jobs.fields.status"),
@@ -338,9 +263,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "status", key: "status",
ellipsis: true, ellipsis: true,
sorter: (a, b) => statusSort(a.status, b.status, activeStatuses), sorter: (a, b) => statusSort(a.status, b.status, activeStatuses),
sortOrder: sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
state.sortedInfo.columnKey === "status" && state.sortedInfo.order, render: (text, record) => <ProductionListColumnStatus record={record} />
render: (text, record) => <ProductionListColumnStatus record={record} />,
}, },
{ {
title: i18n.t("jobs.fields.category"), title: i18n.t("jobs.fields.category"),
@@ -353,37 +277,30 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
bodyshop.md_categories.map((s) => { bodyshop.md_categories.map((s) => {
return { return {
text: s, text: s,
value: [s], value: [s]
}; };
})) || })) ||
[], [],
onFilter: (value, record) => value.includes(record.category), onFilter: (value, record) => value.includes(record.category),
sorter: (a, b) => alphaSort(a.category, b.category), sorter: (a, b) => alphaSort(a.category, b.category),
sortOrder: sortOrder: state.sortedInfo.columnKey === "category" && state.sortedInfo.order,
state.sortedInfo.columnKey === "category" && state.sortedInfo.order, render: (text, record) => <ProductionListColumnCategory record={record} />
render: (text, record) => (
<ProductionListColumnCategory record={record} />
),
}, },
{ {
title: i18n.t("production.labels.bodyhours"), title: i18n.t("production.labels.bodyhours"),
dataIndex: "labhrs", dataIndex: "labhrs",
key: "labhrs", key: "labhrs",
sorter: (a, b) => sorter: (a, b) => a.labhrs.aggregate.sum.mod_lb_hrs - b.labhrs.aggregate.sum.mod_lb_hrs,
a.labhrs.aggregate.sum.mod_lb_hrs - b.labhrs.aggregate.sum.mod_lb_hrs, sortOrder: state.sortedInfo.columnKey === "labhrs" && state.sortedInfo.order,
sortOrder: render: (text, record) => record.labhrs.aggregate.sum.mod_lb_hrs
state.sortedInfo.columnKey === "labhrs" && state.sortedInfo.order,
render: (text, record) => record.labhrs.aggregate.sum.mod_lb_hrs,
}, },
{ {
title: i18n.t("production.labels.refinishhours"), title: i18n.t("production.labels.refinishhours"),
dataIndex: "larhrs", dataIndex: "larhrs",
key: "larhrs", key: "larhrs",
sorter: (a, b) => sorter: (a, b) => a.larhrs.aggregate.sum.mod_lb_hrs - b.larhrs.aggregate.sum.mod_lb_hrs,
a.larhrs.aggregate.sum.mod_lb_hrs - b.larhrs.aggregate.sum.mod_lb_hrs, sortOrder: state.sortedInfo.columnKey === "larhrs" && state.sortedInfo.order,
sortOrder: render: (text, record) => record.larhrs.aggregate.sum.mod_lb_hrs
state.sortedInfo.columnKey === "larhrs" && state.sortedInfo.order,
render: (text, record) => record.larhrs.aggregate.sum.mod_lb_hrs,
}, },
{ {
title: i18n.t("production.labels.totalhours"), title: i18n.t("production.labels.totalhours"),
@@ -393,38 +310,36 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
a.labhrs.aggregate.sum.mod_lb_hrs + a.labhrs.aggregate.sum.mod_lb_hrs +
a.larhrs.aggregate.sum.mod_lb_hrs - a.larhrs.aggregate.sum.mod_lb_hrs -
(b.labhrs.aggregate.sum.mod_lb_hrs + b.larhrs.aggregate.sum.mod_lb_hrs), (b.labhrs.aggregate.sum.mod_lb_hrs + b.larhrs.aggregate.sum.mod_lb_hrs),
sortOrder: sortOrder: state.sortedInfo.columnKey === "totalhours" && state.sortedInfo.order,
state.sortedInfo.columnKey === "totalhours" && state.sortedInfo.order,
render: (text, record) => render: (text, record) =>
( (record.labhrs.aggregate.sum.mod_lb_hrs + record.larhrs.aggregate.sum.mod_lb_hrs).toFixed(1)
record.labhrs.aggregate.sum.mod_lb_hrs +
record.larhrs.aggregate.sum.mod_lb_hrs
).toFixed(1),
}, },
{ {
title: i18n.t("production.labels.alert"), title: i18n.t("production.labels.alert"),
dataIndex: "alert", dataIndex: "alert",
key: "alert", key: "alert",
sorter: (a, b) => sorter: (a, b) => Number(a.production_vars?.alert || false) - Number(b.production_vars?.alert || false),
Number(a.production_vars?.alert || false) - sortOrder: state.sortedInfo.columnKey === "alert" && state.sortedInfo.order,
Number(b.production_vars?.alert || false), filters: [
sortOrder: { text: "True", value: true },
state.sortedInfo.columnKey === "alert" && state.sortedInfo.order, { text: "False", value: false }
render: (text, record) => <ProductionListColumnAlert record={record} />, ],
onFilter: (value, record) => value === (record.production_vars?.alert || false),
render: (text, record) => <ProductionListColumnAlert record={record} />
}, },
{ {
title: i18n.t("production.labels.note"), title: i18n.t("production.labels.note"),
dataIndex: "note", dataIndex: "note",
key: "note", key: "note",
ellipsis: true, ellipsis: true,
render: (text, record) => <ProductionListColumnNote record={record} />, render: (text, record) => <ProductionListColumnNote record={record} />
}, },
{ {
title: i18n.t("production.labels.comment"), title: i18n.t("production.labels.comment"),
dataIndex: "comment", dataIndex: "comment",
key: "comment", key: "comment",
ellipsis: true, ellipsis: true,
render: (text, record) => <ProductionListColumnComment record={record} />, render: (text, record) => <ProductionListColumnComment record={record} />
}, },
{ {
title: i18n.t("production.labels.touchtime"), title: i18n.t("production.labels.touchtime"),
@@ -432,7 +347,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "tt", key: "tt",
render: (text, record) => { render: (text, record) => {
return <ProductionlistColumnTouchTime job={record} />; return <ProductionlistColumnTouchTime job={record} />;
}, }
}, },
{ {
title: i18n.t("production.labels.bodypriority"), title: i18n.t("production.labels.bodypriority"),
@@ -442,11 +357,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
sorter: (a, b) => sorter: (a, b) =>
((a.production_vars && a.production_vars.bodypriority) || 11) - ((a.production_vars && a.production_vars.bodypriority) || 11) -
((b.production_vars && b.production_vars.bodypriority) || 11), ((b.production_vars && b.production_vars.bodypriority) || 11),
sortOrder: sortOrder: state.sortedInfo.columnKey === "bodypriority" && state.sortedInfo.order,
state.sortedInfo.columnKey === "bodypriority" && state.sortedInfo.order, render: (text, record) => <ProductionListColumnBodyPriority record={record} />
render: (text, record) => (
<ProductionListColumnBodyPriority record={record} />
),
}, },
{ {
title: i18n.t("production.labels.paintpriority"), title: i18n.t("production.labels.paintpriority"),
@@ -456,12 +368,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
sorter: (a, b) => sorter: (a, b) =>
((a.production_vars && a.production_vars.paintpriority) || 11) - ((a.production_vars && a.production_vars.paintpriority) || 11) -
((b.production_vars && b.production_vars.paintpriority) || 11), ((b.production_vars && b.production_vars.paintpriority) || 11),
sortOrder: sortOrder: state.sortedInfo.columnKey === "paintpriority" && state.sortedInfo.order,
state.sortedInfo.columnKey === "paintpriority" && render: (text, record) => <ProductionListColumnPaintPriority record={record} />
state.sortedInfo.order,
render: (text, record) => (
<ProductionListColumnPaintPriority record={record} />
),
}, },
{ {
title: i18n.t("production.labels.detailpriority"), title: i18n.t("production.labels.detailpriority"),
@@ -471,110 +379,74 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
sorter: (a, b) => sorter: (a, b) =>
((a.production_vars && a.production_vars.detailpriority) || 11) - ((a.production_vars && a.production_vars.detailpriority) || 11) -
((b.production_vars && b.production_vars.detailpriority) || 11), ((b.production_vars && b.production_vars.detailpriority) || 11),
sortOrder: sortOrder: state.sortedInfo.columnKey === "detailpriority" && state.sortedInfo.order,
state.sortedInfo.columnKey === "detailpriority" && render: (text, record) => <ProductionListColumnDetailPriority record={record} />
state.sortedInfo.order,
render: (text, record) => (
<ProductionListColumnDetailPriority record={record} />
),
}, },
{ {
title: i18n.t("production.labels.sublets"), title: i18n.t("production.labels.sublets"),
dataIndex: "sublets", dataIndex: "sublets",
key: "sublets", key: "sublets",
render: (text, record) => ( render: (text, record) => <ProductionSubletsManageComponent subletJobLines={record.subletLines} />
<ProductionSubletsManageComponent subletJobLines={record.subletLines} />
),
}, },
{ {
title: i18n.t("jobs.fields.employee_body"), title: i18n.t("jobs.fields.employee_body"),
dataIndex: "employee_body", dataIndex: "employee_body",
key: "employee_body", key: "employee_body",
sortOrder: sortOrder: state.sortedInfo.columnKey === "employee_body" && state.sortedInfo.order,
state.sortedInfo.columnKey === "employee_body" &&
state.sortedInfo.order,
sorter: (a, b) => sorter: (a, b) =>
alphaSort( alphaSort(
bodyshop.employees?.find((e) => e.id === a.employee_body)?.first_name, bodyshop.employees?.find((e) => e.id === a.employee_body)?.first_name,
bodyshop.employees?.find((e) => e.id === b.employee_body)?.first_name bodyshop.employees?.find((e) => e.id === b.employee_body)?.first_name
), ),
render: (text, record) => ( render: (text, record) => <ProductionListEmployeeAssignment record={record} type="employee_body" />
<ProductionListEmployeeAssignment
record={record}
type="employee_body"
/>
),
}, },
{ {
title: i18n.t("jobs.fields.employee_prep"), title: i18n.t("jobs.fields.employee_prep"),
dataIndex: "employee_prep", dataIndex: "employee_prep",
key: "employee_prep", key: "employee_prep",
sortOrder: sortOrder: state.sortedInfo.columnKey === "employee_prep" && state.sortedInfo.order,
state.sortedInfo.columnKey === "employee_prep" &&
state.sortedInfo.order,
sorter: (a, b) => sorter: (a, b) =>
alphaSort( alphaSort(
bodyshop.employees?.find((e) => e.id === a.employee_prep)?.first_name, bodyshop.employees?.find((e) => e.id === a.employee_prep)?.first_name,
bodyshop.employees?.find((e) => e.id === b.employee_prep)?.first_name bodyshop.employees?.find((e) => e.id === b.employee_prep)?.first_name
), ),
render: (text, record) => ( render: (text, record) => <ProductionListEmployeeAssignment record={record} type="employee_prep" />
<ProductionListEmployeeAssignment
record={record}
type="employee_prep"
/>
),
}, },
{ {
title: i18n.t("jobs.fields.employee_csr"), title: i18n.t("jobs.fields.employee_csr"),
dataIndex: "employee_csr", dataIndex: "employee_csr",
key: "employee_csr", key: "employee_csr",
sortOrder: sortOrder: state.sortedInfo.columnKey === "employee_csr" && state.sortedInfo.order,
state.sortedInfo.columnKey === "employee_csr" && state.sortedInfo.order,
sorter: (a, b) => sorter: (a, b) =>
alphaSort( alphaSort(
bodyshop.employees?.find((e) => e.id === a.employee_csr)?.first_name, bodyshop.employees?.find((e) => e.id === a.employee_csr)?.first_name,
bodyshop.employees?.find((e) => e.id === b.employee_csr)?.first_name bodyshop.employees?.find((e) => e.id === b.employee_csr)?.first_name
), ),
render: (text, record) => ( render: (text, record) => <ProductionListEmployeeAssignment record={record} type="employee_csr" />
<ProductionListEmployeeAssignment record={record} type="employee_csr" />
),
}, },
{ {
title: i18n.t("jobs.fields.employee_refinish"), title: i18n.t("jobs.fields.employee_refinish"),
dataIndex: "employee_refinish", dataIndex: "employee_refinish",
key: "employee_refinish", key: "employee_refinish",
sortOrder: sortOrder: state.sortedInfo.columnKey === "employee_refinish" && state.sortedInfo.order,
state.sortedInfo.columnKey === "employee_refinish" &&
state.sortedInfo.order,
sorter: (a, b) => sorter: (a, b) =>
alphaSort( alphaSort(
bodyshop.employees?.find((e) => e.id === a.employee_refinish) bodyshop.employees?.find((e) => e.id === a.employee_refinish)?.first_name,
?.first_name, bodyshop.employees?.find((e) => e.id === b.employee_refinish)?.first_name
bodyshop.employees?.find((e) => e.id === b.employee_refinish)
?.first_name
), ),
render: (text, record) => ( render: (text, record) => <ProductionListEmployeeAssignment record={record} type="employee_refinish" />
<ProductionListEmployeeAssignment
record={record}
type="employee_refinish"
/>
),
}, },
{ {
title: i18n.t("jobs.labels.parts_received"), title: i18n.t("jobs.labels.parts_received"),
dataIndex: "parts_received", dataIndex: "parts_received",
key: "parts_received", key: "parts_received",
render: (text, record) => ( render: (text, record) => <ProductionListColumnPartsReceived record={record} />
<ProductionListColumnPartsReceived record={record} />
),
}, },
{ {
title: i18n.t("jobs.fields.partsstatus"), title: i18n.t("jobs.fields.partsstatus"),
dataIndex: "partsstatus", dataIndex: "partsstatus",
key: "partsstatus", key: "partsstatus",
render: (text, record) => ( render: (text, record) => <JobPartsQueueCount parts={record.joblines_status} record={record} />
<JobPartsQueueCount parts={record.joblines_status} record={record} />
),
}, },
{ {
title: i18n.t("jobs.labels.estimator"), title: i18n.t("jobs.labels.estimator"),
@@ -585,8 +457,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
`${a.est_ct_fn || ""} ${a.est_ct_ln || ""}`.trim(), `${a.est_ct_fn || ""} ${a.est_ct_ln || ""}`.trim(),
`${b.est_ct_fn || ""} ${b.est_ct_ln || ""}`.trim() `${b.est_ct_fn || ""} ${b.est_ct_ln || ""}`.trim()
), ),
sortOrder: sortOrder: state.sortedInfo.columnKey === "estimator" && state.sortedInfo.order,
state.sortedInfo.columnKey === "estimator" && state.sortedInfo.order,
filters: filters:
(data && (data &&
data data
@@ -595,16 +466,12 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
.map((s) => { .map((s) => {
return { return {
text: s || "N/A", text: s || "N/A",
value: [s], value: [s]
}; };
})) || })) ||
[], [],
onFilter: (value, record) => onFilter: (value, record) => value.includes(`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim()),
value.includes( render: (text, record) => `${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim()
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim()
),
render: (text, record) =>
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim(),
}, },
//Added as a place holder for St Claude. Not implemented as it requires another join for a field used by only 1 client. //Added as a place holder for St Claude. Not implemented as it requires another join for a field used by only 1 client.
@@ -634,12 +501,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "date_repairstarted", key: "date_repairstarted",
ellipsis: true, ellipsis: true,
sorter: (a, b) => dateSort(a.date_repairstarted, b.date_repairstarted), sorter: (a, b) => dateSort(a.date_repairstarted, b.date_repairstarted),
sortOrder: sortOrder: state.sortedInfo.columnKey === "date_repairstarted" && state.sortedInfo.order,
state.sortedInfo.columnKey === "date_repairstarted" && render: (text, record) => <ProductionListDate record={record} field="date_repairstarted" time />
state.sortedInfo.order,
render: (text, record) => (
<ProductionListDate record={record} field="date_repairstarted" time />
),
}, },
{ {
title: i18n.t("jobs.fields.date_repairstarted") + " (HH:MM)", title: i18n.t("jobs.fields.date_repairstarted") + " (HH:MM)",
@@ -647,10 +510,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "date_repairstarted_time", key: "date_repairstarted_time",
ellipsis: true, ellipsis: true,
render: (text, record) => ( render: (text, record) => <TimeFormatter>{record.date_repairstarted}</TimeFormatter>
<TimeFormatter>{record.date_repairstarted}</TimeFormatter> }
),
},
]; ];
}; };
export default r; export default r;

View File

@@ -6,7 +6,8 @@ import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort, statusSort } from "../../utils/sorters"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
import OwnerNameDisplay, { import OwnerNameDisplay, {
OwnerNameDisplayFunction, OwnerNameDisplayFunction,
} from "../owner-name-display/owner-name-display.component"; } from "../owner-name-display/owner-name-display.component";
@@ -79,7 +80,18 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
})), })),
onFilter: (value, record) => value.includes(record.status), onFilter: (value, record) => value.includes(record.status),
}, },
{
title: t("jobs.fields.actual_completion"),
dataIndex: "actual_completion",
key: "actual_completion",
render: (text, record) => (
<DateTimeFormatter>{record.actual_completion}</DateTimeFormatter>
),
sorter: (a, b) => dateSort(a.actual_completion, b.actual_completion),
sortOrder:
state.sortedInfo.columnKey === "actual_completion" &&
state.sortedInfo.order,
},
{ {
title: t("jobs.fields.clm_total"), title: t("jobs.fields.clm_total"),
dataIndex: "clm_total", dataIndex: "clm_total",

View File

@@ -5,7 +5,6 @@ import { getFirestore } from "firebase/firestore";
import { getMessaging, getToken, onMessage } from "firebase/messaging"; import { getMessaging, getToken, onMessage } from "firebase/messaging";
import { store } from "../redux/store"; import { store } from "../redux/store";
import axios from "axios"; import axios from "axios";
import { checkBeta } from "../utils/handleBeta";
const config = JSON.parse(process.env.REACT_APP_FIREBASE_CONFIG); const config = JSON.parse(process.env.REACT_APP_FIREBASE_CONFIG);
initializeApp(config); initializeApp(config);
@@ -51,7 +50,7 @@ export { messaging };
export const requestForToken = () => { export const requestForToken = () => {
return getToken(messaging, { return getToken(messaging, {
vapidKey: process.env.REACT_APP_FIREBASE_PUBLIC_VAPID_KEY, vapidKey: process.env.REACT_APP_FIREBASE_PUBLIC_VAPID_KEY
}) })
.then((currentToken) => { .then((currentToken) => {
if (currentToken) { if (currentToken) {
@@ -59,9 +58,7 @@ export const requestForToken = () => {
// Perform any other necessary action with the token // Perform any other necessary action with the token
} else { } else {
// Show permission request UI // Show permission request UI
console.log( console.log("No registration token available. Request permission to generate one.");
"No registration token available. Request permission to generate one."
);
} }
}) })
.catch((err) => { .catch((err) => {
@@ -80,24 +77,17 @@ export const onMessageListener = () =>
export const logImEXEvent = (eventName, additionalParams, stateProp = null) => { export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
const state = stateProp || store.getState(); const state = stateProp || store.getState();
const eventParams = { const eventParams = {
shop: shop: (state.user && state.user.bodyshop && state.user.bodyshop.shopname) || null,
(state.user && state.user.bodyshop && state.user.bodyshop.shopname) || user: (state.user && state.user.currentUser && state.user.currentUser.email) || null,
null, ...additionalParams
user:
(state.user && state.user.currentUser && state.user.currentUser.email) ||
null,
...additionalParams,
}; };
axios.post("/ioevent", { axios.post("/ioevent", {
useremail: useremail: (state.user && state.user.currentUser && state.user.currentUser.email) || null,
(state.user && state.user.currentUser && state.user.currentUser.email) || bodyshopid: (state.user && state.user.bodyshop && state.user.bodyshop.id) || null,
null,
bodyshopid:
(state.user && state.user.bodyshop && state.user.bodyshop.id) || null,
operationName: eventName, operationName: eventName,
variables: additionalParams, variables: additionalParams,
dbevent: false, dbevent: false,
env: checkBeta() ? "beta" : "master", env: "master"
}); });
// console.log( // console.log(

View File

@@ -58,7 +58,7 @@ export const QUERY_ALL_BILLS_PAGINATED = gql`
} }
`; `;
export const QUERY_BILLS_BY_JOBID = gql` export const QUERY_PARTS_BILLS_BY_JOBID = gql`
query QUERY_PARTS_BILLS_BY_JOBID($jobid: uuid!) { query QUERY_PARTS_BILLS_BY_JOBID($jobid: uuid!) {
parts_orders( parts_orders(
where: { jobid: { _eq: $jobid } } where: { jobid: { _eq: $jobid } }
@@ -73,6 +73,7 @@ export const QUERY_BILLS_BY_JOBID = gql`
order_date order_date
deliver_by deliver_by
return return
returnfrombill
orderedby orderedby
parts_order_lines { parts_order_lines {
id id

View File

@@ -1,7 +1,7 @@
import { gql } from "@apollo/client"; import { gql } from "@apollo/client";
export const QUERY_ALL_ACTIVE_JOBS_PAGINATED = gql` export const QUERY_ALL_ACTIVE_JOBS_PAGINATED = gql`
query QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED( query QUERY_ALL_ACTIVE_JOBS_PAGINATED(
$offset: Int $offset: Int
$limit: Int $limit: Int
$order: [jobs_order_by!] $order: [jobs_order_by!]

View File

@@ -71,6 +71,7 @@ export const QUERY_OWNER_BY_ID = gql`
tax_number tax_number
jobs(order_by: { date_open: desc }) { jobs(order_by: { date_open: desc }) {
id id
actual_completion
ro_number ro_number
clm_no clm_no
status status

View File

@@ -30,6 +30,7 @@ export const QUERY_VEHICLE_BY_ID = gql`
notes notes
jobs(order_by: { date_open: desc }) { jobs(order_by: { date_open: desc }) {
id id
actual_completion
ro_number ro_number
ownr_co_nm ownr_co_nm
ownr_fn ownr_fn

View File

@@ -8,6 +8,7 @@ import Icon, {
SyncOutlined, SyncOutlined,
ToolFilled, ToolFilled,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useQuery } from "@apollo/client";
import { import {
Button, Button,
Divider, Divider,
@@ -48,6 +49,7 @@ import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gal
import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container"; import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container";
import NoteUpsertModalComponent from "../../components/note-upsert-modal/note-upsert-modal.container"; import NoteUpsertModalComponent from "../../components/note-upsert-modal/note-upsert-modal.container";
import ScheduleJobModalContainer from "../../components/schedule-job-modal/schedule-job-modal.container"; import ScheduleJobModalContainer from "../../components/schedule-job-modal/schedule-job-modal.container";
import { QUERY_PARTS_BILLS_BY_JOBID } from "../../graphql/bills.queries.js";
import { insertAuditTrail } from "../../redux/application/application.actions"; import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
@@ -78,19 +80,49 @@ export function JobsDetailPage({
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [form] = Form.useForm(); const [form] = Form.useForm();
const history = useHistory();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const search = queryString.parse(useLocation().search); const search = queryString.parse(useLocation().search);
const history = useHistory();
const formItemLayout = { const formItemLayout = {
layout: "vertical", layout: "vertical",
}; };
const billsQuery = useQuery(QUERY_PARTS_BILLS_BY_JOBID, {
variables: { jobid: job.id },
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
useEffect(() => { useEffect(() => {
//form.setFieldsValue(transormJobToForm(job)); //form.setFieldsValue(transormJobToForm(job));
form.resetFields(); form.resetFields();
}, [form, job]); }, [form, job]);
const handleBillOnRowClick = (record) => {
if (record) {
if (record.id) {
search.billid = record.id;
history.push({ search: queryString.stringify(search) });
}
} else {
delete search.billid;
history.push({ search: queryString.stringify(search) });
}
};
const handlePartsOrderOnRowClick = (record) => {
if (record) {
if (record.id) {
search.partsorderid = record.id;
history.push({ search: queryString.stringify(search) });
}
} else {
delete search.partsorderid;
history.push({ search: queryString.stringify(search) });
}
};
//useKeyboardSaveShortcut(form.submit); //useKeyboardSaveShortcut(form.submit);
const handleFinish = async (values) => { const handleFinish = async (values) => {
@@ -286,6 +318,9 @@ export function JobsDetailPage({
<JobsLinesContainer <JobsLinesContainer
job={job} job={job}
joblines={job.joblines} joblines={job.joblines}
billsQuery={billsQuery}
handleBillOnRowClick={handleBillOnRowClick}
handlePartsOrderOnRowClick={handlePartsOrderOnRowClick}
refetch={refetch} refetch={refetch}
form={form} form={form}
/> />
@@ -322,7 +357,12 @@ export function JobsDetailPage({
} }
key="partssublet" key="partssublet"
> >
<JobsDetailPliContainer job={job} /> <JobsDetailPliContainer
job={job}
billsQuery={billsQuery}
handleBillOnRowClick={handleBillOnRowClick}
handlePartsOrderOnRowClick={handlePartsOrderOnRowClick}
/>
</Tabs.TabPane> </Tabs.TabPane>
<Tabs.TabPane <Tabs.TabPane
tab={ tab={

View File

@@ -222,6 +222,7 @@
"onlycmforinvoiced": "Only credit memos can be entered for any Job that has been invoiced, exported, or voided.", "onlycmforinvoiced": "Only credit memos can be entered for any Job that has been invoiced, exported, or voided.",
"printlabels": "Print Labels", "printlabels": "Print Labels",
"retailtotal": "Bills Retail Total", "retailtotal": "Bills Retail Total",
"returnfrombill": "Return From Bill",
"savewithdiscrepancy": "You are about to save this bill with a discrepancy. The system will continue to use the calculated amount using the bill lines. Press cancel to return to the bill.", "savewithdiscrepancy": "You are about to save this bill with a discrepancy. The system will continue to use the calculated amount using the bill lines. Press cancel to return to the bill.",
"state_tax": "Provincial/State Tax", "state_tax": "Provincial/State Tax",
"subtotal": "Subtotal", "subtotal": "Subtotal",
@@ -827,10 +828,11 @@
}, },
"status": { "status": {
"in": "Available", "in": "Available",
"inservice": "In Service", "inservice": "Service/Maintenance",
"leasereturn": "Lease Returned", "leasereturn": "Lease Returned",
"out": "Rented", "out": "Rented",
"sold": "Sold" "sold": "Sold",
"unavailable": "Unavailable"
}, },
"successes": { "successes": {
"saved": "Courtesy Car saved successfully." "saved": "Courtesy Car saved successfully."
@@ -885,6 +887,7 @@
"refhrs": "Refinish Hrs" "refhrs": "Refinish Hrs"
}, },
"titles": { "titles": {
"joblifecycle": "Job Lifecycle",
"labhours": "Total Body Hours", "labhours": "Total Body Hours",
"larhours": "Total Refinish Hours", "larhours": "Total Refinish Hours",
"monthlyemployeeefficiency": "Monthly Employee Efficiency", "monthlyemployeeefficiency": "Monthly Employee Efficiency",
@@ -899,8 +902,7 @@
"scheduledindate": "Sheduled In Today: {{date}}", "scheduledindate": "Sheduled In Today: {{date}}",
"scheduledintoday": "Sheduled In Today", "scheduledintoday": "Sheduled In Today",
"scheduledoutdate": "Sheduled Out Today: {{date}}", "scheduledoutdate": "Sheduled Out Today: {{date}}",
"scheduledouttoday": "Sheduled Out Today", "scheduledouttoday": "Sheduled Out Today"
"joblifecycle": "Job Lifecycle"
} }
}, },
"dms": { "dms": {
@@ -1236,22 +1238,21 @@
"columns": { "columns": {
"duration": "Duration", "duration": "Duration",
"end": "End", "end": "End",
"human_readable": "Human Readable",
"percentage": "Percentage",
"relative_end": "Relative End", "relative_end": "Relative End",
"relative_start": "Relative Start", "relative_start": "Relative Start",
"start": "Start", "start": "Start",
"value": "Value",
"status": "Status", "status": "Status",
"percentage": "Percentage", "status_count": "In Status",
"human_readable": "Human Readable", "value": "Value"
"status_count": "In Status"
},
"titles": {
"dashboard": "Job Lifecycle",
"top_durations": "Top Durations"
}, },
"content": { "content": {
"calculated_based_on": "Calculated based on",
"current_status_accumulated_time": "Current Status Accumulated Time", "current_status_accumulated_time": "Current Status Accumulated Time",
"data_unavailable": " There is currently no Lifecycle data for this Job.", "data_unavailable": " There is currently no Lifecycle data for this Job.",
"joblifecycle": "",
"jobs_in_since": "Jobs in since",
"legend_title": "Legend", "legend_title": "Legend",
"loading": "Loading Job Timelines....", "loading": "Loading Job Timelines....",
"not_available": "N/A", "not_available": "N/A",
@@ -1259,12 +1260,14 @@
"title": "Job Lifecycle Component", "title": "Job Lifecycle Component",
"title_durations": "Historical Status Durations", "title_durations": "Historical Status Durations",
"title_loading": "Loading", "title_loading": "Loading",
"title_transitions": "Transitions", "title_transitions": "Transitions"
"calculated_based_on": "Calculated based on",
"jobs_in_since": "Jobs in since"
}, },
"errors": { "errors": {
"fetch": "Error getting Job Lifecycle Data" "fetch": "Error getting Job Lifecycle Data"
},
"titles": {
"dashboard": "Job Lifecycle",
"top_durations": "Top Durations"
} }
}, },
"job_payments": { "job_payments": {
@@ -1284,6 +1287,7 @@
"amount": "Amount", "amount": "Amount",
"dateOfPayment": "Date of Payment", "dateOfPayment": "Date of Payment",
"descriptions": "Payment Details", "descriptions": "Payment Details",
"hint": "Hint",
"payer": "Payer", "payer": "Payer",
"payername": "Payer Name", "payername": "Payer Name",
"paymentid": "Payment Reference ID", "paymentid": "Payment Reference ID",

View File

@@ -217,10 +217,12 @@
"markexported": "", "markexported": "",
"markforreexport": "", "markforreexport": "",
"new": "", "new": "",
"nobilllines": "",
"noneselected": "", "noneselected": "",
"onlycmforinvoiced": "", "onlycmforinvoiced": "",
"printlabels": "", "printlabels": "",
"retailtotal": "", "retailtotal": "",
"returnfrombill": "",
"savewithdiscrepancy": "", "savewithdiscrepancy": "",
"state_tax": "", "state_tax": "",
"subtotal": "", "subtotal": "",
@@ -829,7 +831,8 @@
"inservice": "", "inservice": "",
"leasereturn": "", "leasereturn": "",
"out": "", "out": "",
"sold": "" "sold": "",
"unavailable": ""
}, },
"successes": { "successes": {
"saved": "" "saved": ""
@@ -884,6 +887,7 @@
"refhrs": "" "refhrs": ""
}, },
"titles": { "titles": {
"joblifecycle": "",
"labhours": "", "labhours": "",
"larhours": "", "larhours": "",
"monthlyemployeeefficiency": "", "monthlyemployeeefficiency": "",
@@ -898,8 +902,7 @@
"scheduledindate": "", "scheduledindate": "",
"scheduledintoday": "", "scheduledintoday": "",
"scheduledoutdate": "", "scheduledoutdate": "",
"scheduledouttoday": "", "scheduledouttoday": ""
"joblifecycle": ""
} }
}, },
"dms": { "dms": {
@@ -1114,6 +1117,7 @@
"loadingshop": "Cargando datos de la tienda ...", "loadingshop": "Cargando datos de la tienda ...",
"loggingin": "Iniciando sesión ...", "loggingin": "Iniciando sesión ...",
"markedexported": "", "markedexported": "",
"media": "",
"message": "", "message": "",
"monday": "", "monday": "",
"na": "N / A", "na": "N / A",
@@ -1234,22 +1238,21 @@
"columns": { "columns": {
"duration": "", "duration": "",
"end": "", "end": "",
"human_readable": "",
"percentage": "",
"relative_end": "", "relative_end": "",
"relative_start": "", "relative_start": "",
"start": "", "start": "",
"value": "",
"status": "", "status": "",
"percentage": "", "status_count": "",
"human_readable": "", "value": ""
"status_count": ""
},
"titles": {
"dashboard": "",
"top_durations": ""
}, },
"content": { "content": {
"calculated_based_on": "",
"current_status_accumulated_time": "", "current_status_accumulated_time": "",
"data_unavailable": "", "data_unavailable": "",
"joblifecycle": "",
"jobs_in_since": "",
"legend_title": "", "legend_title": "",
"loading": "", "loading": "",
"not_available": "", "not_available": "",
@@ -1257,12 +1260,14 @@
"title": "", "title": "",
"title_durations": "", "title_durations": "",
"title_loading": "", "title_loading": "",
"title_transitions": "", "title_transitions": ""
"calculated_based_on": "",
"jobs_in_since": ""
}, },
"errors": { "errors": {
"fetch": "Error al obtener los datos del ciclo de vida del trabajo" "fetch": "Error al obtener los datos del ciclo de vida del trabajo"
},
"titles": {
"dashboard": "",
"top_durations": ""
} }
}, },
"job_payments": { "job_payments": {
@@ -1282,6 +1287,7 @@
"amount": "", "amount": "",
"dateOfPayment": "", "dateOfPayment": "",
"descriptions": "", "descriptions": "",
"hint": "",
"payer": "", "payer": "",
"payername": "", "payername": "",
"paymentid": "", "paymentid": "",

View File

@@ -217,10 +217,12 @@
"markexported": "", "markexported": "",
"markforreexport": "", "markforreexport": "",
"new": "", "new": "",
"nobilllines": "",
"noneselected": "", "noneselected": "",
"onlycmforinvoiced": "", "onlycmforinvoiced": "",
"printlabels": "", "printlabels": "",
"retailtotal": "", "retailtotal": "",
"returnfrombill": "",
"savewithdiscrepancy": "", "savewithdiscrepancy": "",
"state_tax": "", "state_tax": "",
"subtotal": "", "subtotal": "",
@@ -829,7 +831,8 @@
"inservice": "", "inservice": "",
"leasereturn": "", "leasereturn": "",
"out": "", "out": "",
"sold": "" "sold": "",
"unavailable": ""
}, },
"successes": { "successes": {
"saved": "" "saved": ""
@@ -884,6 +887,7 @@
"refhrs": "" "refhrs": ""
}, },
"titles": { "titles": {
"joblifecycle": "",
"labhours": "", "labhours": "",
"larhours": "", "larhours": "",
"monthlyemployeeefficiency": "", "monthlyemployeeefficiency": "",
@@ -1113,6 +1117,7 @@
"loadingshop": "Chargement des données de la boutique ...", "loadingshop": "Chargement des données de la boutique ...",
"loggingin": "Vous connecter ...", "loggingin": "Vous connecter ...",
"markedexported": "", "markedexported": "",
"media": "",
"message": "", "message": "",
"monday": "", "monday": "",
"na": "N / A", "na": "N / A",
@@ -1233,22 +1238,21 @@
"columns": { "columns": {
"duration": "", "duration": "",
"end": "", "end": "",
"human_readable": "",
"percentage": "",
"relative_end": "", "relative_end": "",
"relative_start": "", "relative_start": "",
"start": "", "start": "",
"value": "",
"status": "", "status": "",
"percentage": "", "status_count": "",
"human_readable": "", "value": ""
"status_count": ""
},
"titles": {
"dashboard": "",
"top_durations": ""
}, },
"content": { "content": {
"calculated_based_on": "",
"current_status_accumulated_time": "", "current_status_accumulated_time": "",
"data_unavailable": "", "data_unavailable": "",
"joblifecycle": "",
"jobs_in_since": "",
"legend_title": "", "legend_title": "",
"loading": "", "loading": "",
"not_available": "", "not_available": "",
@@ -1256,13 +1260,14 @@
"title": "", "title": "",
"title_durations": "", "title_durations": "",
"title_loading": "", "title_loading": "",
"title_transitions": "", "title_transitions": ""
"calculated_based_on": "",
"jobs_in_since": "",
"joblifecycle": ""
}, },
"errors": { "errors": {
"fetch": "Erreur lors de l'obtention des données du cycle de vie des tâches" "fetch": "Erreur lors de l'obtention des données du cycle de vie des tâches"
},
"titles": {
"dashboard": "",
"top_durations": ""
} }
}, },
"job_payments": { "job_payments": {
@@ -1282,6 +1287,7 @@
"amount": "", "amount": "",
"dateOfPayment": "", "dateOfPayment": "",
"descriptions": "", "descriptions": "",
"hint": "",
"payer": "", "payer": "",
"payername": "", "payername": "",
"paymentid": "", "paymentid": "",

View File

@@ -1,37 +0,0 @@
export const BETA_KEY = 'betaSwitchImex';
export const checkBeta = () => {
const cookie = document.cookie.split('; ').find(row => row.startsWith(BETA_KEY));
return cookie ? cookie.split('=')[1] === 'true' : false;
}
export const setBeta = (value) => {
const domain = window.location.hostname.split('.').slice(-2).join('.');
document.cookie = `${BETA_KEY}=${value}; path=/; domain=.${domain}`;
}
export const handleBeta = () => {
// If the current host name does not start with beta or test, then we don't need to do anything.
if (window.location.hostname.startsWith('localhost')) {
console.log('Not on beta or test, so no need to handle beta.');
return;
}
const isBeta = checkBeta();
const currentHostName = window.location.hostname;
// Beta is enabled, but the current host name does start with beta.
if (isBeta && !currentHostName.startsWith('beta')) {
const href= `${window.location.protocol}//beta.${currentHostName}${window.location.pathname}${window.location.search}${window.location.hash}`;
window.location.replace(href);
}
// Beta is not enabled, but the current host name does start with beta.
else if (!isBeta && currentHostName.startsWith('beta')) {
const href = `${window.location.protocol}//${currentHostName.replace('beta.', '')}${window.location.pathname}${window.location.search}${window.location.hash}`;
window.location.replace(href);
}
}
export default handleBeta;

View File

@@ -224,11 +224,34 @@ exports.default = async function (socket, jobid) {
if (mapaAccount) { if (mapaAccount) {
if (!costCenterHash[mapaAccountName]) if (!costCenterHash[mapaAccountName])
costCenterHash[mapaAccountName] = Dinero(); costCenterHash[mapaAccountName] = Dinero();
costCenterHash[mapaAccountName] = costCenterHash[mapaAccountName].add( if (job.bodyshop.use_paint_scale_data === true) {
Dinero(job.job_totals.rates.mapa.total).percentage( if (job.mixdata.length > 0) {
bodyshop?.cdk_configuration?.sendmaterialscosting costCenterHash[mapaAccountName] = costCenterHash[
) mapaAccountName
); ].add(
Dinero({
amount: Math.round(
((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) *
100
),
})
);
} else {
costCenterHash[mapaAccountName] = costCenterHash[
mapaAccountName
].add(
Dinero(job.job_totals.rates.mapa.total).percentage(
bodyshop?.cdk_configuration?.sendmaterialscosting
)
);
}
} else {
costCenterHash[mapaAccountName] = costCenterHash[mapaAccountName].add(
Dinero(job.job_totals.rates.mapa.total).percentage(
bodyshop?.cdk_configuration?.sendmaterialscosting
)
);
}
} else { } else {
//console.log("NO MAPA ACCOUNT FOUND!!"); //console.log("NO MAPA ACCOUNT FOUND!!");
} }

View File

@@ -1793,6 +1793,7 @@ exports.GET_CDK_ALLOCATIONS = `query QUERY_JOB_CLOSE_DETAILS($id: uuid!) {
md_responsibility_centers md_responsibility_centers
cdk_configuration cdk_configuration
pbs_configuration pbs_configuration
use_paint_scale_data
} }
ro_number ro_number
dms_allocation dms_allocation
@@ -1900,6 +1901,10 @@ exports.GET_CDK_ALLOCATIONS = `query QUERY_JOB_CLOSE_DETAILS($id: uuid!) {
line_ref line_ref
unq_seq unq_seq
} }
mixdata(limit: 1, order_by: {updated_at: desc}) {
jobid
totalliquidcost
}
} }
}`; }`;
@@ -2175,4 +2180,12 @@ exports.COMPLETE_SURVEY = `mutation COMPLETE_SURVEY($surveyId: uuid!, $survey: c
update_csi(where: { id: { _eq: $surveyId } }, _set: $survey) { update_csi(where: { id: { _eq: $surveyId } }, _set: $survey) {
affected_rows affected_rows
} }
}`; }`;
exports.GET_JOBS_BY_PKS = `query GET_JOBS_BY_PKS($ids: [uuid!]!) {
jobs(where: {id: {_in: $ids}}) {
id
shopid
}
}
`;

View File

@@ -8,21 +8,15 @@ const moment = require("moment");
const logger = require("../utils/logger"); const logger = require("../utils/logger");
require("dotenv").config({ require("dotenv").config({
path: path.resolve( path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
}); });
const domain = process.env.NODE_ENV ? "secure" : "test"; const domain = process.env.NODE_ENV ? "secure" : "test";
const { const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager");
SecretsManagerClient,
GetSecretValueCommand,
} = require("@aws-sdk/client-secrets-manager");
const client = new SecretsManagerClient({ const client = new SecretsManagerClient({
region: "ca-central-1", region: "ca-central-1" //TODO-AIO: instance manager required when merged to master-AIO
}); });
const gqlClient = require("../graphql-client/graphql-client").client; const gqlClient = require("../graphql-client/graphql-client").client;
@@ -32,7 +26,7 @@ const getShopCredentials = async (bodyshop) => {
if (process.env.NODE_ENV === undefined) { if (process.env.NODE_ENV === undefined) {
return { return {
merchantkey: process.env.INTELLIPAY_MERCHANTKEY, merchantkey: process.env.INTELLIPAY_MERCHANTKEY,
apikey: process.env.INTELLIPAY_APIKEY, apikey: process.env.INTELLIPAY_APIKEY
}; };
} }
@@ -42,26 +36,20 @@ const getShopCredentials = async (bodyshop) => {
const secret = await client.send( const secret = await client.send(
new GetSecretValueCommand({ new GetSecretValueCommand({
SecretId: `intellipay-credentials-${bodyshop.imexshopid}`, SecretId: `intellipay-credentials-${bodyshop.imexshopid}`,
VersionStage: "AWSCURRENT", // VersionStage defaults to AWSCURRENT if unspecified VersionStage: "AWSCURRENT" // VersionStage defaults to AWSCURRENT if unspecified
}) })
); );
return JSON.parse(secret.SecretString); return JSON.parse(secret.SecretString);
} catch (error) { } catch (error) {
return { return {
error: error.message, error: error.message
}; };
} }
} }
}; };
exports.lightbox_credentials = async (req, res) => { exports.lightbox_credentials = async (req, res) => {
logger.log( logger.log("intellipay-lightbox-credentials", "DEBUG", req.user?.email, null, null);
"intellipay-lightbox-credentials",
"DEBUG",
req.user?.email,
null,
null
);
const shopCredentials = await getShopCredentials(req.body.bodyshop); const shopCredentials = await getShopCredentials(req.body.bodyshop);
@@ -75,11 +63,9 @@ exports.lightbox_credentials = async (req, res) => {
headers: { "content-type": "application/x-www-form-urlencoded" }, headers: { "content-type": "application/x-www-form-urlencoded" },
data: qs.stringify({ data: qs.stringify({
...shopCredentials, ...shopCredentials,
operatingenv: "businessattended", operatingenv: "businessattended"
}), }),
url: `https://${domain}.cpteller.com/api/custapi.cfc?method=autoterminal${ url: `https://${domain}.cpteller.com/api/custapi.cfc?method=autoterminal${req.body.refresh ? "_refresh" : ""}` //autoterminal_refresh
req.body.refresh ? "_refresh" : ""
}`, //autoterminal_refresh
}; };
const response = await axios(options); const response = await axios(options);
@@ -87,13 +73,9 @@ exports.lightbox_credentials = async (req, res) => {
res.send(response.data); res.send(response.data);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
logger.log( logger.log("intellipay-lightbox-credentials-error", "ERROR", req.user?.email, null, {
"intellipay-lightbox-credentials-error", error: JSON.stringify(error)
"ERROR", });
req.user?.email,
null,
{ error: JSON.stringify(error) }
);
res.json({ error }); res.json({ error });
} }
}; };
@@ -112,9 +94,9 @@ exports.payment_refund = async (req, res) => {
method: "payment_refund", method: "payment_refund",
...shopCredentials, ...shopCredentials,
paymentid: req.body.paymentid, paymentid: req.body.paymentid,
amount: req.body.amount, amount: req.body.amount
}), }),
url: `https://${domain}.cpteller.com/api/26/webapi.cfc?method=payment_refund`, url: `https://${domain}.cpteller.com/api/26/webapi.cfc?method=payment_refund`
}; };
const response = await axios(options); const response = await axios(options);
@@ -123,7 +105,7 @@ exports.payment_refund = async (req, res) => {
} catch (error) { } catch (error) {
console.log(error); console.log(error);
logger.log("intellipay-refund-error", "ERROR", req.user?.email, null, { logger.log("intellipay-refund-error", "ERROR", req.user?.email, null, {
error: JSON.stringify(error), error: JSON.stringify(error)
}); });
res.json({ error }); res.json({ error });
} }
@@ -141,15 +123,13 @@ exports.generate_payment_url = async (req, res) => {
data: qs.stringify({ data: qs.stringify({
...shopCredentials, ...shopCredentials,
//...req.body, //...req.body,
amount: Dinero({ amount: Math.round(req.body.amount * 100) }).toFormat( amount: Dinero({ amount: Math.round(req.body.amount * 100) }).toFormat("0.00"),
"0.00"
),
account: req.body.account, account: req.body.account,
invoice: req.body.invoice, invoice: req.body.invoice,
createshorturl: true, createshorturl: true
//The postback URL is set at the CP teller global terminal settings page. //The postback URL is set at the CP teller global terminal settings page.
}), }),
url: `https://${domain}.cpteller.com/api/custapi.cfc?method=generate_lightbox_url`, url: `https://${domain}.cpteller.com/api/custapi.cfc?method=generate_lightbox_url`
}; };
const response = await axios(options); const response = await axios(options);
@@ -158,56 +138,100 @@ exports.generate_payment_url = async (req, res) => {
} catch (error) { } catch (error) {
console.log(error); console.log(error);
logger.log("intellipay-payment-url-error", "ERROR", req.user?.email, null, { logger.log("intellipay-payment-url-error", "ERROR", req.user?.email, null, {
error: JSON.stringify(error), error: JSON.stringify(error)
}); });
res.json({ error }); res.json({ error });
} }
}; };
exports.postback = async (req, res) => { exports.postback = async (req, res) => {
logger.log("intellipay-postback", "ERROR", req.user?.email, null, req.body); logger.log("intellipay-postback", "DEBUG", req.user?.email, null, req.body);
const { body: values } = req; const { body: values } = req;
if (!values.invoice) { const comment = Buffer.from(values?.comment, "base64").toString();
if ((!values.invoice || values.invoice === "") && !comment) {
//invoice is specified through the pay link. Comment by IO.
logger.log("intellipay-postback-ignored", "DEBUG", req.user?.email, null, req.body);
res.sendStatus(200); res.sendStatus(200);
return; return;
} }
// TODO query job by account name
const job = await gqlClient.request(queries.GET_JOB_BY_PK, {
id: values.invoice,
});
// TODO add mutation to database
try { try {
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, { if (values.invoice) {
paymentInput: { //This is a link email that's been sent out.
amount: values.total, const job = await gqlClient.request(queries.GET_JOB_BY_PK, {
transactionid: `C00 ${values.authcode}`, id: values.invoice
payer: "Customer", });
type: values.cardtype,
jobid: values.invoice,
date: moment(Date.now()),
},
});
await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, { const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
paymentResponse: { paymentInput: {
amount: values.total, amount: values.total,
bodyshopid: job.jobs_by_pk.shopid, transactionid: values.authcode,
paymentid: paymentResult.id, payer: "Customer",
jobid: values.invoice, type: values.cardtype,
declinereason: "Approved", jobid: values.invoice,
ext_paymentid: values.paymentid, date: moment(Date.now())
successful: true, }
response: values, });
},
});
res.send({ message: "Postback Successful" }); const responseResults = await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, {
paymentResponse: {
amount: values.total,
bodyshopid: job.jobs_by_pk.shopid,
paymentid: paymentResult.id,
jobid: values.invoice,
declinereason: "Approved",
ext_paymentid: values.paymentid,
successful: true,
response: values
}
});
logger.log("intellipay-postback-link-success", "DEBUG", req.user?.email, null, {
iprequest: values,
responseResults,
paymentResult
});
res.sendStatus(200);
} else if (comment) {
//This has been triggered by IO and may have multiple jobs.
const partialPayments = JSON.parse(comment);
const jobs = await gqlClient.request(queries.GET_JOBS_BY_PKS, {
ids: partialPayments.map((p) => p.jobid)
});
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
paymentInput: partialPayments.map((p) => ({
amount: p.amount,
transactionid: values.authcode,
payer: "Customer",
type: values.cardtype,
jobid: p.jobid,
date: moment(Date.now()),
payment_responses: {
data: {
amount: values.total,
bodyshopid: jobs.jobs[0].shopid,
jobid: p.jobid,
declinereason: "Approved",
ext_paymentid: values.paymentid,
successful: true,
response: values
}
}
}))
});
logger.log("intellipay-postback-app-success", "DEBUG", req.user?.email, null, {
iprequest: values,
paymentResult
});
res.sendStatus(200);
}
} catch (error) { } catch (error) {
logger.log("intellipay-postback-error", "ERROR", req.user?.email, null, { logger.log("intellipay-postback-error", "ERROR", req.user?.email, null, {
error: JSON.stringify(error), error: JSON.stringify(error),
body: req.body, body: req.body
}); });
res.status(400).json({ succesful: false, error: error.message }); res.status(400).json({ succesful: false, error: error.message });
} }