- {(fields, {add, remove}) => {
+ {(fields, {add, remove, move}) => {
return (
{fields.map((field, index) => (
diff --git a/client/src/components/vehicle-detail-jobs/vehicle-detail-jobs.component.jsx b/client/src/components/vehicle-detail-jobs/vehicle-detail-jobs.component.jsx
index 4237ade41..6fe616d09 100644
--- a/client/src/components/vehicle-detail-jobs/vehicle-detail-jobs.component.jsx
+++ b/client/src/components/vehicle-detail-jobs/vehicle-detail-jobs.component.jsx
@@ -6,8 +6,10 @@ import {Link} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import {selectBodyshop} from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
-import {alphaSort, statusSort} from "../../utils/sorters";
-import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
+import { alphaSort, statusSort } from "../../utils/sorters";
+import OwnerNameDisplay, {
+ OwnerNameDisplayFunction,
+} from "../owner-name-display/owner-name-display.component";
import VehicleDetailUpdateJobsComponent from "../vehicle-detail-update-jobs/vehicle-detail-update-jobs.component";
const mapStateToProps = createStructuredSelector({
@@ -45,6 +47,10 @@ export function VehicleDetailJobsComponent({vehicle, bodyshop}) {
title: t("jobs.fields.owner"),
dataIndex: "owner",
key: "owner",
+ sorter: (a, b) =>
+ alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
+ sortOrder:
+ state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
render: (text, record) => (
@@ -63,9 +69,15 @@ export function VehicleDetailJobsComponent({vehicle, bodyshop}) {
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status",
- sorter: (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses),
+ sorter: (a, b) =>
+ statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses),
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
+ filters: bodyshop.md_ro_statuses.statuses.map((status) => ({
+ text: status,
+ value: status,
+ })),
+ onFilter: (value, record) => value.includes(record.status),
},
{
diff --git a/client/src/components/vehicles-list/vehicles-list.component.jsx b/client/src/components/vehicles-list/vehicles-list.component.jsx
index 82c1123a4..a5f8bf982 100644
--- a/client/src/components/vehicles-list/vehicles-list.component.jsx
+++ b/client/src/components/vehicles-list/vehicles-list.component.jsx
@@ -6,6 +6,7 @@ import {useTranslation} from "react-i18next";
import {Link, useLocation, useNavigate} from "react-router-dom";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
import {pageLimit} from "../../utils/config";
+import { alphaSort } from '../../utils/sorters';
export default function VehiclesListComponent({
loading,
@@ -32,6 +33,8 @@ export default function VehiclesListComponent({
title: t("vehicles.fields.v_vin"),
dataIndex: "v_vin",
key: "v_vin",
+ sorter: (a, b) => alphaSort(a.v_vin, b.v_vin),
+ sortOrder: state.sortedInfo.columnKey === "v_vin" && state.sortedInfo.order,
render: (text, record) => (
{record.v_vin || "N/A"}
@@ -52,8 +55,10 @@ export default function VehiclesListComponent({
},
{
title: t("vehicles.fields.plate_no"),
- dataIndex: "plate",
- key: "plate",
+ dataIndex: "plate_no",
+ key: "plate_no",
+ sorter: (a, b) => alphaSort(a.plate_no, b.plate_no),
+ sortOrder: state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order,
render: (text, record) => {
return (
{`${record.plate_st || ""} | ${record.plate_no || ""}`}
diff --git a/client/src/firebase/firebase.utils.js b/client/src/firebase/firebase.utils.js
index 1d13a2312..0d6ba4708 100644
--- a/client/src/firebase/firebase.utils.js
+++ b/client/src/firebase/firebase.utils.js
@@ -4,6 +4,8 @@ import {getAuth, updatePassword, updateProfile} from "firebase/auth";
import {getFirestore} from "firebase/firestore";
import {getMessaging, getToken, onMessage} from "firebase/messaging";
import {store} from "../redux/store";
+import axios from "axios";
+import { checkBeta } from "../utils/handleBeta";
const config = JSON.parse(process.env.REACT_APP_FIREBASE_CONFIG);
initializeApp(config);
@@ -86,6 +88,17 @@ export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
null,
...additionalParams,
};
+ axios.post("/ioevent", {
+ useremail:
+ (state.user && state.user.currentUser && state.user.currentUser.email) ||
+ null,
+ bodyshopid:
+ (state.user && state.user.bodyshop && state.user.bodyshop.id) || null,
+ operationName: eventName,
+ variables: additionalParams,
+ dbevent: false,
+ env: checkBeta() ? "beta" : "master",
+ });
// console.log(
// "%c[Analytics]",
// "background-color: green ;font-weight:bold;",
diff --git a/client/src/graphql/vehicles.queries.js b/client/src/graphql/vehicles.queries.js
index 742c4de36..32d970655 100644
--- a/client/src/graphql/vehicles.queries.js
+++ b/client/src/graphql/vehicles.queries.js
@@ -1,47 +1,48 @@
import {gql} from "@apollo/client";
export const QUERY_VEHICLE_BY_ID = gql`
- query QUERY_VEHICLE_BY_ID($id: uuid!) {
- vehicles_by_pk(id: $id) {
- created_at
- db_v_code
- id
- plate_no
- plate_st
- v_vin
- v_type
- v_trimcode
- v_tone
- v_stage
- v_prod_dt
- v_paint_codes
- v_options
- v_model_yr
- v_model_desc
- v_mldgcode
- v_makecode
- v_make_desc
- v_engine
- v_cond
- v_color
- v_bstyle
- updated_at
- trim_color
- notes
- jobs(order_by: { date_open: desc }) {
- id
- ro_number
- ownr_fn
- ownr_ln
- owner {
- id
- }
- clm_no
- status
- clm_total
- }
+ query QUERY_VEHICLE_BY_ID($id: uuid!) {
+ vehicles_by_pk(id: $id) {
+ created_at
+ db_v_code
+ id
+ plate_no
+ plate_st
+ v_vin
+ v_type
+ v_trimcode
+ v_tone
+ v_stage
+ v_prod_dt
+ v_paint_codes
+ v_options
+ v_model_yr
+ v_model_desc
+ v_mldgcode
+ v_makecode
+ v_make_desc
+ v_engine
+ v_cond
+ v_color
+ v_bstyle
+ updated_at
+ trim_color
+ notes
+ jobs(order_by: { date_open: desc }) {
+ id
+ ro_number
+ ownr_co_nm
+ ownr_fn
+ ownr_ln
+ owner {
+ id
}
+ clm_no
+ status
+ clm_total
+ }
}
+ }
`;
export const UPDATE_VEHICLE = gql`
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index ddd7dbc11..204d3de15 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -257,6 +257,7 @@
"saving": "Error encountered while saving. {{message}}"
},
"fields": {
+ "ReceivableCustomField": "QBO Receivable Custom Field {{number}}",
"address1": "Address 1",
"address2": "Address 2",
"appt_alt_transport": "Appointment Alternative Transportation Options",
@@ -475,7 +476,7 @@
"editaccess": "Users -> Edit access"
}
},
- "ReceivableCustomField": "QBO Receivable Custom Field {{number}}",
+
"responsibilitycenter": "Responsibility Center",
"responsibilitycenter_accountdesc": "Account Description",
"responsibilitycenter_accountitem": "Item",
@@ -606,7 +607,7 @@
"dms": {
"cdk": {
"controllist": "Control Number List",
- "payers": "CDK Payers"
+ "payers": " Payers"
},
"cdk_dealerid": "CDK Dealer ID",
"pbs_serialnumber": "PBS Serial Number",
@@ -842,8 +843,8 @@
"notconfigured": "You do not have any current CSI Question Sets configured.",
"notfoundsubtitle": "We were unable to find a survey using the link you provided. Please ensure the URL is correct or reach out to your shop for more help.",
"notfoundtitle": "No survey found.",
- "surveycompletetitle": "Survey previously completed",
- "surveycompletesubtitle": "This survey was already completed on {{date}}."
+ "surveycompletesubtitle": "This survey was already completed on {{date}}.",
+ "surveycompletetitle": "Survey previously completed"
},
"fields": {
"completedon": "Completed On",
@@ -852,13 +853,13 @@
"validuntil": "Valid Until"
},
"labels": {
+ "copyright": "Copyright © $t(titles.app). All Rights Reserved.",
+ "greeting": "Hi {{name}}!",
+ "intro": "At {{shopname}}, we value your feedback. We would love to hear what you have to say. Please fill out the form below.",
"nologgedinuser": "Please log out of $t(titles.app)",
"nologgedinuser_sub": "Users of $t(titles.app) cannot complete CSI surveys while logged in. Please log out and try again.",
"noneselected": "No response selected.",
- "title": "Customer Satisfaction Survey",
- "greeting": "Hi {{name}}!",
- "intro": "At {{shopname}}, we value your feedback. We would love to hear what you have to say. Please fill out the form below.",
- "copyright": "Copyright © $t(titles.app). All Rights Reserved."
+ "title": "Customer Satisfaction Survey"
},
"successes": {
"created": "CSI created successfully. ",
@@ -896,7 +897,8 @@
"scheduledindate": "Sheduled In Today: {{date}}",
"scheduledintoday": "Sheduled In Today",
"scheduledoutdate": "Sheduled Out Today: {{date}}",
- "scheduledouttoday": "Sheduled Out Today"
+ "scheduledouttoday": "Sheduled Out Today",
+ "joblifecycle": "Job Lifecycle"
}
},
"dms": {
@@ -1269,7 +1271,15 @@
"relative_end": "Relative End",
"relative_start": "Relative Start",
"start": "Start",
- "value": "Value"
+ "value": "Value",
+ "status": "Status",
+ "percentage": "Percentage",
+ "human_readable": "Human Readable",
+ "status_count": "In Status"
+ },
+ "titles": {
+ "dashboard": "Job Lifecycle",
+ "top_durations": "Top Durations"
},
"content": {
"current_status_accumulated_time": "Current Status Accumulated Time",
@@ -1281,7 +1291,9 @@
"title": "Job Lifecycle Component",
"title_durations": "Historical Status Durations",
"title_loading": "Loading",
- "title_transitions": "Transitions"
+ "title_transitions": "Transitions",
+ "calculated_based_on": "Calculated based on",
+ "jobs_in_since": "Jobs in since"
},
"errors": {
"fetch": "Error getting Job Lifecycle Data"
@@ -1856,6 +1868,7 @@
"job": "Job Details",
"jobcosting": "Job Costing",
"jobtotals": "Job Totals",
+ "labor_hrs": "B/P/T Hrs",
"labor_rates_subtotal": "Labor Rates Subtotal",
"laborallocations": "Labor Allocations",
"labortotals": "Labor Totals",
@@ -2450,6 +2463,7 @@
"invoice_total_payable": "Invoice (Total Payable)",
"iou_form": "IOU Form",
"job_costing_ro": "Job Costing",
+ "job_lifecycle_ro": "Job Lifecycle",
"job_notes": "Job Notes",
"key_tag": "Key Tag",
"labels": {
@@ -2616,17 +2630,17 @@
},
"labels": {
"advanced_filters": "Advanced Filters and Sorters",
- "advanced_filters_show": "Show",
- "advanced_filters_hide": "Hide",
- "advanced_filters_filters": "Filters",
- "advanced_filters_sorters": "Sorters",
- "advanced_filters_filter_field": "Field",
- "advanced_filters_sorter_field": "Field",
- "advanced_filters_true": "True",
"advanced_filters_false": "False",
- "advanced_filters_sorter_direction": "Direction",
+ "advanced_filters_filter_field": "Field",
"advanced_filters_filter_operator": "Operator",
"advanced_filters_filter_value": "Value",
+ "advanced_filters_filters": "Filters",
+ "advanced_filters_hide": "Hide",
+ "advanced_filters_show": "Show",
+ "advanced_filters_sorter_direction": "Direction",
+ "advanced_filters_sorter_field": "Field",
+ "advanced_filters_sorters": "Sorters",
+ "advanced_filters_true": "True",
"dates": "Dates",
"employee": "Employee",
"filterson": "Filters on {{object}}: {{field}}",
@@ -2708,6 +2722,8 @@
"job_costing_ro_date_summary": "Job Costing by RO - Summary",
"job_costing_ro_estimator": "Job Costing by Estimator",
"job_costing_ro_ins_co": "Job Costing by RO Source",
+ "job_lifecycle_date_detail": "Job Lifecycle by Date - Detail",
+ "job_lifecycle_date_summary": "Job Lifecycle by Date - Summary",
"jobs_completed_not_invoiced": "Jobs Completed not Invoiced",
"jobs_invoiced_not_exported": "Jobs Invoiced not Exported",
"jobs_reconcile": "Parts/Sublet/Labor Reconciliation",
diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json
index ea403b6ed..8a779eae9 100644
--- a/client/src/translations/es/common.json
+++ b/client/src/translations/es/common.json
@@ -258,6 +258,7 @@
"saving": ""
},
"fields": {
+ "ReceivableCustomField": "",
"address1": "",
"address2": "",
"appt_alt_transport": "",
@@ -476,7 +477,7 @@
"editaccess": ""
}
},
- "ReceivableCustomField": "",
+
"responsibilitycenter": "",
"responsibilitycenter_accountdesc": "",
"responsibilitycenter_accountitem": "",
@@ -843,8 +844,8 @@
"notconfigured": "",
"notfoundsubtitle": "",
"notfoundtitle": "",
- "surveycompletetitle": "",
- "surveycompletesubtitle": ""
+ "surveycompletesubtitle": "",
+ "surveycompletetitle": ""
},
"fields": {
"completedon": "",
@@ -853,13 +854,13 @@
"validuntil": ""
},
"labels": {
+ "copyright": "",
+ "greeting": "",
+ "intro": "",
"nologgedinuser": "",
"nologgedinuser_sub": "",
"noneselected": "",
- "title": "",
- "greeting": "",
- "intro": "",
- "copyright": ""
+ "title": ""
},
"successes": {
"created": "",
@@ -897,7 +898,8 @@
"scheduledindate": "",
"scheduledintoday": "",
"scheduledoutdate": "",
- "scheduledouttoday": ""
+ "scheduledouttoday": "",
+ "joblifecycle": ""
}
},
"dms": {
@@ -1270,7 +1272,15 @@
"relative_end": "",
"relative_start": "",
"start": "",
- "value": ""
+ "value": "",
+ "status": "",
+ "percentage": "",
+ "human_readable": "",
+ "status_count": ""
+ },
+ "titles": {
+ "dashboard": "",
+ "top_durations": ""
},
"content": {
"current_status_accumulated_time": "",
@@ -1282,7 +1292,9 @@
"title": "",
"title_durations": "",
"title_loading": "",
- "title_transitions": ""
+ "title_transitions": "",
+ "calculated_based_on": "",
+ "jobs_in_since": ""
},
"errors": {
"fetch": "Error al obtener los datos del ciclo de vida del trabajo"
@@ -1857,6 +1869,7 @@
"job": "",
"jobcosting": "",
"jobtotals": "",
+ "labor_hrs": "",
"labor_rates_subtotal": "",
"laborallocations": "",
"labortotals": "",
@@ -2451,6 +2464,7 @@
"invoice_total_payable": "",
"iou_form": "",
"job_costing_ro": "",
+ "job_lifecycle_ro": "",
"job_notes": "",
"key_tag": "",
"labels": {
@@ -2617,17 +2631,17 @@
},
"labels": {
"advanced_filters": "",
- "advanced_filters_show": "",
- "advanced_filters_hide": "",
- "advanced_filters_filters": "",
- "advanced_filters_sorters": "",
- "advanced_filters_filter_field": "",
- "advanced_filters_sorter_field": "",
- "advanced_filters_true": "",
"advanced_filters_false": "",
- "advanced_filters_sorter_direction": "",
+ "advanced_filters_filter_field": "",
"advanced_filters_filter_operator": "",
"advanced_filters_filter_value": "",
+ "advanced_filters_filters": "",
+ "advanced_filters_hide": "",
+ "advanced_filters_show": "",
+ "advanced_filters_sorter_direction": "",
+ "advanced_filters_sorter_field": "",
+ "advanced_filters_sorters": "",
+ "advanced_filters_true": "",
"dates": "",
"employee": "",
"filterson": "",
@@ -2709,6 +2723,8 @@
"job_costing_ro_date_summary": "",
"job_costing_ro_estimator": "",
"job_costing_ro_ins_co": "",
+ "job_lifecycle_date_detail": "",
+ "job_lifecycle_date_summary": "",
"jobs_completed_not_invoiced": "",
"jobs_invoiced_not_exported": "",
"jobs_reconcile": "",
diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json
index 569b13e3e..5a28a5ea6 100644
--- a/client/src/translations/fr/common.json
+++ b/client/src/translations/fr/common.json
@@ -258,6 +258,7 @@
"saving": ""
},
"fields": {
+ "ReceivableCustomField": "",
"address1": "",
"address2": "",
"appt_alt_transport": "",
@@ -476,7 +477,7 @@
"editaccess": ""
}
},
- "ReceivableCustomField": "",
+
"responsibilitycenter": "",
"responsibilitycenter_accountdesc": "",
"responsibilitycenter_accountitem": "",
@@ -843,8 +844,8 @@
"notconfigured": "",
"notfoundsubtitle": "",
"notfoundtitle": "",
- "surveycompletetitle": "",
- "surveycompletesubtitle": ""
+ "surveycompletesubtitle": "",
+ "surveycompletetitle": ""
},
"fields": {
"completedon": "",
@@ -853,13 +854,13 @@
"validuntil": ""
},
"labels": {
+ "copyright": "",
+ "greeting": "",
+ "intro": "",
"nologgedinuser": "",
"nologgedinuser_sub": "",
"noneselected": "",
- "title": "",
- "greeting": "",
- "intro": "",
- "copyright": ""
+ "title": ""
},
"successes": {
"created": "",
@@ -1270,7 +1271,15 @@
"relative_end": "",
"relative_start": "",
"start": "",
- "value": ""
+ "value": "",
+ "status": "",
+ "percentage": "",
+ "human_readable": "",
+ "status_count": ""
+ },
+ "titles": {
+ "dashboard": "",
+ "top_durations": ""
},
"content": {
"current_status_accumulated_time": "",
@@ -1282,7 +1291,10 @@
"title": "",
"title_durations": "",
"title_loading": "",
- "title_transitions": ""
+ "title_transitions": "",
+ "calculated_based_on": "",
+ "jobs_in_since": "",
+ "joblifecycle": ""
},
"errors": {
"fetch": "Erreur lors de l'obtention des données du cycle de vie des tâches"
@@ -1857,6 +1869,7 @@
"job": "",
"jobcosting": "",
"jobtotals": "",
+ "labor_hrs": "",
"labor_rates_subtotal": "",
"laborallocations": "",
"labortotals": "",
@@ -2451,6 +2464,7 @@
"invoice_total_payable": "",
"iou_form": "",
"job_costing_ro": "",
+ "job_lifecycle_ro": "",
"job_notes": "",
"key_tag": "",
"labels": {
@@ -2617,17 +2631,17 @@
},
"labels": {
"advanced_filters": "",
- "advanced_filters_show": "",
- "advanced_filters_hide": "",
- "advanced_filters_filters": "",
- "advanced_filters_sorters": "",
- "advanced_filters_filter_field": "",
- "advanced_filters_sorter_field": "",
- "advanced_filters_true": "",
"advanced_filters_false": "",
- "advanced_filters_sorter_direction": "",
+ "advanced_filters_filter_field": "",
"advanced_filters_filter_operator": "",
"advanced_filters_filter_value": "",
+ "advanced_filters_filters": "",
+ "advanced_filters_hide": "",
+ "advanced_filters_show": "",
+ "advanced_filters_sorter_direction": "",
+ "advanced_filters_sorter_field": "",
+ "advanced_filters_sorters": "",
+ "advanced_filters_true": "",
"dates": "",
"employee": "",
"filterson": "",
@@ -2709,6 +2723,8 @@
"job_costing_ro_date_summary": "",
"job_costing_ro_estimator": "",
"job_costing_ro_ins_co": "",
+ "job_lifecycle_date_detail": "",
+ "job_lifecycle_date_summary": "",
"jobs_completed_not_invoiced": "",
"jobs_invoiced_not_exported": "",
"jobs_reconcile": "",
diff --git a/client/src/utils/TemplateConstants.js b/client/src/utils/TemplateConstants.js
index 019b8d4e5..e06f2f003 100644
--- a/client/src/utils/TemplateConstants.js
+++ b/client/src/utils/TemplateConstants.js
@@ -514,6 +514,14 @@ export const TemplateList = (type, context) => {
group: "financial",
dms: true,
},
+ job_lifecycle_ro: {
+ title: i18n.t("printcenter.jobs.job_lifecycle_ro"),
+ description: "",
+ subject: i18n.t("printcenter.jobs.job_lifecycle_ro"),
+ key: "job_lifecycle_ro",
+ disabled: false,
+ group: "post",
+ },
}
: {}),
...(!type || type === "job_special"
@@ -2048,6 +2056,30 @@ export const TemplateList = (type, context) => {
datedisable: true,
group: "customers",
},
+ job_lifecycle_date_detail: {
+ title: i18n.t("reportcenter.templates.job_lifecycle_date_detail"),
+ subject: i18n.t("reportcenter.templates.job_lifecycle_date_detail"),
+ key: "job_lifecycle_date_detail",
+ //idtype: "vendor",
+ disabled: false,
+ rangeFilter: {
+ object: i18n.t("reportcenter.labels.objects.jobs"),
+ field: i18n.t("jobs.fields.date_invoiced"),
+ },
+ group: "jobs",
+ },
+ job_lifecycle_date_summary: {
+ title: i18n.t("reportcenter.templates.job_lifecycle_date_summary"),
+ subject: i18n.t("reportcenter.templates.job_lifecycle_date_summary"),
+ key: "job_lifecycle_date_summary",
+ //idtype: "vendor",
+ disabled: false,
+ rangeFilter: {
+ object: i18n.t("reportcenter.labels.objects.jobs"),
+ field: i18n.t("jobs.fields.date_invoiced"),
+ },
+ group: "jobs",
+ },
}
: {}),
...(!type || type === "courtesycarcontract"
diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml
index ae375e361..9cc14dd54 100644
--- a/hasura/metadata/tables.yaml
+++ b/hasura/metadata/tables.yaml
@@ -4200,7 +4200,7 @@
interval_sec: 10
num_retries: 0
timeout_sec: 60
- webhook_from_env: HASURA_API_URL
+ webhook: https://worktest.home.irony.online
headers:
- name: event-secret
value_from_env: EVENT_SECRET
diff --git a/hasura/migrations/1710520532230_alter_table_public_ioevents_add_column_useremail/down.sql b/hasura/migrations/1710520532230_alter_table_public_ioevents_add_column_useremail/down.sql
new file mode 100644
index 000000000..c15a1de62
--- /dev/null
+++ b/hasura/migrations/1710520532230_alter_table_public_ioevents_add_column_useremail/down.sql
@@ -0,0 +1,4 @@
+-- Could not auto-generate a down migration.
+-- Please write an appropriate down migration for the SQL below:
+-- alter table "public"."ioevents" add column "useremail" text
+-- not null;
diff --git a/hasura/migrations/1710520532230_alter_table_public_ioevents_add_column_useremail/up.sql b/hasura/migrations/1710520532230_alter_table_public_ioevents_add_column_useremail/up.sql
new file mode 100644
index 000000000..52debb0eb
--- /dev/null
+++ b/hasura/migrations/1710520532230_alter_table_public_ioevents_add_column_useremail/up.sql
@@ -0,0 +1 @@
+alter table "public"."ioevents" add column "useremail" text;
diff --git a/hasura/migrations/1710520551755_alter_table_public_ioevents_add_column_bodyshopid/down.sql b/hasura/migrations/1710520551755_alter_table_public_ioevents_add_column_bodyshopid/down.sql
new file mode 100644
index 000000000..ce3de08a7
--- /dev/null
+++ b/hasura/migrations/1710520551755_alter_table_public_ioevents_add_column_bodyshopid/down.sql
@@ -0,0 +1,4 @@
+-- Could not auto-generate a down migration.
+-- Please write an appropriate down migration for the SQL below:
+-- alter table "public"."ioevents" add column "bodyshopid" uuid
+-- null;
diff --git a/hasura/migrations/1710520551755_alter_table_public_ioevents_add_column_bodyshopid/up.sql b/hasura/migrations/1710520551755_alter_table_public_ioevents_add_column_bodyshopid/up.sql
new file mode 100644
index 000000000..ac3ff14ab
--- /dev/null
+++ b/hasura/migrations/1710520551755_alter_table_public_ioevents_add_column_bodyshopid/up.sql
@@ -0,0 +1,2 @@
+alter table "public"."ioevents" add column "bodyshopid" uuid
+ null;
diff --git a/hasura/migrations/1710520563676_alter_table_public_ioevents_alter_column_useremail/down.sql b/hasura/migrations/1710520563676_alter_table_public_ioevents_alter_column_useremail/down.sql
new file mode 100644
index 000000000..5c6a0fcaf
--- /dev/null
+++ b/hasura/migrations/1710520563676_alter_table_public_ioevents_alter_column_useremail/down.sql
@@ -0,0 +1 @@
+alter table "public"."ioevents" alter column "useremail" set not null;
diff --git a/hasura/migrations/1710520563676_alter_table_public_ioevents_alter_column_useremail/up.sql b/hasura/migrations/1710520563676_alter_table_public_ioevents_alter_column_useremail/up.sql
new file mode 100644
index 000000000..12a8aaaab
--- /dev/null
+++ b/hasura/migrations/1710520563676_alter_table_public_ioevents_alter_column_useremail/up.sql
@@ -0,0 +1 @@
+alter table "public"."ioevents" alter column "useremail" drop not null;
diff --git a/hasura/migrations/1710520583458_alter_table_public_ioevents_add_column_env/down.sql b/hasura/migrations/1710520583458_alter_table_public_ioevents_add_column_env/down.sql
new file mode 100644
index 000000000..a70c9031c
--- /dev/null
+++ b/hasura/migrations/1710520583458_alter_table_public_ioevents_add_column_env/down.sql
@@ -0,0 +1,4 @@
+-- Could not auto-generate a down migration.
+-- Please write an appropriate down migration for the SQL below:
+-- alter table "public"."ioevents" add column "env" text
+-- null;
diff --git a/hasura/migrations/1710520583458_alter_table_public_ioevents_add_column_env/up.sql b/hasura/migrations/1710520583458_alter_table_public_ioevents_add_column_env/up.sql
new file mode 100644
index 000000000..21077366d
--- /dev/null
+++ b/hasura/migrations/1710520583458_alter_table_public_ioevents_add_column_env/up.sql
@@ -0,0 +1,2 @@
+alter table "public"."ioevents" add column "env" text
+ null;
diff --git a/hasura/migrations/1710520629475_create_index_ioevents_useremail/down.sql b/hasura/migrations/1710520629475_create_index_ioevents_useremail/down.sql
new file mode 100644
index 000000000..413d340c9
--- /dev/null
+++ b/hasura/migrations/1710520629475_create_index_ioevents_useremail/down.sql
@@ -0,0 +1 @@
+DROP INDEX IF EXISTS "public"."ioevents_useremail";
diff --git a/hasura/migrations/1710520629475_create_index_ioevents_useremail/up.sql b/hasura/migrations/1710520629475_create_index_ioevents_useremail/up.sql
new file mode 100644
index 000000000..d9a5a1a2c
--- /dev/null
+++ b/hasura/migrations/1710520629475_create_index_ioevents_useremail/up.sql
@@ -0,0 +1,2 @@
+CREATE INDEX "ioevents_useremail" on
+ "public"."ioevents" using btree ("useremail");
diff --git a/hasura/migrations/1710520654372_set_fk_public_ioevents_useremail/down.sql b/hasura/migrations/1710520654372_set_fk_public_ioevents_useremail/down.sql
new file mode 100644
index 000000000..33632f335
--- /dev/null
+++ b/hasura/migrations/1710520654372_set_fk_public_ioevents_useremail/down.sql
@@ -0,0 +1 @@
+alter table "public"."ioevents" drop constraint "ioevents_useremail_fkey";
diff --git a/hasura/migrations/1710520654372_set_fk_public_ioevents_useremail/up.sql b/hasura/migrations/1710520654372_set_fk_public_ioevents_useremail/up.sql
new file mode 100644
index 000000000..47ac7307c
--- /dev/null
+++ b/hasura/migrations/1710520654372_set_fk_public_ioevents_useremail/up.sql
@@ -0,0 +1,5 @@
+alter table "public"."ioevents"
+ add constraint "ioevents_useremail_fkey"
+ foreign key ("useremail")
+ references "public"."users"
+ ("email") on update set null on delete set null;
diff --git a/hasura/migrations/1710520693899_set_fk_public_ioevents_bodyshopid/down.sql b/hasura/migrations/1710520693899_set_fk_public_ioevents_bodyshopid/down.sql
new file mode 100644
index 000000000..aa868e98e
--- /dev/null
+++ b/hasura/migrations/1710520693899_set_fk_public_ioevents_bodyshopid/down.sql
@@ -0,0 +1 @@
+alter table "public"."ioevents" drop constraint "ioevents_bodyshopid_fkey";
diff --git a/hasura/migrations/1710520693899_set_fk_public_ioevents_bodyshopid/up.sql b/hasura/migrations/1710520693899_set_fk_public_ioevents_bodyshopid/up.sql
new file mode 100644
index 000000000..e11c5e9b9
--- /dev/null
+++ b/hasura/migrations/1710520693899_set_fk_public_ioevents_bodyshopid/up.sql
@@ -0,0 +1,5 @@
+alter table "public"."ioevents"
+ add constraint "ioevents_bodyshopid_fkey"
+ foreign key ("bodyshopid")
+ references "public"."bodyshops"
+ ("id") on update set null on delete set null;
diff --git a/hasura/migrations/1710523428339_create_index_idx_audit_trail_type/down.sql b/hasura/migrations/1710523428339_create_index_idx_audit_trail_type/down.sql
new file mode 100644
index 000000000..65e575e43
--- /dev/null
+++ b/hasura/migrations/1710523428339_create_index_idx_audit_trail_type/down.sql
@@ -0,0 +1 @@
+DROP INDEX IF EXISTS "public"."idx_audit_trail_type";
diff --git a/hasura/migrations/1710523428339_create_index_idx_audit_trail_type/up.sql b/hasura/migrations/1710523428339_create_index_idx_audit_trail_type/up.sql
new file mode 100644
index 000000000..70e81381d
--- /dev/null
+++ b/hasura/migrations/1710523428339_create_index_idx_audit_trail_type/up.sql
@@ -0,0 +1,2 @@
+CREATE INDEX "idx_audit_trail_type" on
+ "public"."audit_trail" using btree ("type");
diff --git a/server/data/kaizen.js b/server/data/kaizen.js
index 19f8d75a3..d6dc1c7eb 100644
--- a/server/data/kaizen.js
+++ b/server/data/kaizen.js
@@ -193,28 +193,33 @@ exports.default = async (req, res) => {
});
}
- //***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml
- } catch (error) {
- logger.log("kaizen-sftp-error", "ERROR", "api", null, {
- ...error,
- });
- } finally {
- sftp.end();
- }
- sendServerEmail({
- subject: `Kaizen Report ${moment().format("MM-DD-YY")}`,
- text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
- Uploaded: ${JSON.stringify(
- allxmlsToUpload.map((x) => ({filename: x.filename, count: x.count})),
- null,
- 2
- )}
- `,
- });
- res.sendStatus(200);
+ //***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml
} catch (error) {
- res.status(200).json(error);
+ logger.log("kaizen-sftp-error", "ERROR", "api", null, {
+ ...error,
+ });
+ } finally {
+ sftp.end();
}
+ // sendServerEmail({
+ // subject: `Kaizen Report ${moment().format("MM-DD-YY")}`,
+ // text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
+ // Uploaded: ${JSON.stringify(
+ // allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
+ // null,
+ // 2
+ // )}
+ // `,
+ // });
+ res.sendStatus(200);
+ } catch (error) {
+ res.status(200).json(error);
+ sendServerEmail({
+ subject: `Kaizen Report ${moment().format("MM-DD-YY @ HH:mm:ss")}`,
+ text: `Errors: JSON.stringify(error)}
+ All Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}`,
+ });
+ }
};
const CreateRepairOrderTag = (job, errorCallback) => {
diff --git a/server/ioevent/ioevent.js b/server/ioevent/ioevent.js
index bab241bbd..46d5564ba 100644
--- a/server/ioevent/ioevent.js
+++ b/server/ioevent/ioevent.js
@@ -11,27 +11,40 @@ require("dotenv").config({
});
exports.default = async (req, res) => {
- const {operationName, time, dbevent, user, imexshopid} = req.body;
+ const {
+ useremail,
+ bodyshopid,
+ operationName,
+ variables,
+ env,
+ time,
+ dbevent,
+ user,
+ } = req.body;
try {
- // await client.request(queries.INSERT_IOEVENT, {
- // event: {
- // operationname: operationName,
- // time,
- // dbevent,
- // },
- // });
- console.log("IOEVENT", operationName, time, dbevent, user, imexshopid);
- logger.log("ioevent", "trace", user, null, {
- imexshopid,
- operationName,
- time,
- dbevent,
+ await client.request(queries.INSERT_IOEVENT, {
+ event: {
+ operationname: operationName,
+ time,
+ dbevent,
+ env,
+ variables,
+ bodyshopid,
+ useremail,
+ },
});
-
res.sendStatus(200);
} catch (error) {
- console.log("error", error);
- res.status(400).send(error);
+ logger.log("ioevent-error", "trace", user, null, {
+ operationname: operationName,
+ time,
+ dbevent,
+ env,
+ variables,
+ bodyshopid,
+ useremail,
+ });
+ res.sendStatus(200);
}
};
diff --git a/server/job/job-lifecycle.js b/server/job/job-lifecycle.js
index c2ea72737..b94f9ab68 100644
--- a/server/job/job-lifecycle.js
+++ b/server/job/job-lifecycle.js
@@ -3,6 +3,7 @@ const queries = require("../graphql-client/queries");
const moment = require("moment");
const durationToHumanReadable = require("../utils/durationToHumanReadable");
const calculateStatusDuration = require("../utils/calculateStatusDuration");
+const getLifecycleStatusColor = require("../utils/getLifecycleStatusColor");
const jobLifecycle = async (req, res) => {
// Grab the jobids and statuses from the request body
@@ -28,12 +29,12 @@ const jobLifecycle = async (req, res) => {
jobIDs,
transitions: []
});
-
}
const transitionsByJobId = _.groupBy(resp.transitions, 'jobid');
const groupedTransitions = {};
+ const allDurations = [];
for (let jobId in transitionsByJobId) {
let lifecycle = transitionsByJobId[jobId].map(transition => {
@@ -53,15 +54,57 @@ const jobLifecycle = async (req, res) => {
return transition;
});
+ const durations = calculateStatusDuration(lifecycle, statuses);
+
groupedTransitions[jobId] = {
- lifecycle: lifecycle,
- durations: calculateStatusDuration(lifecycle, statuses),
+ lifecycle,
+ durations
};
+
+ if (durations?.summations) {
+ allDurations.push(durations.summations);
+ }
}
+ const finalSummations = [];
+ const flatGroupedAllDurations = _.groupBy(allDurations.flat(),'status');
+
+ const finalStatusCounts = Object.keys(flatGroupedAllDurations).reduce((acc, status) => {
+ acc[status] = flatGroupedAllDurations[status].length;
+ return acc;
+ }, {});
+ // Calculate total value of all statuses
+ const finalTotal = Object.values(flatGroupedAllDurations).reduce((total, statusArr) => {
+ return total + statusArr.reduce((acc, curr) => acc + curr.value, 0);
+ }, 0);
+
+ Object.keys(flatGroupedAllDurations).forEach(status => {
+ const value = flatGroupedAllDurations[status].reduce((acc, curr) => acc + curr.value, 0);
+ const humanReadable = durationToHumanReadable(moment.duration(value));
+ const percentage = (value / finalTotal) * 100;
+ const color = getLifecycleStatusColor(status);
+ const roundedPercentage = `${Math.round(percentage)}%`;
+ finalSummations.push({
+ status,
+ value,
+ humanReadable,
+ percentage,
+ color,
+ roundedPercentage
+ });
+ });
+
return res.status(200).json({
jobIDs,
transition: groupedTransitions,
+ durations: {
+ jobs: jobIDs.length,
+ summations: finalSummations,
+ totalStatuses: finalSummations.length,
+ total: finalTotal,
+ statusCounts: finalStatusCounts,
+ humanReadable: durationToHumanReadable(moment.duration(finalTotal))
+ }
});
}
diff --git a/server/utils/calculateStatusDuration.js b/server/utils/calculateStatusDuration.js
index 16165d001..1fcdcc487 100644
--- a/server/utils/calculateStatusDuration.js
+++ b/server/utils/calculateStatusDuration.js
@@ -1,15 +1,7 @@
const durationToHumanReadable = require("./durationToHumanReadable");
const moment = require("moment");
+const getLifecycleStatusColor = require("./getLifecycleStatusColor");
const _ = require("lodash");
-const crypto = require('crypto');
-
-const getColor = (key) => {
- const hash = crypto.createHash('sha256');
- hash.update(key);
- const hashedKey = hash.digest('hex');
- const num = parseInt(hashedKey, 16);
- return '#' + (num % 16777215).toString(16).padStart(6, '0');
-};
const calculateStatusDuration = (transitions, statuses) => {
let statusDuration = {};
@@ -33,26 +25,16 @@ const calculateStatusDuration = (transitions, statuses) => {
if (!transition.prev_value) {
statusDuration[transition.value] = {
value: duration,
- humanReadable: transition.duration_readable
+ humanReadable: durationToHumanReadable(moment.duration(duration))
};
- } else if (!transition.next_value) {
+ } else {
if (statusDuration[transition.value]) {
statusDuration[transition.value].value += duration;
- statusDuration[transition.value].humanReadable = transition.duration_readable;
+ statusDuration[transition.value].humanReadable = durationToHumanReadable(moment.duration(statusDuration[transition.value].value));
} else {
statusDuration[transition.value] = {
value: duration,
- humanReadable: transition.duration_readable
- };
- }
- } else {
- if (statusDuration[transition.value]) {
- statusDuration[transition.value].value += duration;
- statusDuration[transition.value].humanReadable = transition.duration_readable;
- } else {
- statusDuration[transition.value] = {
- value: duration,
- humanReadable: transition.duration_readable
+ humanReadable: durationToHumanReadable(moment.duration(duration))
};
}
}
@@ -79,7 +61,7 @@ const calculateStatusDuration = (transitions, statuses) => {
value,
humanReadable,
percentage: statusDuration[status].percentage,
- color: getColor(status),
+ color: getLifecycleStatusColor(status),
roundedPercentage: `${Math.round(statusDuration[status].percentage)}%`
});
}
diff --git a/server/utils/getLifecycleStatusColor.js b/server/utils/getLifecycleStatusColor.js
new file mode 100644
index 000000000..d726e1cb3
--- /dev/null
+++ b/server/utils/getLifecycleStatusColor.js
@@ -0,0 +1,11 @@
+const crypto = require('crypto');
+
+const getLifecycleStatusColor = (key) => {
+ const hash = crypto.createHash('sha256');
+ hash.update(key);
+ const hashedKey = hash.digest('hex');
+ const num = parseInt(hashedKey, 16);
+ return '#' + (num % 16777215).toString(16).padStart(6, '0');
+};
+
+module.exports = getLifecycleStatusColor;
\ No newline at end of file