diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel
index 73f7047a6..7ca06523b 100644
--- a/bodyshop_translations.babel
+++ b/bodyshop_translations.babel
@@ -14310,6 +14310,27 @@
+
+ nofeatureaccess
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
noshop
false
diff --git a/client/src/components/feature-wrapper/feature-wrapper.component.jsx b/client/src/components/feature-wrapper/feature-wrapper.component.jsx
new file mode 100644
index 000000000..af609ac1f
--- /dev/null
+++ b/client/src/components/feature-wrapper/feature-wrapper.component.jsx
@@ -0,0 +1,50 @@
+import moment from "moment";
+import React from "react";
+import { useTranslation } from "react-i18next";
+import { connect } from "react-redux";
+import { createStructuredSelector } from "reselect";
+import { selectBodyshop } from "../../redux/user/user.selectors";
+import AlertComponent from "../alert/alert.component";
+
+const mapStateToProps = createStructuredSelector({
+ bodyshop: selectBodyshop,
+});
+
+function FeatureWrapper({
+ bodyshop,
+ featureName,
+ noauth,
+ children,
+ ...restProps
+}) {
+ const { t } = useTranslation();
+
+ if (HasFeatureAccess({ featureName, bodyshop })) return children;
+
+ return (
+ noauth || (
+
+ )
+ );
+}
+
+export function HasFeatureAccess({ featureName, bodyshop }) {
+ return (
+ bodyshop.features.allAccess ||
+ moment(bodyshop.features[featureName]).isAfter(moment())
+ );
+}
+
+export default connect(mapStateToProps, null)(FeatureWrapper);
+
+/*
+dashboard
+production-board
+scoreboard
+csi
+tech-console
+mobile-imaging
+*/
diff --git a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.csi.component.jsx b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.csi.component.jsx
index 9cecb5d22..c205e3088 100644
--- a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.csi.component.jsx
+++ b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.csi.component.jsx
@@ -19,6 +19,7 @@ import {
import { selectBodyshop } from "../../redux/user/user.selectors";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants";
+import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser'
@@ -179,6 +180,8 @@ export function JobsDetailHeaderCsi({
}
};
+ if (!HasFeatureAccess({ featureName: "csi", bodyshop })) return <>>;
+
return (
({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -28,9 +29,11 @@ export function ExportsLogPageContainer({ setBreadcrumbs, setSelectedHeader }) {
}, [setBreadcrumbs, t, setSelectedHeader]);
return (
-
-
-
+
+
+
+
+
);
}
export default connect(null, mapDispatchToProps)(ExportsLogPageContainer);
diff --git a/client/src/pages/production-board/production-board.container.jsx b/client/src/pages/production-board/production-board.container.jsx
index c99a9f26a..20fc35d51 100644
--- a/client/src/pages/production-board/production-board.container.jsx
+++ b/client/src/pages/production-board/production-board.container.jsx
@@ -9,6 +9,7 @@ import {
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import ProductionBoardComponent from "./production-board.component";
+import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -38,9 +39,11 @@ export function ProductionBoardContainer({
}, [t, setBreadcrumbs, setSelectedHeader]);
return (
-
-
-
+
+
+
+
+
);
}
export default connect(
diff --git a/client/src/pages/scoreboard/scoreboard.page.container.jsx b/client/src/pages/scoreboard/scoreboard.page.container.jsx
index 3f17416aa..2ded23396 100644
--- a/client/src/pages/scoreboard/scoreboard.page.container.jsx
+++ b/client/src/pages/scoreboard/scoreboard.page.container.jsx
@@ -12,6 +12,7 @@ import { useSubscription } from "@apollo/client";
import { SUBSCRIPTION_SCOREBOARD } from "../../graphql/scoreboard.queries";
import moment from "moment";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
+import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -44,9 +45,11 @@ export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
}, [t, setBreadcrumbs, setSelectedHeader]);
return (
-
-
-
+
+
+
+
+
);
}
export default connect(
diff --git a/client/src/pages/tech/tech.page.component.jsx b/client/src/pages/tech/tech.page.component.jsx
index 95c5c8138..ef00da57e 100644
--- a/client/src/pages/tech/tech.page.component.jsx
+++ b/client/src/pages/tech/tech.page.component.jsx
@@ -10,6 +10,7 @@ import LoadingSpinner from "../../components/loading-spinner/loading-spinner.com
import TechHeader from "../../components/tech-header/tech-header.component";
import TechSider from "../../components/tech-sider/tech-sider.component";
import { selectTechnician } from "../../redux/tech/tech.selectors";
+import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
import "./tech.page.styles.scss";
const TimeTicketModalContainer = lazy(() =>
import("../../components/time-ticket-modal/time-ticket-modal.container")
@@ -51,52 +52,55 @@ export function TechPage({ technician, match }) {
}, [t]);
return (
-
+
{technician ? null : }
-
+
- }>
-
-
-
-
-
-
-
-
-
-
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/pages/tech/tech.page.container.jsx b/client/src/pages/tech/tech.page.container.jsx
index de48e041d..5fb547cd4 100644
--- a/client/src/pages/tech/tech.page.container.jsx
+++ b/client/src/pages/tech/tech.page.container.jsx
@@ -7,12 +7,18 @@ import { setBodyshop } from "../../redux/user/user.actions";
import TechPage from "./tech.page.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import { useTranslation } from "react-i18next";
+import { selectBodyshop } from "../../redux/user/user.selectors";
+import { createStructuredSelector } from "reselect";
+
+const mapStateToProps = createStructuredSelector({
+ bodyshop: selectBodyshop,
+});
const mapDispatchToProps = (dispatch) => ({
setBodyshop: (bs) => dispatch(setBodyshop(bs)),
});
-export function TechPageContainer({ setBodyshop, match }) {
+export function TechPageContainer({ bodyshop, setBodyshop, match }) {
const { loading, error, data } = useQuery(QUERY_BODYSHOP, {
fetchPolicy: "network-only",
});
@@ -21,10 +27,10 @@ export function TechPageContainer({ setBodyshop, match }) {
if (data) setBodyshop(data.bodyshops[0]);
}, [data, setBodyshop]);
- if (loading)
+ if (loading || !bodyshop)
return ;
if (error) return ;
return ;
}
-export default connect(null, mapDispatchToProps)(TechPageContainer);
+export default connect(mapStateToProps, mapDispatchToProps)(TechPageContainer);
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index d0cd83a08..2c4ad3f22 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -899,6 +899,7 @@
"newversionmessage": "Click refresh below to update to the latest available version of ImEX Online. Please make sure all other tabs and windows are closed.",
"newversiontitle": "New version of ImEX Online Available",
"noacctfilepath": "There is no accounting file path set. You will not be able to export any items.",
+ "nofeatureaccess": "You do not have access to this feature of ImEX Online. Please contact support to request a license for this feature.",
"noshop": "You do not have access to any shops. Please reach out to your shop manager or technical support. ",
"notfoundsub": "Please make sure that you have access to the data or that the link is correct.",
"notfoundtitle": "We couldn't find what you're looking for...",
diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json
index 223b5772f..d2004c724 100644
--- a/client/src/translations/es/common.json
+++ b/client/src/translations/es/common.json
@@ -899,6 +899,7 @@
"newversionmessage": "",
"newversiontitle": "",
"noacctfilepath": "",
+ "nofeatureaccess": "",
"noshop": "",
"notfoundsub": "",
"notfoundtitle": "",
diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json
index 08efbf140..16733c621 100644
--- a/client/src/translations/fr/common.json
+++ b/client/src/translations/fr/common.json
@@ -899,6 +899,7 @@
"newversionmessage": "",
"newversiontitle": "",
"noacctfilepath": "",
+ "nofeatureaccess": "",
"noshop": "",
"notfoundsub": "",
"notfoundtitle": "",
diff --git a/hasura/migrations/1624480412524_track_all_relationships/down.yaml b/hasura/migrations/1624480412524_track_all_relationships/down.yaml
new file mode 100644
index 000000000..af1251f44
--- /dev/null
+++ b/hasura/migrations/1624480412524_track_all_relationships/down.yaml
@@ -0,0 +1,12 @@
+- args:
+ relationship: partsOrdersByOrderedby
+ table:
+ name: users
+ schema: public
+ type: drop_relationship
+- args:
+ relationship: userByOrderedby
+ table:
+ name: parts_orders
+ schema: public
+ type: drop_relationship
diff --git a/hasura/migrations/1624480412524_track_all_relationships/up.yaml b/hasura/migrations/1624480412524_track_all_relationships/up.yaml
new file mode 100644
index 000000000..b03f180ad
--- /dev/null
+++ b/hasura/migrations/1624480412524_track_all_relationships/up.yaml
@@ -0,0 +1,20 @@
+- args:
+ name: partsOrdersByOrderedby
+ table:
+ name: users
+ schema: public
+ using:
+ foreign_key_constraint_on:
+ column: orderedby
+ table:
+ name: parts_orders
+ schema: public
+ type: create_array_relationship
+- args:
+ name: userByOrderedby
+ table:
+ name: parts_orders
+ schema: public
+ using:
+ foreign_key_constraint_on: orderedby
+ type: create_object_relationship
diff --git a/hasura/migrations/1624480467391_alter_table_public_bodyshops_add_column_features/down.yaml b/hasura/migrations/1624480467391_alter_table_public_bodyshops_add_column_features/down.yaml
new file mode 100644
index 000000000..d53fc1675
--- /dev/null
+++ b/hasura/migrations/1624480467391_alter_table_public_bodyshops_add_column_features/down.yaml
@@ -0,0 +1,5 @@
+- args:
+ cascade: false
+ read_only: false
+ sql: ALTER TABLE "public"."bodyshops" DROP COLUMN "features";
+ type: run_sql
diff --git a/hasura/migrations/1624480467391_alter_table_public_bodyshops_add_column_features/up.yaml b/hasura/migrations/1624480467391_alter_table_public_bodyshops_add_column_features/up.yaml
new file mode 100644
index 000000000..68869f6a8
--- /dev/null
+++ b/hasura/migrations/1624480467391_alter_table_public_bodyshops_add_column_features/up.yaml
@@ -0,0 +1,6 @@
+- args:
+ cascade: false
+ read_only: false
+ sql: ALTER TABLE "public"."bodyshops" ADD COLUMN "features" jsonb NULL DEFAULT
+ jsonb_build_object();
+ type: run_sql
diff --git a/hasura/migrations/1624480496210_update_permission_user_public_table_bodyshops/down.yaml b/hasura/migrations/1624480496210_update_permission_user_public_table_bodyshops/down.yaml
new file mode 100644
index 000000000..330d7d1af
--- /dev/null
+++ b/hasura/migrations/1624480496210_update_permission_user_public_table_bodyshops/down.yaml
@@ -0,0 +1,86 @@
+- args:
+ role: user
+ table:
+ name: bodyshops
+ schema: public
+ type: drop_select_permission
+- args:
+ permission:
+ allow_aggregations: false
+ columns:
+ - accountingconfig
+ - address1
+ - address2
+ - appt_alt_transport
+ - appt_colors
+ - appt_length
+ - bill_tax_rates
+ - cdk_dealerid
+ - city
+ - country
+ - created_at
+ - default_adjustment_rate
+ - deliverchecklist
+ - email
+ - enforce_class
+ - enforce_referral
+ - federal_tax_id
+ - id
+ - imexshopid
+ - inhousevendorid
+ - insurance_vendor_id
+ - intakechecklist
+ - jc_hourly_rates
+ - jobsizelimit
+ - logo_img_path
+ - md_categories
+ - md_ccc_rates
+ - md_classes
+ - md_hour_split
+ - md_ins_cos
+ - md_jobline_presets
+ - md_labor_rates
+ - md_messaging_presets
+ - md_notes_presets
+ - md_order_statuses
+ - md_parts_locations
+ - md_payment_types
+ - md_rbac
+ - md_referral_sources
+ - md_responsibility_centers
+ - md_ro_statuses
+ - messagingservicesid
+ - phone
+ - prodtargethrs
+ - production_config
+ - region_config
+ - schedule_end_time
+ - schedule_start_time
+ - scoreboard_target
+ - shopname
+ - shoprates
+ - speedprint
+ - ssbuckets
+ - state
+ - state_tax_id
+ - stripe_acct_id
+ - sub_status
+ - target_touchtime
+ - template_header
+ - textid
+ - updated_at
+ - use_fippa
+ - website
+ - workingdays
+ - zip_post
+ computed_fields: []
+ filter:
+ associations:
+ user:
+ authid:
+ _eq: X-Hasura-User-Id
+ role: user
+ table:
+ name: bodyshops
+ schema: public
+ type: create_select_permission
diff --git a/hasura/migrations/1624480496210_update_permission_user_public_table_bodyshops/up.yaml b/hasura/migrations/1624480496210_update_permission_user_public_table_bodyshops/up.yaml
new file mode 100644
index 000000000..24984ef56
--- /dev/null
+++ b/hasura/migrations/1624480496210_update_permission_user_public_table_bodyshops/up.yaml
@@ -0,0 +1,87 @@
+- args:
+ role: user
+ table:
+ name: bodyshops
+ schema: public
+ type: drop_select_permission
+- args:
+ permission:
+ allow_aggregations: false
+ columns:
+ - accountingconfig
+ - address1
+ - address2
+ - appt_alt_transport
+ - appt_colors
+ - appt_length
+ - bill_tax_rates
+ - cdk_dealerid
+ - city
+ - country
+ - created_at
+ - default_adjustment_rate
+ - deliverchecklist
+ - email
+ - enforce_class
+ - enforce_referral
+ - features
+ - federal_tax_id
+ - id
+ - imexshopid
+ - inhousevendorid
+ - insurance_vendor_id
+ - intakechecklist
+ - jc_hourly_rates
+ - jobsizelimit
+ - logo_img_path
+ - md_categories
+ - md_ccc_rates
+ - md_classes
+ - md_hour_split
+ - md_ins_cos
+ - md_jobline_presets
+ - md_labor_rates
+ - md_messaging_presets
+ - md_notes_presets
+ - md_order_statuses
+ - md_parts_locations
+ - md_payment_types
+ - md_rbac
+ - md_referral_sources
+ - md_responsibility_centers
+ - md_ro_statuses
+ - messagingservicesid
+ - phone
+ - prodtargethrs
+ - production_config
+ - region_config
+ - schedule_end_time
+ - schedule_start_time
+ - scoreboard_target
+ - shopname
+ - shoprates
+ - speedprint
+ - ssbuckets
+ - state
+ - state_tax_id
+ - stripe_acct_id
+ - sub_status
+ - target_touchtime
+ - template_header
+ - textid
+ - updated_at
+ - use_fippa
+ - website
+ - workingdays
+ - zip_post
+ computed_fields: []
+ filter:
+ associations:
+ user:
+ authid:
+ _eq: X-Hasura-User-Id
+ role: user
+ table:
+ name: bodyshops
+ schema: public
+ type: create_select_permission
diff --git a/hasura/migrations/metadata.yaml b/hasura/migrations/metadata.yaml
index 3a4f43614..3481eec42 100644
--- a/hasura/migrations/metadata.yaml
+++ b/hasura/migrations/metadata.yaml
@@ -766,6 +766,7 @@ tables:
- email
- enforce_class
- enforce_referral
+ - features
- federal_tax_id
- id
- imexshopid
@@ -3596,6 +3597,9 @@ tables:
- name: user
using:
foreign_key_constraint_on: user_email
+ - name: userByOrderedby
+ using:
+ foreign_key_constraint_on: orderedby
- name: vendor
using:
foreign_key_constraint_on: vendorid
@@ -4172,6 +4176,13 @@ tables:
table:
schema: public
name: parts_orders
+ - name: partsOrdersByOrderedby
+ using:
+ foreign_key_constraint_on:
+ column: orderedby
+ table:
+ schema: public
+ name: parts_orders
insert_permissions:
- role: user
permission: