From e15edeadb5c594da3ebc5885cad302bf1cba61a8 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 29 Nov 2023 23:54:37 +0000 Subject: [PATCH 01/67] Revert "Fix issues with limits. (pull request #1089)" --- .../components/inventory-list/inventory-list.container.jsx | 5 ++--- client/src/components/jobs-list/jobs-list.component.jsx | 2 +- client/src/components/owners-list/owners-list.container.jsx | 5 ++--- .../src/components/vehicles-list/vehicles-list.container.jsx | 5 ++--- client/src/pages/bills/bills.page.container.jsx | 5 ++--- client/src/pages/contracts/contracts.page.container.jsx | 5 ++--- .../courtesy-car-detail.page.container.jsx | 5 ++--- client/src/pages/export-logs/export-logs.page.component.jsx | 4 ++-- client/src/pages/jobs-all/jobs-all.container.jsx | 5 ++--- .../src/pages/payments-all/payments-all.container.page.jsx | 5 ++--- client/src/pages/phonebook/phonebook.page.component.jsx | 4 ++-- client/src/pages/shop-csi/shop-csi.container.page.jsx | 5 ++--- 12 files changed, 23 insertions(+), 32 deletions(-) diff --git a/client/src/components/inventory-list/inventory-list.container.jsx b/client/src/components/inventory-list/inventory-list.container.jsx index e98266464..bb265060f 100644 --- a/client/src/components/inventory-list/inventory-list.container.jsx +++ b/client/src/components/inventory-list/inventory-list.container.jsx @@ -11,7 +11,6 @@ import { } from "../../redux/application/application.actions"; import AlertComponent from "../alert/alert.component"; import InventoryListPaginated from "./inventory-list.component"; -import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ //bodyshop: selectBodyshop, @@ -33,8 +32,8 @@ export function InventoryList({ setBreadcrumbs, setSelectedHeader }) { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + offset: page ? (page - 1) * 25 : 0, + limit: 25, consumedIsNull: showall === "true" ? null : true, order: [ { diff --git a/client/src/components/jobs-list/jobs-list.component.jsx b/client/src/components/jobs-list/jobs-list.component.jsx index b7bd3141d..885b53634 100644 --- a/client/src/components/jobs-list/jobs-list.component.jsx +++ b/client/src/components/jobs-list/jobs-list.component.jsx @@ -38,7 +38,7 @@ export function JobsList({bodyshop,}) { const {loading, error, data, refetch} = useQuery(QUERY_ALL_ACTIVE_JOBS_PAGINATED, { variables: { offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + limit: 10, statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"], order: [ { diff --git a/client/src/components/owners-list/owners-list.container.jsx b/client/src/components/owners-list/owners-list.container.jsx index 7303c4222..78db6bf2b 100644 --- a/client/src/components/owners-list/owners-list.container.jsx +++ b/client/src/components/owners-list/owners-list.container.jsx @@ -5,7 +5,6 @@ import AlertComponent from "../alert/alert.component"; import OwnersListComponent from "./owners-list.component"; import queryString from "query-string"; import { useLocation } from "react-router-dom"; -import {pageLimit} from "../../utils/config"; export default function OwnersListContainer() { const searchParams = queryString.parse(useLocation().search); @@ -17,8 +16,8 @@ export default function OwnersListContainer() { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + offset: page ? (page - 1) * 25 : 0, + limit: 25, order: [ { [sortcolumn || "created_at"]: sortorder diff --git a/client/src/components/vehicles-list/vehicles-list.container.jsx b/client/src/components/vehicles-list/vehicles-list.container.jsx index 2e85236f8..34fc4940b 100644 --- a/client/src/components/vehicles-list/vehicles-list.container.jsx +++ b/client/src/components/vehicles-list/vehicles-list.container.jsx @@ -5,7 +5,6 @@ import AlertComponent from "../alert/alert.component"; import { QUERY_ALL_VEHICLES_PAGINATED } from "../../graphql/vehicles.queries"; import queryString from "query-string"; import { useLocation } from "react-router-dom"; -import {pageLimit} from "../../utils/config"; export default function VehiclesListContainer() { const searchParams = queryString.parse(useLocation().search); @@ -16,8 +15,8 @@ export default function VehiclesListContainer() { { variables: { search: search || "", - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + offset: page ? (page - 1) * 25 : 0, + limit: 25, order: [ { [sortcolumn || "created_at"]: sortorder diff --git a/client/src/pages/bills/bills.page.container.jsx b/client/src/pages/bills/bills.page.container.jsx index 40e662899..22a8b61b9 100644 --- a/client/src/pages/bills/bills.page.container.jsx +++ b/client/src/pages/bills/bills.page.container.jsx @@ -13,7 +13,6 @@ import { setSelectedHeader, } from "../../redux/application/application.actions"; import BillsPageComponent from "./bills.page.component"; -import {pageLimit} from "../../utils/config"; const mapDispatchToProps = (dispatch) => ({ setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), @@ -39,8 +38,8 @@ export function BillsPageContainer({ setBreadcrumbs, setSelectedHeader }) { fetchPolicy: "network-only", nextFetchPolicy: "network-only", variables: { - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + offset: page ? (page - 1) * 25 : 0, + limit: 25, order: [ searchObj ? JSON.parse(searchObj) diff --git a/client/src/pages/contracts/contracts.page.container.jsx b/client/src/pages/contracts/contracts.page.container.jsx index 466b468e0..c4f4fef7b 100644 --- a/client/src/pages/contracts/contracts.page.container.jsx +++ b/client/src/pages/contracts/contracts.page.container.jsx @@ -12,7 +12,6 @@ import { setSelectedHeader, } from "../../redux/application/application.actions"; import ContractsPageComponent from "./contracts.page.component"; -import {pageLimit} from "../../utils/config"; const mapDispatchToProps = (dispatch) => ({ setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), @@ -30,8 +29,8 @@ export function ContractsPageContainer({ setBreadcrumbs, setSelectedHeader }) { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + offset: page ? (page - 1) * 25 : 0, + limit: 25, order: [ { [sortcolumn || "start"]: sortorder diff --git a/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx b/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx index 93bffe97f..e41e80f40 100644 --- a/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx +++ b/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx @@ -19,7 +19,6 @@ import NotFound from "../../components/not-found/not-found.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import queryString from "query-string"; import { useLocation } from "react-router-dom"; -import {pageLimit} from "../../utils/config"; const mapDispatchToProps = (dispatch) => ({ setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), @@ -42,8 +41,8 @@ export function CourtesyCarDetailPageContainer({ const { loading, error, data } = useQuery(QUERY_CC_BY_PK, { variables: { id: ccId, - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + offset: page ? (page - 1) * 25 : 0, + limit: 25, order: [ { [sortcolumn || "start"]: sortorder diff --git a/client/src/pages/export-logs/export-logs.page.component.jsx b/client/src/pages/export-logs/export-logs.page.component.jsx index 7ae8977cf..0c6517a58 100644 --- a/client/src/pages/export-logs/export-logs.page.component.jsx +++ b/client/src/pages/export-logs/export-logs.page.component.jsx @@ -30,8 +30,8 @@ export function ExportLogsPageComponent({ bodyshop }) { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + offset: page ? (page - 1) * 25 : 0, + limit: 25, order: [ { [sortcolumn || "created_at"]: sortorder diff --git a/client/src/pages/jobs-all/jobs-all.container.jsx b/client/src/pages/jobs-all/jobs-all.container.jsx index b9fa9a52a..dd78369a0 100644 --- a/client/src/pages/jobs-all/jobs-all.container.jsx +++ b/client/src/pages/jobs-all/jobs-all.container.jsx @@ -13,7 +13,6 @@ import { setBreadcrumbs, setSelectedHeader, } from "../../redux/application/application.actions"; -import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ //bodyshop: selectBodyshop, @@ -34,8 +33,8 @@ export function AllJobs({ setBreadcrumbs, setSelectedHeader }) { fetchPolicy: "network-only", nextFetchPolicy: "network-only", variables: { - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + offset: page ? (page - 1) * 10 : 0, + limit: 25, ...(statusFilters ? { statusList: JSON.parse(statusFilters) } : {}), order: [ { diff --git a/client/src/pages/payments-all/payments-all.container.page.jsx b/client/src/pages/payments-all/payments-all.container.page.jsx index 3d3cb6287..07547bc2e 100644 --- a/client/src/pages/payments-all/payments-all.container.page.jsx +++ b/client/src/pages/payments-all/payments-all.container.page.jsx @@ -14,7 +14,6 @@ import { setSelectedHeader, } from "../../redux/application/application.actions"; import { selectBodyshop } from "../../redux/user/user.selectors"; -import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -35,8 +34,8 @@ export function AllJobs({ bodyshop, setBreadcrumbs, setSelectedHeader }) { fetchPolicy: "network-only", nextFetchPolicy: "network-only", variables: { - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + offset: page ? (page - 1) * 25 : 0, + limit: 25, order: [ searchObj ? JSON.parse(searchObj) diff --git a/client/src/pages/phonebook/phonebook.page.component.jsx b/client/src/pages/phonebook/phonebook.page.component.jsx index 5ce863444..baf4e18ec 100644 --- a/client/src/pages/phonebook/phonebook.page.component.jsx +++ b/client/src/pages/phonebook/phonebook.page.component.jsx @@ -36,8 +36,8 @@ export function PhonebookPageComponent({ bodyshop, authLevel }) { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + offset: page ? (page - 1) * 25 : 0, + limit: 25, order: [ { [sortcolumn || "lastname"]: sortorder diff --git a/client/src/pages/shop-csi/shop-csi.container.page.jsx b/client/src/pages/shop-csi/shop-csi.container.page.jsx index f0c295685..132ee7999 100644 --- a/client/src/pages/shop-csi/shop-csi.container.page.jsx +++ b/client/src/pages/shop-csi/shop-csi.container.page.jsx @@ -16,7 +16,6 @@ import { } from "../../redux/application/application.actions"; import { selectBodyshop } from "../../redux/user/user.selectors"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, }); @@ -43,8 +42,8 @@ export function ShopCsiContainer({ nextFetchPolicy: "network-only", variables: { //search: search || "", - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + offset: page ? (page - 1) * 25 : 0, + limit: 25, order: [ { [sortcolumn || "completedon"]: sortorder From 0852d55837f540892c9f1714380cd2b1d9731334 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 30 Nov 2023 00:22:20 +0000 Subject: [PATCH 02/67] Revert "Revert "Fix issues with limits. (pull request #1090)" --- .../components/inventory-list/inventory-list.container.jsx | 5 +++-- client/src/components/jobs-list/jobs-list.component.jsx | 2 +- client/src/components/owners-list/owners-list.container.jsx | 5 +++-- .../src/components/vehicles-list/vehicles-list.container.jsx | 5 +++-- client/src/pages/bills/bills.page.container.jsx | 5 +++-- client/src/pages/contracts/contracts.page.container.jsx | 5 +++-- .../courtesy-car-detail.page.container.jsx | 5 +++-- client/src/pages/export-logs/export-logs.page.component.jsx | 4 ++-- client/src/pages/jobs-all/jobs-all.container.jsx | 5 +++-- .../src/pages/payments-all/payments-all.container.page.jsx | 5 +++-- client/src/pages/phonebook/phonebook.page.component.jsx | 4 ++-- client/src/pages/shop-csi/shop-csi.container.page.jsx | 5 +++-- 12 files changed, 32 insertions(+), 23 deletions(-) diff --git a/client/src/components/inventory-list/inventory-list.container.jsx b/client/src/components/inventory-list/inventory-list.container.jsx index bb265060f..e98266464 100644 --- a/client/src/components/inventory-list/inventory-list.container.jsx +++ b/client/src/components/inventory-list/inventory-list.container.jsx @@ -11,6 +11,7 @@ import { } from "../../redux/application/application.actions"; import AlertComponent from "../alert/alert.component"; import InventoryListPaginated from "./inventory-list.component"; +import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ //bodyshop: selectBodyshop, @@ -32,8 +33,8 @@ export function InventoryList({ setBreadcrumbs, setSelectedHeader }) { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, consumedIsNull: showall === "true" ? null : true, order: [ { diff --git a/client/src/components/jobs-list/jobs-list.component.jsx b/client/src/components/jobs-list/jobs-list.component.jsx index 885b53634..b7bd3141d 100644 --- a/client/src/components/jobs-list/jobs-list.component.jsx +++ b/client/src/components/jobs-list/jobs-list.component.jsx @@ -38,7 +38,7 @@ export function JobsList({bodyshop,}) { const {loading, error, data, refetch} = useQuery(QUERY_ALL_ACTIVE_JOBS_PAGINATED, { variables: { offset: page ? (page - 1) * pageLimit : 0, - limit: 10, + limit: pageLimit, statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"], order: [ { diff --git a/client/src/components/owners-list/owners-list.container.jsx b/client/src/components/owners-list/owners-list.container.jsx index 78db6bf2b..7303c4222 100644 --- a/client/src/components/owners-list/owners-list.container.jsx +++ b/client/src/components/owners-list/owners-list.container.jsx @@ -5,6 +5,7 @@ import AlertComponent from "../alert/alert.component"; import OwnersListComponent from "./owners-list.component"; import queryString from "query-string"; import { useLocation } from "react-router-dom"; +import {pageLimit} from "../../utils/config"; export default function OwnersListContainer() { const searchParams = queryString.parse(useLocation().search); @@ -16,8 +17,8 @@ export default function OwnersListContainer() { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ { [sortcolumn || "created_at"]: sortorder diff --git a/client/src/components/vehicles-list/vehicles-list.container.jsx b/client/src/components/vehicles-list/vehicles-list.container.jsx index 34fc4940b..2e85236f8 100644 --- a/client/src/components/vehicles-list/vehicles-list.container.jsx +++ b/client/src/components/vehicles-list/vehicles-list.container.jsx @@ -5,6 +5,7 @@ import AlertComponent from "../alert/alert.component"; import { QUERY_ALL_VEHICLES_PAGINATED } from "../../graphql/vehicles.queries"; import queryString from "query-string"; import { useLocation } from "react-router-dom"; +import {pageLimit} from "../../utils/config"; export default function VehiclesListContainer() { const searchParams = queryString.parse(useLocation().search); @@ -15,8 +16,8 @@ export default function VehiclesListContainer() { { variables: { search: search || "", - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ { [sortcolumn || "created_at"]: sortorder diff --git a/client/src/pages/bills/bills.page.container.jsx b/client/src/pages/bills/bills.page.container.jsx index 22a8b61b9..40e662899 100644 --- a/client/src/pages/bills/bills.page.container.jsx +++ b/client/src/pages/bills/bills.page.container.jsx @@ -13,6 +13,7 @@ import { setSelectedHeader, } from "../../redux/application/application.actions"; import BillsPageComponent from "./bills.page.component"; +import {pageLimit} from "../../utils/config"; const mapDispatchToProps = (dispatch) => ({ setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), @@ -38,8 +39,8 @@ export function BillsPageContainer({ setBreadcrumbs, setSelectedHeader }) { fetchPolicy: "network-only", nextFetchPolicy: "network-only", variables: { - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ searchObj ? JSON.parse(searchObj) diff --git a/client/src/pages/contracts/contracts.page.container.jsx b/client/src/pages/contracts/contracts.page.container.jsx index c4f4fef7b..466b468e0 100644 --- a/client/src/pages/contracts/contracts.page.container.jsx +++ b/client/src/pages/contracts/contracts.page.container.jsx @@ -12,6 +12,7 @@ import { setSelectedHeader, } from "../../redux/application/application.actions"; import ContractsPageComponent from "./contracts.page.component"; +import {pageLimit} from "../../utils/config"; const mapDispatchToProps = (dispatch) => ({ setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), @@ -29,8 +30,8 @@ export function ContractsPageContainer({ setBreadcrumbs, setSelectedHeader }) { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ { [sortcolumn || "start"]: sortorder diff --git a/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx b/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx index e41e80f40..93bffe97f 100644 --- a/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx +++ b/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx @@ -19,6 +19,7 @@ import NotFound from "../../components/not-found/not-found.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import queryString from "query-string"; import { useLocation } from "react-router-dom"; +import {pageLimit} from "../../utils/config"; const mapDispatchToProps = (dispatch) => ({ setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), @@ -41,8 +42,8 @@ export function CourtesyCarDetailPageContainer({ const { loading, error, data } = useQuery(QUERY_CC_BY_PK, { variables: { id: ccId, - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ { [sortcolumn || "start"]: sortorder diff --git a/client/src/pages/export-logs/export-logs.page.component.jsx b/client/src/pages/export-logs/export-logs.page.component.jsx index 0c6517a58..7ae8977cf 100644 --- a/client/src/pages/export-logs/export-logs.page.component.jsx +++ b/client/src/pages/export-logs/export-logs.page.component.jsx @@ -30,8 +30,8 @@ export function ExportLogsPageComponent({ bodyshop }) { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ { [sortcolumn || "created_at"]: sortorder diff --git a/client/src/pages/jobs-all/jobs-all.container.jsx b/client/src/pages/jobs-all/jobs-all.container.jsx index dd78369a0..b9fa9a52a 100644 --- a/client/src/pages/jobs-all/jobs-all.container.jsx +++ b/client/src/pages/jobs-all/jobs-all.container.jsx @@ -13,6 +13,7 @@ import { setBreadcrumbs, setSelectedHeader, } from "../../redux/application/application.actions"; +import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ //bodyshop: selectBodyshop, @@ -33,8 +34,8 @@ export function AllJobs({ setBreadcrumbs, setSelectedHeader }) { fetchPolicy: "network-only", nextFetchPolicy: "network-only", variables: { - offset: page ? (page - 1) * 10 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, ...(statusFilters ? { statusList: JSON.parse(statusFilters) } : {}), order: [ { diff --git a/client/src/pages/payments-all/payments-all.container.page.jsx b/client/src/pages/payments-all/payments-all.container.page.jsx index 07547bc2e..3d3cb6287 100644 --- a/client/src/pages/payments-all/payments-all.container.page.jsx +++ b/client/src/pages/payments-all/payments-all.container.page.jsx @@ -14,6 +14,7 @@ import { setSelectedHeader, } from "../../redux/application/application.actions"; import { selectBodyshop } from "../../redux/user/user.selectors"; +import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -34,8 +35,8 @@ export function AllJobs({ bodyshop, setBreadcrumbs, setSelectedHeader }) { fetchPolicy: "network-only", nextFetchPolicy: "network-only", variables: { - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ searchObj ? JSON.parse(searchObj) diff --git a/client/src/pages/phonebook/phonebook.page.component.jsx b/client/src/pages/phonebook/phonebook.page.component.jsx index baf4e18ec..5ce863444 100644 --- a/client/src/pages/phonebook/phonebook.page.component.jsx +++ b/client/src/pages/phonebook/phonebook.page.component.jsx @@ -36,8 +36,8 @@ export function PhonebookPageComponent({ bodyshop, authLevel }) { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ { [sortcolumn || "lastname"]: sortorder diff --git a/client/src/pages/shop-csi/shop-csi.container.page.jsx b/client/src/pages/shop-csi/shop-csi.container.page.jsx index 132ee7999..f0c295685 100644 --- a/client/src/pages/shop-csi/shop-csi.container.page.jsx +++ b/client/src/pages/shop-csi/shop-csi.container.page.jsx @@ -16,6 +16,7 @@ import { } from "../../redux/application/application.actions"; import { selectBodyshop } from "../../redux/user/user.selectors"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; +import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, }); @@ -42,8 +43,8 @@ export function ShopCsiContainer({ nextFetchPolicy: "network-only", variables: { //search: search || "", - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ { [sortcolumn || "completedon"]: sortorder From 3ca6308dd2cf6bbfe8d83b7c6b1f450fc1354f54 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 30 Nov 2023 20:22:42 +0000 Subject: [PATCH 03/67] Revert "Revert "Revert "Fix issues with limits. (pull request #1091)" --- .../components/inventory-list/inventory-list.container.jsx | 5 ++--- client/src/components/jobs-list/jobs-list.component.jsx | 2 +- client/src/components/owners-list/owners-list.container.jsx | 5 ++--- .../src/components/vehicles-list/vehicles-list.container.jsx | 5 ++--- client/src/pages/bills/bills.page.container.jsx | 5 ++--- client/src/pages/contracts/contracts.page.container.jsx | 5 ++--- .../courtesy-car-detail.page.container.jsx | 5 ++--- client/src/pages/export-logs/export-logs.page.component.jsx | 4 ++-- client/src/pages/jobs-all/jobs-all.container.jsx | 5 ++--- .../src/pages/payments-all/payments-all.container.page.jsx | 5 ++--- client/src/pages/phonebook/phonebook.page.component.jsx | 4 ++-- client/src/pages/shop-csi/shop-csi.container.page.jsx | 5 ++--- 12 files changed, 23 insertions(+), 32 deletions(-) diff --git a/client/src/components/inventory-list/inventory-list.container.jsx b/client/src/components/inventory-list/inventory-list.container.jsx index e98266464..bb265060f 100644 --- a/client/src/components/inventory-list/inventory-list.container.jsx +++ b/client/src/components/inventory-list/inventory-list.container.jsx @@ -11,7 +11,6 @@ import { } from "../../redux/application/application.actions"; import AlertComponent from "../alert/alert.component"; import InventoryListPaginated from "./inventory-list.component"; -import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ //bodyshop: selectBodyshop, @@ -33,8 +32,8 @@ export function InventoryList({ setBreadcrumbs, setSelectedHeader }) { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + offset: page ? (page - 1) * 25 : 0, + limit: 25, consumedIsNull: showall === "true" ? null : true, order: [ { diff --git a/client/src/components/jobs-list/jobs-list.component.jsx b/client/src/components/jobs-list/jobs-list.component.jsx index b7bd3141d..885b53634 100644 --- a/client/src/components/jobs-list/jobs-list.component.jsx +++ b/client/src/components/jobs-list/jobs-list.component.jsx @@ -38,7 +38,7 @@ export function JobsList({bodyshop,}) { const {loading, error, data, refetch} = useQuery(QUERY_ALL_ACTIVE_JOBS_PAGINATED, { variables: { offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + limit: 10, statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"], order: [ { diff --git a/client/src/components/owners-list/owners-list.container.jsx b/client/src/components/owners-list/owners-list.container.jsx index 7303c4222..78db6bf2b 100644 --- a/client/src/components/owners-list/owners-list.container.jsx +++ b/client/src/components/owners-list/owners-list.container.jsx @@ -5,7 +5,6 @@ import AlertComponent from "../alert/alert.component"; import OwnersListComponent from "./owners-list.component"; import queryString from "query-string"; import { useLocation } from "react-router-dom"; -import {pageLimit} from "../../utils/config"; export default function OwnersListContainer() { const searchParams = queryString.parse(useLocation().search); @@ -17,8 +16,8 @@ export default function OwnersListContainer() { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + offset: page ? (page - 1) * 25 : 0, + limit: 25, order: [ { [sortcolumn || "created_at"]: sortorder diff --git a/client/src/components/vehicles-list/vehicles-list.container.jsx b/client/src/components/vehicles-list/vehicles-list.container.jsx index 2e85236f8..34fc4940b 100644 --- a/client/src/components/vehicles-list/vehicles-list.container.jsx +++ b/client/src/components/vehicles-list/vehicles-list.container.jsx @@ -5,7 +5,6 @@ import AlertComponent from "../alert/alert.component"; import { QUERY_ALL_VEHICLES_PAGINATED } from "../../graphql/vehicles.queries"; import queryString from "query-string"; import { useLocation } from "react-router-dom"; -import {pageLimit} from "../../utils/config"; export default function VehiclesListContainer() { const searchParams = queryString.parse(useLocation().search); @@ -16,8 +15,8 @@ export default function VehiclesListContainer() { { variables: { search: search || "", - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + offset: page ? (page - 1) * 25 : 0, + limit: 25, order: [ { [sortcolumn || "created_at"]: sortorder diff --git a/client/src/pages/bills/bills.page.container.jsx b/client/src/pages/bills/bills.page.container.jsx index 40e662899..22a8b61b9 100644 --- a/client/src/pages/bills/bills.page.container.jsx +++ b/client/src/pages/bills/bills.page.container.jsx @@ -13,7 +13,6 @@ import { setSelectedHeader, } from "../../redux/application/application.actions"; import BillsPageComponent from "./bills.page.component"; -import {pageLimit} from "../../utils/config"; const mapDispatchToProps = (dispatch) => ({ setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), @@ -39,8 +38,8 @@ export function BillsPageContainer({ setBreadcrumbs, setSelectedHeader }) { fetchPolicy: "network-only", nextFetchPolicy: "network-only", variables: { - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + offset: page ? (page - 1) * 25 : 0, + limit: 25, order: [ searchObj ? JSON.parse(searchObj) diff --git a/client/src/pages/contracts/contracts.page.container.jsx b/client/src/pages/contracts/contracts.page.container.jsx index 466b468e0..c4f4fef7b 100644 --- a/client/src/pages/contracts/contracts.page.container.jsx +++ b/client/src/pages/contracts/contracts.page.container.jsx @@ -12,7 +12,6 @@ import { setSelectedHeader, } from "../../redux/application/application.actions"; import ContractsPageComponent from "./contracts.page.component"; -import {pageLimit} from "../../utils/config"; const mapDispatchToProps = (dispatch) => ({ setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), @@ -30,8 +29,8 @@ export function ContractsPageContainer({ setBreadcrumbs, setSelectedHeader }) { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + offset: page ? (page - 1) * 25 : 0, + limit: 25, order: [ { [sortcolumn || "start"]: sortorder diff --git a/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx b/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx index 93bffe97f..e41e80f40 100644 --- a/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx +++ b/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx @@ -19,7 +19,6 @@ import NotFound from "../../components/not-found/not-found.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import queryString from "query-string"; import { useLocation } from "react-router-dom"; -import {pageLimit} from "../../utils/config"; const mapDispatchToProps = (dispatch) => ({ setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), @@ -42,8 +41,8 @@ export function CourtesyCarDetailPageContainer({ const { loading, error, data } = useQuery(QUERY_CC_BY_PK, { variables: { id: ccId, - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + offset: page ? (page - 1) * 25 : 0, + limit: 25, order: [ { [sortcolumn || "start"]: sortorder diff --git a/client/src/pages/export-logs/export-logs.page.component.jsx b/client/src/pages/export-logs/export-logs.page.component.jsx index 7ae8977cf..0c6517a58 100644 --- a/client/src/pages/export-logs/export-logs.page.component.jsx +++ b/client/src/pages/export-logs/export-logs.page.component.jsx @@ -30,8 +30,8 @@ export function ExportLogsPageComponent({ bodyshop }) { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + offset: page ? (page - 1) * 25 : 0, + limit: 25, order: [ { [sortcolumn || "created_at"]: sortorder diff --git a/client/src/pages/jobs-all/jobs-all.container.jsx b/client/src/pages/jobs-all/jobs-all.container.jsx index b9fa9a52a..dd78369a0 100644 --- a/client/src/pages/jobs-all/jobs-all.container.jsx +++ b/client/src/pages/jobs-all/jobs-all.container.jsx @@ -13,7 +13,6 @@ import { setBreadcrumbs, setSelectedHeader, } from "../../redux/application/application.actions"; -import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ //bodyshop: selectBodyshop, @@ -34,8 +33,8 @@ export function AllJobs({ setBreadcrumbs, setSelectedHeader }) { fetchPolicy: "network-only", nextFetchPolicy: "network-only", variables: { - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + offset: page ? (page - 1) * 10 : 0, + limit: 25, ...(statusFilters ? { statusList: JSON.parse(statusFilters) } : {}), order: [ { diff --git a/client/src/pages/payments-all/payments-all.container.page.jsx b/client/src/pages/payments-all/payments-all.container.page.jsx index 3d3cb6287..07547bc2e 100644 --- a/client/src/pages/payments-all/payments-all.container.page.jsx +++ b/client/src/pages/payments-all/payments-all.container.page.jsx @@ -14,7 +14,6 @@ import { setSelectedHeader, } from "../../redux/application/application.actions"; import { selectBodyshop } from "../../redux/user/user.selectors"; -import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -35,8 +34,8 @@ export function AllJobs({ bodyshop, setBreadcrumbs, setSelectedHeader }) { fetchPolicy: "network-only", nextFetchPolicy: "network-only", variables: { - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + offset: page ? (page - 1) * 25 : 0, + limit: 25, order: [ searchObj ? JSON.parse(searchObj) diff --git a/client/src/pages/phonebook/phonebook.page.component.jsx b/client/src/pages/phonebook/phonebook.page.component.jsx index 5ce863444..baf4e18ec 100644 --- a/client/src/pages/phonebook/phonebook.page.component.jsx +++ b/client/src/pages/phonebook/phonebook.page.component.jsx @@ -36,8 +36,8 @@ export function PhonebookPageComponent({ bodyshop, authLevel }) { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + offset: page ? (page - 1) * 25 : 0, + limit: 25, order: [ { [sortcolumn || "lastname"]: sortorder diff --git a/client/src/pages/shop-csi/shop-csi.container.page.jsx b/client/src/pages/shop-csi/shop-csi.container.page.jsx index f0c295685..132ee7999 100644 --- a/client/src/pages/shop-csi/shop-csi.container.page.jsx +++ b/client/src/pages/shop-csi/shop-csi.container.page.jsx @@ -16,7 +16,6 @@ import { } from "../../redux/application/application.actions"; import { selectBodyshop } from "../../redux/user/user.selectors"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, }); @@ -43,8 +42,8 @@ export function ShopCsiContainer({ nextFetchPolicy: "network-only", variables: { //search: search || "", - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, + offset: page ? (page - 1) * 25 : 0, + limit: 25, order: [ { [sortcolumn || "completedon"]: sortorder From 5c95c72f408cdd3931a68c99f9c7bf53c3f204e4 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Fri, 1 Dec 2023 02:37:10 +0000 Subject: [PATCH 04/67] Revert "Revert "Revert "Revert "Fix issues with limits. (pull request #1097)" --- .../components/inventory-list/inventory-list.container.jsx | 5 +++-- client/src/components/jobs-list/jobs-list.component.jsx | 2 +- client/src/components/owners-list/owners-list.container.jsx | 5 +++-- .../src/components/vehicles-list/vehicles-list.container.jsx | 5 +++-- client/src/pages/bills/bills.page.container.jsx | 5 +++-- client/src/pages/contracts/contracts.page.container.jsx | 5 +++-- .../courtesy-car-detail.page.container.jsx | 5 +++-- client/src/pages/export-logs/export-logs.page.component.jsx | 4 ++-- client/src/pages/jobs-all/jobs-all.container.jsx | 5 +++-- .../src/pages/payments-all/payments-all.container.page.jsx | 5 +++-- client/src/pages/phonebook/phonebook.page.component.jsx | 4 ++-- client/src/pages/shop-csi/shop-csi.container.page.jsx | 5 +++-- 12 files changed, 32 insertions(+), 23 deletions(-) diff --git a/client/src/components/inventory-list/inventory-list.container.jsx b/client/src/components/inventory-list/inventory-list.container.jsx index bb265060f..e98266464 100644 --- a/client/src/components/inventory-list/inventory-list.container.jsx +++ b/client/src/components/inventory-list/inventory-list.container.jsx @@ -11,6 +11,7 @@ import { } from "../../redux/application/application.actions"; import AlertComponent from "../alert/alert.component"; import InventoryListPaginated from "./inventory-list.component"; +import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ //bodyshop: selectBodyshop, @@ -32,8 +33,8 @@ export function InventoryList({ setBreadcrumbs, setSelectedHeader }) { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, consumedIsNull: showall === "true" ? null : true, order: [ { diff --git a/client/src/components/jobs-list/jobs-list.component.jsx b/client/src/components/jobs-list/jobs-list.component.jsx index 885b53634..b7bd3141d 100644 --- a/client/src/components/jobs-list/jobs-list.component.jsx +++ b/client/src/components/jobs-list/jobs-list.component.jsx @@ -38,7 +38,7 @@ export function JobsList({bodyshop,}) { const {loading, error, data, refetch} = useQuery(QUERY_ALL_ACTIVE_JOBS_PAGINATED, { variables: { offset: page ? (page - 1) * pageLimit : 0, - limit: 10, + limit: pageLimit, statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"], order: [ { diff --git a/client/src/components/owners-list/owners-list.container.jsx b/client/src/components/owners-list/owners-list.container.jsx index 78db6bf2b..7303c4222 100644 --- a/client/src/components/owners-list/owners-list.container.jsx +++ b/client/src/components/owners-list/owners-list.container.jsx @@ -5,6 +5,7 @@ import AlertComponent from "../alert/alert.component"; import OwnersListComponent from "./owners-list.component"; import queryString from "query-string"; import { useLocation } from "react-router-dom"; +import {pageLimit} from "../../utils/config"; export default function OwnersListContainer() { const searchParams = queryString.parse(useLocation().search); @@ -16,8 +17,8 @@ export default function OwnersListContainer() { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ { [sortcolumn || "created_at"]: sortorder diff --git a/client/src/components/vehicles-list/vehicles-list.container.jsx b/client/src/components/vehicles-list/vehicles-list.container.jsx index 34fc4940b..2e85236f8 100644 --- a/client/src/components/vehicles-list/vehicles-list.container.jsx +++ b/client/src/components/vehicles-list/vehicles-list.container.jsx @@ -5,6 +5,7 @@ import AlertComponent from "../alert/alert.component"; import { QUERY_ALL_VEHICLES_PAGINATED } from "../../graphql/vehicles.queries"; import queryString from "query-string"; import { useLocation } from "react-router-dom"; +import {pageLimit} from "../../utils/config"; export default function VehiclesListContainer() { const searchParams = queryString.parse(useLocation().search); @@ -15,8 +16,8 @@ export default function VehiclesListContainer() { { variables: { search: search || "", - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ { [sortcolumn || "created_at"]: sortorder diff --git a/client/src/pages/bills/bills.page.container.jsx b/client/src/pages/bills/bills.page.container.jsx index 22a8b61b9..40e662899 100644 --- a/client/src/pages/bills/bills.page.container.jsx +++ b/client/src/pages/bills/bills.page.container.jsx @@ -13,6 +13,7 @@ import { setSelectedHeader, } from "../../redux/application/application.actions"; import BillsPageComponent from "./bills.page.component"; +import {pageLimit} from "../../utils/config"; const mapDispatchToProps = (dispatch) => ({ setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), @@ -38,8 +39,8 @@ export function BillsPageContainer({ setBreadcrumbs, setSelectedHeader }) { fetchPolicy: "network-only", nextFetchPolicy: "network-only", variables: { - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ searchObj ? JSON.parse(searchObj) diff --git a/client/src/pages/contracts/contracts.page.container.jsx b/client/src/pages/contracts/contracts.page.container.jsx index c4f4fef7b..466b468e0 100644 --- a/client/src/pages/contracts/contracts.page.container.jsx +++ b/client/src/pages/contracts/contracts.page.container.jsx @@ -12,6 +12,7 @@ import { setSelectedHeader, } from "../../redux/application/application.actions"; import ContractsPageComponent from "./contracts.page.component"; +import {pageLimit} from "../../utils/config"; const mapDispatchToProps = (dispatch) => ({ setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), @@ -29,8 +30,8 @@ export function ContractsPageContainer({ setBreadcrumbs, setSelectedHeader }) { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ { [sortcolumn || "start"]: sortorder diff --git a/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx b/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx index e41e80f40..93bffe97f 100644 --- a/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx +++ b/client/src/pages/courtesy-car-detail/courtesy-car-detail.page.container.jsx @@ -19,6 +19,7 @@ import NotFound from "../../components/not-found/not-found.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import queryString from "query-string"; import { useLocation } from "react-router-dom"; +import {pageLimit} from "../../utils/config"; const mapDispatchToProps = (dispatch) => ({ setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), @@ -41,8 +42,8 @@ export function CourtesyCarDetailPageContainer({ const { loading, error, data } = useQuery(QUERY_CC_BY_PK, { variables: { id: ccId, - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ { [sortcolumn || "start"]: sortorder diff --git a/client/src/pages/export-logs/export-logs.page.component.jsx b/client/src/pages/export-logs/export-logs.page.component.jsx index 0c6517a58..7ae8977cf 100644 --- a/client/src/pages/export-logs/export-logs.page.component.jsx +++ b/client/src/pages/export-logs/export-logs.page.component.jsx @@ -30,8 +30,8 @@ export function ExportLogsPageComponent({ bodyshop }) { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ { [sortcolumn || "created_at"]: sortorder diff --git a/client/src/pages/jobs-all/jobs-all.container.jsx b/client/src/pages/jobs-all/jobs-all.container.jsx index dd78369a0..b9fa9a52a 100644 --- a/client/src/pages/jobs-all/jobs-all.container.jsx +++ b/client/src/pages/jobs-all/jobs-all.container.jsx @@ -13,6 +13,7 @@ import { setBreadcrumbs, setSelectedHeader, } from "../../redux/application/application.actions"; +import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ //bodyshop: selectBodyshop, @@ -33,8 +34,8 @@ export function AllJobs({ setBreadcrumbs, setSelectedHeader }) { fetchPolicy: "network-only", nextFetchPolicy: "network-only", variables: { - offset: page ? (page - 1) * 10 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, ...(statusFilters ? { statusList: JSON.parse(statusFilters) } : {}), order: [ { diff --git a/client/src/pages/payments-all/payments-all.container.page.jsx b/client/src/pages/payments-all/payments-all.container.page.jsx index 07547bc2e..3d3cb6287 100644 --- a/client/src/pages/payments-all/payments-all.container.page.jsx +++ b/client/src/pages/payments-all/payments-all.container.page.jsx @@ -14,6 +14,7 @@ import { setSelectedHeader, } from "../../redux/application/application.actions"; import { selectBodyshop } from "../../redux/user/user.selectors"; +import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -34,8 +35,8 @@ export function AllJobs({ bodyshop, setBreadcrumbs, setSelectedHeader }) { fetchPolicy: "network-only", nextFetchPolicy: "network-only", variables: { - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ searchObj ? JSON.parse(searchObj) diff --git a/client/src/pages/phonebook/phonebook.page.component.jsx b/client/src/pages/phonebook/phonebook.page.component.jsx index baf4e18ec..5ce863444 100644 --- a/client/src/pages/phonebook/phonebook.page.component.jsx +++ b/client/src/pages/phonebook/phonebook.page.component.jsx @@ -36,8 +36,8 @@ export function PhonebookPageComponent({ bodyshop, authLevel }) { nextFetchPolicy: "network-only", variables: { search: search || "", - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ { [sortcolumn || "lastname"]: sortorder diff --git a/client/src/pages/shop-csi/shop-csi.container.page.jsx b/client/src/pages/shop-csi/shop-csi.container.page.jsx index 132ee7999..f0c295685 100644 --- a/client/src/pages/shop-csi/shop-csi.container.page.jsx +++ b/client/src/pages/shop-csi/shop-csi.container.page.jsx @@ -16,6 +16,7 @@ import { } from "../../redux/application/application.actions"; import { selectBodyshop } from "../../redux/user/user.selectors"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; +import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, }); @@ -42,8 +43,8 @@ export function ShopCsiContainer({ nextFetchPolicy: "network-only", variables: { //search: search || "", - offset: page ? (page - 1) * 25 : 0, - limit: 25, + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, order: [ { [sortcolumn || "completedon"]: sortorder From 25429e78f8d711b1d2efb4430f4d1371ccc4d57f Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Mon, 8 Jan 2024 10:37:19 -0800 Subject: [PATCH 05/67] IO-2518 Null coalesce for v_vin for warning --- .../jobs-detail-header/jobs-detail-header.component.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx b/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx index 9ae08588d..958f7f1d5 100644 --- a/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx +++ b/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx @@ -222,7 +222,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) { {`${job.v_vin || t("general.labels.na")}`} {bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? ( - job.v_vin.length !== 17 ? ( + job.v_vin?.length !== 17 ? ( ) : null ) : null} From 1305277c092c3d589f18933fb1750cf6c05f671a Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Tue, 9 Jan 2024 11:08:02 -0800 Subject: [PATCH 06/67] IO-2520 Kaizen Data Pump --- server.js | 1 + server/data/claimscorp.js | 2 +- server/data/data.js | 3 +- server/data/kaizen.js | 830 +++++++++++++++++++++++++++++++ server/graphql-client/queries.js | 199 +++++++- 5 files changed, 1032 insertions(+), 3 deletions(-) create mode 100644 server/data/kaizen.js diff --git a/server.js b/server.js index f16d583d5..a64545b72 100644 --- a/server.js +++ b/server.js @@ -224,6 +224,7 @@ app.post("/qbo/payments", fb.validateFirebaseIdToken, qbo.payments); var data = require("./server/data/data"); app.post("/data/ah", data.autohouse); app.post("/data/cc", data.claimscorp); +app.post("/data/kaizen", data.kaizen); app.post("/record-handler/arms", data.arms); var taskHandler = require("./server/tasks/tasks"); diff --git a/server/data/claimscorp.js b/server/data/claimscorp.js index 60943aed3..1768f6eb1 100644 --- a/server/data/claimscorp.js +++ b/server/data/claimscorp.js @@ -507,7 +507,7 @@ const CreateRepairOrderTag = (job, errorCallback) => { Body: repairCosts.BodyLaborTotalCost.toFormat(CCDineroFormat), Paint: repairCosts.RefinishLaborTotalCost.toFormat(CCDineroFormat), Prep: Dinero().toFormat(CCDineroFormat), - Frame: Dinero(job.job_totals.rates.laf.total).toFormat(CCDineroFormat), + Frame: repairCosts.FrameLaborTotalCost.toFormat(CCDineroFormat), Mech: repairCosts.MechanicalLaborTotalCost.toFormat(CCDineroFormat), Glass: repairCosts.GlassLaborTotalCost.toFormat(CCDineroFormat), Elec: repairCosts.ElectricalLaborTotalCost.toFormat(CCDineroFormat), diff --git a/server/data/data.js b/server/data/data.js index 077f9f134..18ec4c321 100644 --- a/server/data/data.js +++ b/server/data/data.js @@ -1,3 +1,4 @@ +exports.arms = require("./arms").default; exports.autohouse = require("./autohouse").default; exports.claimscorp = require("./claimscorp").default; -exports.arms = require("./arms").default; \ No newline at end of file +exports.kaizen = require("./kaizen").default; \ No newline at end of file diff --git a/server/data/kaizen.js b/server/data/kaizen.js new file mode 100644 index 000000000..57c354e4e --- /dev/null +++ b/server/data/kaizen.js @@ -0,0 +1,830 @@ +const path = require("path"); +const queries = require("../graphql-client/queries"); +const Dinero = require("dinero.js"); +const moment = require("moment-timezone"); +var builder = require("xmlbuilder2"); +const _ = require("lodash"); +const logger = require("../utils/logger"); +const fs = require("fs"); +require("dotenv").config({ + path: path.resolve( + process.cwd(), + `.env.${process.env.NODE_ENV || "development"}` + ), +}); +let Client = require("ssh2-sftp-client"); + +const client = require("../graphql-client/graphql-client").client; +const { sendServerEmail } = require("../email/sendemail"); +const DineroFormat = "0,0.00"; +const DateFormat = "MM/DD/YYYY"; + +const repairOpCodes = ["OP4", "OP9", "OP10"]; +const replaceOpCodes = ["OP2", "OP5", "OP11", "OP12"]; + +const ftpSetup = { + host: process.env.KAIZEN_HOST, + port: process.env.KAIZEN_PORT, + username: process.env.KAIZEN_USER, + password: process.env.KAIZEN_PASSWORD, + debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data), + algorithms: { + serverHostKey: ["ssh-rsa", "ssh-dss"], + }, +}; + +exports.default = async (req, res) => { + //Query for the List of Bodyshop Clients. + logger.log("kaizen-start", "DEBUG", "api", null, null); + const kaizenShopsNames = ["SUMMIT", "STRATHMORE", "SUNRIDGE"]; + + const { bodyshops } = await client.request(queries.GET_KAIZEN_SHOPS, { + shopname: kaizenShopsNames, + }); + + const specificShopIds = req.body.bodyshopIds; // ['uuid] + const { start, end, skipUpload } = req.body; //YYYY-MM-DD + if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) { + res.sendStatus(401); + return; + } + const allxmlsToUpload = []; + const allErrors = []; + try { + for (const bodyshop of specificShopIds + ? bodyshops.filter((b) => specificShopIds.includes(b.id)) + : bodyshops) { + logger.log("kaizen-start-shop-extract", "DEBUG", "api", bodyshop.id, { + shopname: bodyshop.shopname, + }); + const erroredJobs = []; + try { + const { jobs, bodyshops_by_pk } = await client.request( + queries.KAIZEN_QUERY, + { + bodyshopid: bodyshop.id, + start: start + ? moment(start).startOf("day") + : moment().subtract(5, "days").startOf("day"), + ...(end && { end: moment(end).endOf("day") }), + } + ); + + const kaizenObject = { + DataFeed: { + ShopInfo: { + ShopName: bodyshops_by_pk.shopname, + Jobs: jobs.map((j) => + CreateRepairOrderTag( + { ...j, bodyshop: bodyshops_by_pk }, + function ({ job, error }) { + erroredJobs.push({ job: job, error: error.toString() }); + } + ) + ), + }, + }, + }; + + if (erroredJobs.length > 0) { + logger.log("kaizen-failed-jobs", "ERROR", "api", bodyshop.id, { + count: erroredJobs.length, + jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number)), + }); + } + + var ret = builder + .create( + { + // version: "1.0", + // encoding: "UTF-8", + //keepNullNodes: true, + }, + kaizenObject + ) + .end({ allowEmptyTags: true }); + + allxmlsToUpload.push({ + count: kaizenObject.DataFeed.ShopInfo.Jobs.length, + xml: ret, + filename: `${bodyshop.shopname}-${moment().format( + "YYYYMMDDTHHMMss" + )}.xml`, + }); + + logger.log("kaizen-end-shop-extract", "DEBUG", "api", bodyshop.id, { + shopname: bodyshop.shopname, + }); + } catch (error) { + //Error at the shop level. + logger.log("kaizen-error-shop", "ERROR", "api", bodyshop.id, { + ...error, + }); + + allErrors.push({ + bodyshopid: bodyshop.id, + imexshopid: bodyshop.imexshopid, + shopname: bodyshop.shopname, + fatal: true, + errors: [error.toString()], + }); + } finally { + allErrors.push({ + bodyshopid: bodyshop.id, + imexshopid: bodyshop.imexshopid, + shopname: bodyshop.shopname, + errors: erroredJobs.map((ej) => ({ + ro_number: ej.job?.ro_number, + jobid: ej.job?.id, + error: ej.error, + })), + }); + } + } + + if (skipUpload) { + for (const xmlObj of allxmlsToUpload) { + fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml); + } + + res.json(allxmlsToUpload); + 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 + )} + `, + }); + return; + } + + let sftp = new Client(); + sftp.on("error", (errors) => + logger.log("kaizen-sftp-error", "ERROR", "api", null, { + ...errors, + }) + ); + try { + //Connect to the FTP and upload all. + + await sftp.connect(ftpSetup); + + for (const xmlObj of allxmlsToUpload) { + logger.log("kaizen-sftp-upload", "DEBUG", "api", null, { + filename: xmlObj.filename, + }); + + const uploadResult = await sftp.put( + Buffer.from(xmlObj.xml), + `/${xmlObj.filename}` + ); + logger.log("kaizen-sftp-upload-result", "DEBUG", "api", null, { + uploadResult, + }); + } + + //***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); + } catch (error) { + res.status(200).json(error); + } +}; + +const CreateRepairOrderTag = (job, errorCallback) => { + //Level 2 + + if (!job.job_totals) { + errorCallback({ + jobid: job.id, + job: job, + ro_number: job.ro_number, + error: { toString: () => "No job totals for RO." }, + }); + return {}; + } + + const repairCosts = CreateCosts(job); + + try { + const ret = { + JobID: job.id, + RoNumber: job.ro_number, + JobStatus: job.tlos_ind + ? "Total Loss" + : job.ro_number + ? job.status + : "Estimate", + Customer: { + CompanyName: job.ownr_co_nm?.trim() || "", + FirstName: job.ownr_fn?.trim() || "", + LastName: job.ownr_ln?.trim() || "", + Address1: job.ownr_addr1?.trim() || "", + Address2: job.ownr_addr2?.trim() || "", + City: job.ownr_city?.trim() || "", + State: job.ownr_st?.trim() || "", + Zip: job.ownr_zip?.trim() || "", + }, + Vehicle: { + Year: job.v_model_yr + ? parseInt(job.v_model_yr.match(/\d/g)) + ? parseInt(job.v_model_yr.match(/\d/g).join(""), 10) + : "" + : "", + Make: job.v_make_desc || "", + Model: job.v_model_desc || "", + BodyStyle: job.vehicle?.v_bstyle || "", + Color: job.v_color || "", + VIN: job.v_vin || "", + PlateNo: job.plate_no || "", + }, + InsuranceCompany: job.ins_co_nm || "", + Claim: job.clm_no || "", + Contacts: { + CSR: job.employee_csr_rel + ? `${ + job.employee_csr_rel.last_name + ? job.employee_csr_rel.last_name + : "" + }${job.employee_csr_rel.last_name ? ", " : ""}${ + job.employee_csr_rel.first_name + ? job.employee_csr_rel.first_name + : "" + }` + : "", + Estimator: `${job.est_ct_ln ? job.est_ct_ln : ""}${ + job.est_ct_ln ? ", " : "" + }${job.est_ct_fn ? job.est_ct_fn : ""}`, + }, + Dates: { + DateEstimated: + (job.date_estimated && + moment(job.date_estimated).format(DateFormat)) || + "", + DateOpened: + (job.date_opened && moment(job.date_opened).format(DateFormat)) || "", + DateScheduled: + (job.scheduled_in && + moment(job.scheduled_in) + .tz(job.bodyshop.timezone) + .format(DateFormat)) || + "", + DateArrived: + (job.actual_in && + moment(job.actual_in) + .tz(job.bodyshop.timezone) + .format(DateFormat)) || + "", + DateStart: job.date_repairstarted + ? (job.date_repairstarted && + moment(job.date_repairstarted) + .tz(job.bodyshop.timezone) + .format(DateFormat)) || + "" + : (job.actual_in && + moment(job.actual_in) + .tz(job.bodyshop.timezone) + .format(DateFormat)) || + "", + DateScheduledCompletion: + (job.scheduled_completion && + moment(job.scheduled_completion) + .tz(job.bodyshop.timezone) + .format(DateFormat)) || + "", + DateCompleted: + (job.actual_completion && + moment(job.actual_completion) + .tz(job.bodyshop.timezone) + .format(DateFormat)) || + "", + DateScheduledDelivery: + (job.scheduled_delivery && + moment(job.scheduled_delivery) + .tz(job.bodyshop.timezone) + .format(DateFormat)) || + "", + DateDelivered: + (job.actual_delivery && + moment(job.actual_delivery) + .tz(job.bodyshop.timezone) + .format(DateFormat)) || + "", + DateInvoiced: + (job.date_invoiced && + moment(job.date_invoiced) + .tz(job.bodyshop.timezone) + .format(DateFormat)) || + "", + DateExported: + (job.date_exported && + moment(job.date_exported) + .tz(job.bodyshop.timezone) + .format(DateFormat)) || + "", + }, + Sales: { + Labour: { + Aluminum: Dinero(job.job_totals.rates.laa.total).toFormat( + DineroFormat + ), + Body: Dinero(job.job_totals.rates.lab.total).toFormat(DineroFormat), + Diagnostic: Dinero(job.job_totals.rates.lad.total).toFormat( + DineroFormat + ), + Electrical: Dinero(job.job_totals.rates.lae.total).toFormat( + DineroFormat + ), + Frame: Dinero(job.job_totals.rates.laf.total).toFormat(DineroFormat), + Glass: Dinero(job.job_totals.rates.lag.total).toFormat(DineroFormat), + Mechanical: Dinero(job.job_totals.rates.lam.total).toFormat( + DineroFormat + ), + OtherLabour: Dinero(job.job_totals.rates.la1.total) + .add(Dinero(job.job_totals.rates.la2.total)) + .add(Dinero(job.job_totals.rates.la3.total)) + .add(Dinero(job.job_totals.rates.la4.total)) + .add(Dinero(job.job_totals.rates.lau.total)) + .toFormat(DineroFormat), + Refinish: Dinero(job.job_totals.rates.lar.total).toFormat( + DineroFormat + ), + Structural: Dinero(job.job_totals.rates.las.total).toFormat( + DineroFormat + ), + }, + Materials: { + Body: Dinero(job.job_totals.rates.mash.total).toFormat(DineroFormat), + Refinish: Dinero(job.job_totals.rates.mapa.total).toFormat( + DineroFormat + ), + }, + Parts: { + Aftermarket: Dinero( + job.job_totals.parts.parts.list.PAA && + job.job_totals.parts.parts.list.PAA.total + ).toFormat(DineroFormat), + LKQ: Dinero( + job.job_totals.parts.parts.list.PAL && + job.job_totals.parts.parts.list.PAL.total + ).toFormat(DineroFormat), + OEM: Dinero( + job.job_totals.parts.parts.list.PAN && + job.job_totals.parts.parts.list.PAN.total + ) + .add( + Dinero( + job.job_totals.parts.parts.list.PAP && + job.job_totals.parts.parts.list.PAP.total + ) + ) + .toFormat(DineroFormat), + OtherParts: Dinero( + job.job_totals.parts.parts.list.PAO && + job.job_totals.parts.parts.list.PAO.total + ).toFormat(DineroFormat), + Reconditioned: Dinero( + job.job_totals.parts.parts.list.PAM && + job.job_totals.parts.parts.list.PAM.total + ).toFormat(DineroFormat), + TotalParts: Dinero( + job.job_totals.parts.parts.list.PAA && + job.job_totals.parts.parts.list.PAA.total + ) + .add( + Dinero( + job.job_totals.parts.parts.list.PAL && + job.job_totals.parts.parts.list.PAL.total + ) + ) + .add( + Dinero( + job.job_totals.parts.parts.list.PAN && + job.job_totals.parts.parts.list.PAN.total + ) + ) + .add( + Dinero( + job.job_totals.parts.parts.list.PAO && + job.job_totals.parts.parts.list.PAO.total + ) + ) + .add( + Dinero( + job.job_totals.parts.parts.list.PAM && + job.job_totals.parts.parts.list.PAM.total + ) + ) + .toFormat(DineroFormat), + }, + OtherSales: Dinero(job.job_totals.additional.storage).toFormat( + DineroFormat + ), + Sublet: Dinero(job.job_totals.parts.sublets.total).toFormat( + DineroFormat + ), + Towing: Dinero(job.job_totals.additional.towing).toFormat(DineroFormat), + ATS: + job.job_totals.additional.additionalCostItems.includes( + "ATS Amount" + ) === true + ? Dinero( + job.job_totals.additional.additionalCostItems[ + job.job_totals.additional.additionalCostItems.indexOf( + "ATS Amount" + ) + ].total + ).toFormat(DineroFormat) + : Dinero().toFormat(DineroFormat), + SaleSubtotal: Dinero(job.job_totals.totals.subtotal).toFormat( + DineroFormat + ), + Tax: Dinero(job.job_totals.totals.local_tax) + .add(Dinero(job.job_totals.totals.state_tax)) + .add(Dinero(job.job_totals.totals.federal_tax)) + .add(Dinero(job.job_totals.additional.pvrt)) + .toFormat(DineroFormat), + SaleTotal: Dinero(job.job_totals.totals.total_repairs).toFormat( + DineroFormat + ), + }, + SaleHours: { + Aluminum: job.job_totals.rates.laa.hours.toFixed(2), + Body: job.job_totals.rates.lab.hours.toFixed(2), + Diagnostic: job.job_totals.rates.lad.hours.toFixed(2), + Electrical: job.job_totals.rates.lae.hours.toFixed(2), + Frame: job.job_totals.rates.laf.hours.toFixed(2), + Glass: job.job_totals.rates.lag.hours.toFixed(2), + Mechanical: job.job_totals.rates.lam.hours.toFixed(2), + Other: ( + job.job_totals.rates.la1.hours + + job.job_totals.rates.la2.hours + + job.job_totals.rates.la3.hours + + job.job_totals.rates.la4.hours + + job.job_totals.rates.lau.hours + ).toFixed(2), + Refinish: job.job_totals.rates.lar.hours.toFixed(2), + Structural: job.job_totals.rates.las.hours.toFixed(2), + TotalHours: job.joblines + .reduce((acc, val) => acc + val.mod_lb_hrs, 0) + .toFixed(2), + }, + Costs: { + Labour: { + Aluminum: repairCosts.AluminumLabourTotalCost.toFormat(DineroFormat), + Body: repairCosts.BodyLabourTotalCost.toFormat(DineroFormat), + Diagnostic: + repairCosts.DiagnosticLabourTotalCost.toFormat(DineroFormat), + Electrical: + repairCosts.ElectricalLabourTotalCost.toFormat(DineroFormat), + Frame: repairCosts.FrameLabourTotalCost.toFormat(DineroFormat), + Glass: repairCosts.GlassLabourTotalCost.toFormat(DineroFormat), + Mechancial: + repairCosts.MechanicalLabourTotalCost.toFormat(DineroFormat), + OtherLabour: repairCosts.LabourMiscTotalCost.toFormat(DineroFormat), + Refinish: repairCosts.RefinishLabourTotalCost.toFormat(DineroFormat), + Structural: + repairCosts.StructuralLabourTotalCost.toFormat(DineroFormat), + TotalLabour: repairCosts.LabourTotalCost.toFormat(DineroFormat), + }, + Materials: { + Body: repairCosts.BMTotalCost.toFormat(DineroFormat), + Refinish: repairCosts.PMTotalCost.toFormat(DineroFormat), + }, + Parts: { + Aftermarket: repairCosts.PartsAMCost.toFormat(DineroFormat), + LKQ: repairCosts.PartsRecycledCost.toFormat(DineroFormat), + OEM: repairCosts.PartsOemCost.toFormat(DineroFormat), + OtherCost: repairCosts.PartsOtherCost.toFormat(DineroFormat), + Reconditioned: + repairCosts.PartsReconditionedCost.toFormat(DineroFormat), + TotalParts: repairCosts.PartsAMCost.add(repairCosts.PartsRecycledCost) + .add(repairCosts.PartsReconditionedCost) + .add(repairCosts.PartsOemCost) + .add(repairCosts.PartsOtherCost) + .toFormat(DineroFormat), + }, + Sublet: repairCosts.SubletTotalCost.toFormat(DineroFormat), + Towing: repairCosts.TowingTotalCost.toFormat(DineroFormat), + ATS: Dinero().toFormat(DineroFormat), + Storage: repairCosts.StorageTotalCost.toFormat(DineroFormat), + CostTotal: repairCosts.TotalCost.toFormat(DineroFormat), + }, + CostHours: { + Aluminum: repairCosts.AluminumLabourTotalHrs.toFixed(2), + Body: repairCosts.BodyLabourTotalHrs.toFixed(2), + Diagnostic: repairCosts.DiagnosticLabourTotalHrs.toFixed(2), + Refinish: repairCosts.RefinishLabourTotalHrs.toFixed(2), + Frame: repairCosts.FrameLabourTotalHrs.toFixed(2), + Mechanical: repairCosts.MechanicalLabourTotalHrs.toFixed(2), + Glass: repairCosts.GlassLabourTotalHrs.toFixed(2), + Electrical: repairCosts.ElectricalLabourTotalHrs.toFixed(2), + Structural: repairCosts.StructuralLabourTotalHrs.toFixed(2), + Other: repairCosts.LabourMiscTotalHrs.toFixed(2), + CostTotalHours: repairCosts.TotalHrs.toFixed(2), + }, + }; + return ret; + } catch (error) { + logger.log("kaizen-job-calculate-error", "ERROR", "api", null, { + error, + }); + + errorCallback({ jobid: job.id, ro_number: job.ro_number, error }); + } +}; + +const CreateCosts = (job) => { + //Create a mapping based on AH Requirements + + //For DMS, the keys in the object below are the CIECA part types. + const billTotalsByCostCenters = job.bills.reduce((bill_acc, bill_val) => { + //At the bill level. + bill_val.billlines.map((line_val) => { + //At the bill line level. + + if (!bill_acc[line_val.cost_center]) + bill_acc[line_val.cost_center] = Dinero(); + + bill_acc[line_val.cost_center] = bill_acc[line_val.cost_center].add( + Dinero({ + amount: Math.round((line_val.actual_cost || 0) * 100), + }) + .multiply(line_val.quantity) + .multiply(bill_val.is_credit_memo ? -1 : 1) + ); + + return null; + }); + return bill_acc; + }, {}); + + //If the hourly rates for job costing are set, add them in. + if ( + job.bodyshop.jc_hourly_rates && + (job.bodyshop.jc_hourly_rates.mapa || + typeof job.bodyshop.jc_hourly_rates.mapa === "number" || + isNaN(job.bodyshop.jc_hourly_rates.mapa) === false) + ) { + if ( + !billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MAPA + ] + ) + billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MAPA + ] = Dinero(); + if (job.bodyshop.use_paint_scale_data === true) { + if (job.mixdata.length > 0) { + billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MAPA + ] = Dinero({ + amount: Math.round( + ((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) * 100 + ), + }); + } else { + billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MAPA + ] = billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MAPA + ].add( + Dinero({ + amount: Math.round( + (job.bodyshop.jc_hourly_rates && + job.bodyshop.jc_hourly_rates.mapa * 100) || + 0 + ), + }).multiply(job.job_totals.rates.mapa.hours) + ); + } + } else { + billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MAPA + ] = billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MAPA + ].add( + Dinero({ + amount: Math.round( + (job.bodyshop.jc_hourly_rates && + job.bodyshop.jc_hourly_rates.mapa * 100) || + 0 + ), + }).multiply(job.job_totals.rates.mapa.hours) + ); + } + } + if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mash) { + if ( + !billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MASH + ] + ) + billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MASH + ] = Dinero(); + billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MASH + ] = billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MASH + ].add( + Dinero({ + amount: Math.round( + (job.bodyshop.jc_hourly_rates && + job.bodyshop.jc_hourly_rates.mash * 100) || + 0 + ), + }).multiply(job.job_totals.rates.mash.hours) + ); + } + //Uses CIECA Labour types. + const ticketTotalsByCostCenter = job.timetickets.reduce( + (ticket_acc, ticket_val) => { + //At the invoice level. + if (!ticket_acc[ticket_val.cost_center]) + ticket_acc[ticket_val.cost_center] = Dinero(); + + ticket_acc[ticket_val.cost_center] = ticket_acc[ + ticket_val.cost_center + ].add( + Dinero({ + amount: Math.round((ticket_val.rate || 0) * 100), + }).multiply( + (ticket_val.flat_rate + ? ticket_val.productivehrs + : ticket_val.actualhrs) || 0 + ) + ); + + return ticket_acc; + }, + {} + ); + const ticketHrsByCostCenter = job.timetickets.reduce( + (ticket_acc, ticket_val) => { + //At the invoice level. + if (!ticket_acc[ticket_val.cost_center]) + ticket_acc[ticket_val.cost_center] = 0; + + ticket_acc[ticket_val.cost_center] = + ticket_acc[ticket_val.cost_center] + + (ticket_val.flat_rate + ? ticket_val.productivehrs + : ticket_val.actualhrs) || 0; + + return ticket_acc; + }, + {} + ); + //CIECA STANDARD MAPPING OBJECT. + + const ciecaObj = { + ATS: "ATS", + LA1: "LA1", + LA2: "LA2", + LA3: "LA3", + LA4: "LA4", + LAA: "LAA", + LAB: "LAB", + LAD: "LAD", + LAE: "LAE", + LAF: "LAF", + LAG: "LAG", + LAM: "LAM", + LAR: "LAR", + LAS: "LAS", + LAU: "LAU", + PAA: "PAA", + PAC: "PAC", + PAG: "PAG", + PAL: "PAL", + PAM: "PAM", + PAN: "PAN", + PAO: "PAO", + PAP: "PAP", + PAR: "PAR", + PAS: "PAS", + TOW: "TOW", + MAPA: "MAPA", + MASH: "MASH", + PASL: "PASL", + }; + const defaultCosts = + job.bodyshop.cdk_dealerid || job.bodyshop.pbs_serialnumber + ? ciecaObj + : job.bodyshop.md_responsibility_centers.defaults.costs; + + return { + PartsTotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => { + if ( + key !== defaultCosts.PAS && + key !== defaultCosts.PASL && + key !== defaultCosts.MAPA && + key !== defaultCosts.MASH && + key !== defaultCosts.TOW + ) + return acc.add(billTotalsByCostCenters[key]); + return acc; + }, Dinero()), + PartsOemCost: (billTotalsByCostCenters[defaultCosts.PAN] || Dinero()).add( + billTotalsByCostCenters[defaultCosts.PAP] || Dinero() + ), + PartsAMCost: billTotalsByCostCenters[defaultCosts.PAA] || Dinero(), + PartsReconditionedCost: + billTotalsByCostCenters[defaultCosts.PAM] || Dinero(), + PartsRecycledCost: billTotalsByCostCenters[defaultCosts.PAL] || Dinero(), + PartsOtherCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(), + + SubletTotalCost: + billTotalsByCostCenters[defaultCosts.PAS] || + Dinero(billTotalsByCostCenters[defaultCosts.PASL] || Dinero()), + + AluminumLabourTotalCost: + ticketTotalsByCostCenter[defaultCosts.LAA] || Dinero(), + AluminumLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAA] || 0, + BodyLabourTotalCost: ticketTotalsByCostCenter[defaultCosts.LAB] || Dinero(), + BodyLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAB] || 0, + DiagnosticLabourTotalCost: + ticketTotalsByCostCenter[defaultCosts.LAD] || Dinero(), + DiagnosticLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAD] || 0, + ElectricalLabourTotalCost: + ticketTotalsByCostCenter[defaultCosts.LAE] || Dinero(), + ElectricalLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAE] || 0, + FrameLabourTotalCost: + ticketTotalsByCostCenter[defaultCosts.LAF] || Dinero(), + FrameLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAF] || 0, + GlassLabourTotalCost: + ticketTotalsByCostCenter[defaultCosts.LAG] || Dinero(), + GlassLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAG] || 0, + LabourMiscTotalCost: ( + ticketTotalsByCostCenter[defaultCosts.LA1] || Dinero() + ) + .add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero()) + .add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero()) + .add(ticketTotalsByCostCenter[defaultCosts.LA3] || Dinero()) + .add(ticketTotalsByCostCenter[defaultCosts.LA4] || Dinero()) + .add(ticketTotalsByCostCenter[defaultCosts.LAU] || Dinero()), + LabourMiscTotalHrs: + (ticketHrsByCostCenter[defaultCosts.LA1] || 0) + + (ticketHrsByCostCenter[defaultCosts.LA2] || 0) + + (ticketHrsByCostCenter[defaultCosts.LA3] || 0) + + (ticketHrsByCostCenter[defaultCosts.LA4] || 0) + + (ticketHrsByCostCenter[defaultCosts.LAU] || 0), + MechanicalLabourTotalCost: + ticketTotalsByCostCenter[defaultCosts.LAM] || Dinero(), + MechanicalLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAM] || 0, + RefinishLabourTotalCost: + ticketTotalsByCostCenter[defaultCosts.LAR] || Dinero(), + RefinishLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAR] || 0, + StructuralLabourTotalCost: + ticketTotalsByCostCenter[defaultCosts.LAS] || Dinero(), + StructuralLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAS] || 0, + + PMTotalCost: billTotalsByCostCenters[defaultCosts.MAPA] || Dinero(), + BMTotalCost: billTotalsByCostCenters[defaultCosts.MASH] || Dinero(), + + MiscTotalCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(), + TowingTotalCost: billTotalsByCostCenters[defaultCosts.TOW] || Dinero(), + StorageTotalCost: Dinero(), + DetailTotal: Dinero(), + DetailTotalCost: Dinero(), + + SalesTaxTotalCost: Dinero(), + LabourTotalCost: Object.keys(ticketTotalsByCostCenter).reduce( + (acc, key) => { + return acc.add(ticketTotalsByCostCenter[key]); + }, + Dinero() + ), + TotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => { + return acc.add(billTotalsByCostCenters[key]); + }, Dinero()), + TotalHrs: job.timetickets.reduce((acc, ticket_val) => { + return ( + acc + + (ticket_val.flat_rate + ? ticket_val.productivehrs + : ticket_val.actualhrs) || 0 + ); + }, 0), + }; +}; diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index a27ac8bd0..554388afe 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -1070,6 +1070,183 @@ query ENTEGRAL_EXPORT($bodyshopid: uuid!) { } }`; +exports.KAIZEN_QUERY = `query KAIZEN_EXPORT($start: timestamptz, $bodyshopid: uuid!, $end: timestamptz) { + bodyshops_by_pk(id: $bodyshopid){ + id + shopname + address1 + city + state + zip_post + country + phone + last_name_first + md_ro_statuses + md_order_statuses + md_responsibility_centers + jc_hourly_rates + cdk_dealerid + pbs_serialnumber + use_paint_scale_data + timezone + } + jobs(where: {_and: [{updated_at: {_gt: $start}}, {updated_at: {_lte: $end}}, {shopid: {_eq: $bodyshopid}}]}) { + actual_completion + actual_delivery + actual_in + asgn_date + bills { + billlines { + actual_cost + cost_center + id + quantity + } + federal_tax_rate + id + is_credit_memo + local_tax_rate + state_tax_rate + } + created_at + clm_no + date_estimated + date_exported + date_invoiced + date_open + date_repairstarted + employee_body_rel { + first_name + last_name + employee_number + id + } + employee_csr_rel { + first_name + last_name + employee_number + id + } + employee_prep_rel { + first_name + last_name + employee_number + id + } + employee_refinish_rel { + first_name + last_name + employee_number + id + } + est_ct_fn + est_ct_ln + id + ins_co_nm + joblines(where: {removed: {_eq: false}}) { + act_price + billlines(order_by: {bill: {date: desc_nulls_last}} limit: 1) { + actual_cost + actual_price + quantity + bill { + vendor { + name + } + invoice_number + date + } + } + db_price + id + lbr_op + line_desc + line_ind + line_no + mod_lb_hrs + mod_lbr_ty + parts_order_lines(order_by: {parts_order: {order_date: desc_nulls_last}} limit: 1){ + parts_order{ + id + order_date + } + } + part_qty + part_type + profitcenter_part + profitcenter_labor + prt_dsmk_m + prt_dsmk_p + oem_partno + status + } + job_totals + loss_date + mixdata(limit: 1, order_by: {updated_at: desc}) { + jobid + totalliquidcost + } + ownr_addr1 + ownr_addr2 + ownr_city + ownr_co_nm + ownr_fn + ownr_ln + ownr_st + ownr_zip + parts_orders(limit: 1, order_by: {created_at: desc}) { + created_at + } + parts_tax_rates + plate_no + rate_la1 + rate_la2 + rate_la3 + rate_la4 + rate_laa + rate_lab + rate_lad + rate_lae + rate_laf + rate_lag + rate_lam + rate_lar + rate_las + rate_lau + rate_ma2s + rate_ma2t + rate_ma3s + rate_mabl + rate_macs + rate_mahw + rate_matd + rate_mapa + rate_mash + ro_number + scheduled_completion + scheduled_delivery + scheduled_in + status + timetickets { + id + rate + cost_center + actualhrs + productivehrs + flat_rate + } + tlos_ind + v_color + v_model_yr + v_model_desc + v_make_desc + v_vin + vehicle { + v_bstyle + } + } +}`; + exports.UPDATE_JOB = ` mutation UPDATE_JOB($jobId: uuid!, $job: jobs_set_input!) { update_jobs(where: { id: { _eq: $jobId } }, _set: $job) { @@ -1542,7 +1719,7 @@ exports.GET_CLAIMSCORP_SHOPS = `query GET_CLAIMSCORP_SHOPS { } }`; -exports.GET_ENTEGRAL_SHOPS = `query GET_AUTOHOUSE_SHOPS { +exports.GET_ENTEGRAL_SHOPS = `query GET_ENTEGRAL_SHOPS { bodyshops(where: {entegral_id: {_is_null: false}, _or: {entegral_id: {_neq: ""}}}){ id shopname @@ -1562,6 +1739,26 @@ exports.GET_ENTEGRAL_SHOPS = `query GET_AUTOHOUSE_SHOPS { } }`; +exports.GET_KAIZEN_SHOPS = `query GET_KAIZEN_SHOPS($shopname: [String]) { + bodyshops(where: {shopname: {_in: $shopname}}){ + id + shopname + address1 + city + state + zip_post + country + phone + md_ro_statuses + md_order_statuses + autohouseid + md_responsibility_centers + jc_hourly_rates + imexshopid + timezone + } +}`; + exports.DELETE_ALL_DMS_VEHICLES = `mutation DELETE_ALL_DMS_VEHICLES{ delete_dms_vehicles(where: {}) { affected_rows From 3e9279d89a9d27ec5ab4b6935937bdbbfbfef2f9 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Tue, 9 Jan 2024 12:00:24 -0800 Subject: [PATCH 07/67] IO-2520 Change Query Time Bound --- server/data/kaizen.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/data/kaizen.js b/server/data/kaizen.js index 57c354e4e..7cd7fef8c 100644 --- a/server/data/kaizen.js +++ b/server/data/kaizen.js @@ -64,9 +64,9 @@ exports.default = async (req, res) => { { bodyshopid: bodyshop.id, start: start - ? moment(start).startOf("day") - : moment().subtract(5, "days").startOf("day"), - ...(end && { end: moment(end).endOf("day") }), + ? moment(start).startOf("hours") + : moment().subtract(2, "hours").startOf("hour"), + ...(end && { end: moment(end).endOf("hours") }), } ); From 02b6875eecd5ed98e61a88228cfea9a37bcd942e Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Fri, 12 Jan 2024 15:41:10 -0800 Subject: [PATCH 08/67] IO-2602 Beta domain --- server.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server.js b/server.js index a64545b72..5eebba671 100644 --- a/server.js +++ b/server.js @@ -34,6 +34,10 @@ const io = new Server(server, { "http://localhost:3000", "https://imex.online", "https://www.imex.online", + "https://beta.test.imex.online", + "https://www.beta.test.imex.online", + "https://beta.imex.online", + "https://www.beta.imex.online", ], methods: ["GET", "POST"], credentials: true, From 04cff4acb15d806877592077a96dc9cd27e053a1 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Fri, 12 Jan 2024 16:26:09 -0800 Subject: [PATCH 09/67] IO-2520 Add in Server Key format --- server/data/kaizen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/data/kaizen.js b/server/data/kaizen.js index 7cd7fef8c..0df82a9ce 100644 --- a/server/data/kaizen.js +++ b/server/data/kaizen.js @@ -29,7 +29,7 @@ const ftpSetup = { password: process.env.KAIZEN_PASSWORD, debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data), algorithms: { - serverHostKey: ["ssh-rsa", "ssh-dss"], + serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"], }, }; From 4c4e16b0c902cf0ba55f9bfd060c0cd48ffc35d0 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 12 Jan 2024 20:47:06 -0500 Subject: [PATCH 10/67] - Add in the Beta Switch on test Signed-off-by: Dave Richer --- client/src/App/App.jsx | 7 ++++ .../components/header/header.component.jsx | 30 +++++++++++++-- client/src/utils/handleBeta.js | 37 +++++++++++++++++++ 3 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 client/src/utils/handleBeta.js diff --git a/client/src/App/App.jsx b/client/src/App/App.jsx index 618366db4..7d396eef8 100644 --- a/client/src/App/App.jsx +++ b/client/src/App/App.jsx @@ -22,6 +22,7 @@ import { } from "../redux/user/user.selectors"; import PrivateRoute from "../utils/private-route"; import "./App.styles.scss"; +import handleBeta from "../utils/handleBeta"; const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component") @@ -53,6 +54,12 @@ export function App({ }) { const client = useClient(); + // Handle The Beta Switch. + useEffect(() => { + handleBeta(); + }, []) + + useEffect(() => { if (!navigator.onLine) { setOnline(false); diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx index 8d180bdd1..404792639 100644 --- a/client/src/components/header/header.component.jsx +++ b/client/src/components/header/header.component.jsx @@ -13,7 +13,7 @@ import Icon, { FileFilled, //GlobalOutlined, HomeFilled, - ImportOutlined, + ImportOutlined, InfoCircleOutlined, LineChartOutlined, PaperClipOutlined, PhoneOutlined, @@ -26,8 +26,8 @@ import Icon, { UserOutlined, } from "@ant-design/icons"; import { useTreatments } from "@splitsoftware/splitio-react"; -import { Layout, Menu } from "antd"; -import React from "react"; +import {Layout, Menu, Switch, Tooltip} from "antd"; +import React, {useEffect, useState} from "react"; import { useTranslation } from "react-i18next"; import { BsKanban } from "react-icons/bs"; import { @@ -52,6 +52,7 @@ import { selectBodyshop, selectCurrentUser, } from "../../redux/user/user.selectors"; +import {handleBeta, setBeta, checkBeta} from "../../utils/handleBeta"; const mapStateToProps = createStructuredSelector({ currentUser: selectCurrentUser, @@ -102,9 +103,21 @@ function Header({ {}, bodyshop && bodyshop.imexshopid ); + const [betaSwitch, setBetaSwitch] = useState(false); const { t } = useTranslation(); + useEffect(() => { + const isBeta = checkBeta(); + setBetaSwitch(isBeta); + }, []); + + const betaSwitchChange = (checked) => { + setBeta(checked); + setBetaSwitch(checked); + handleBeta(); + } + return ( ))} + + + + Try the new ImEX Online + + + + ); diff --git a/client/src/utils/handleBeta.js b/client/src/utils/handleBeta.js new file mode 100644 index 000000000..155c09f9e --- /dev/null +++ b/client/src/utils/handleBeta.js @@ -0,0 +1,37 @@ +import React, {useState} from "react"; + +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')) { + window.location.href = `${window.location.protocol}//beta.${currentHostName}${window.location.pathname}${window.location.search}${window.location.hash}`; + } + + // Beta is not enabled, but the current host name does start with beta. + else if (!isBeta && currentHostName.startsWith('beta')) { + window.location.href = `${window.location.protocol}//${currentHostName.replace('beta.', '')}${window.location.pathname}${window.location.search}${window.location.hash}`; + } +} +export default handleBeta; From 3704c0cb12a23b4f3fd2b201b3f24f7b1fb492a4 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 12 Jan 2024 20:50:40 -0500 Subject: [PATCH 11/67] - Add in the Beta Switch on test Signed-off-by: Dave Richer --- client/src/utils/handleBeta.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/src/utils/handleBeta.js b/client/src/utils/handleBeta.js index 155c09f9e..428d4fb49 100644 --- a/client/src/utils/handleBeta.js +++ b/client/src/utils/handleBeta.js @@ -1,5 +1,3 @@ -import React, {useState} from "react"; - export const BETA_KEY = 'betaSwitchImex'; export const checkBeta = () => { From ebe5c5b11397485e9a45f0e184b6aabffb3caac8 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Fri, 12 Jan 2024 21:06:39 -0800 Subject: [PATCH 12/67] IO-2520 Adjust to imexshopid instead of shopname & prettify --- server/data/kaizen.js | 13 ++++++++++--- server/graphql-client/queries.js | 4 ++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/server/data/kaizen.js b/server/data/kaizen.js index 0df82a9ce..0d3720d25 100644 --- a/server/data/kaizen.js +++ b/server/data/kaizen.js @@ -29,17 +29,24 @@ const ftpSetup = { password: process.env.KAIZEN_PASSWORD, debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data), algorithms: { - serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"], + serverHostKey: [ + "ssh-rsa", + "ssh-dss", + "rsa-sha2-256", + "rsa-sha2-512", + "ecdsa-sha2-nistp256", + "ecdsa-sha2-nistp384", + ], }, }; exports.default = async (req, res) => { //Query for the List of Bodyshop Clients. logger.log("kaizen-start", "DEBUG", "api", null, null); - const kaizenShopsNames = ["SUMMIT", "STRATHMORE", "SUNRIDGE"]; + const kaizenShopsIDs = ["SUMMIT", "STRATHMORE", "SUNRIDGE"]; const { bodyshops } = await client.request(queries.GET_KAIZEN_SHOPS, { - shopname: kaizenShopsNames, + imexshopid: kaizenShopsIDs, }); const specificShopIds = req.body.bodyshopIds; // ['uuid] diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index 554388afe..b9c491de7 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -1739,8 +1739,8 @@ exports.GET_ENTEGRAL_SHOPS = `query GET_ENTEGRAL_SHOPS { } }`; -exports.GET_KAIZEN_SHOPS = `query GET_KAIZEN_SHOPS($shopname: [String]) { - bodyshops(where: {shopname: {_in: $shopname}}){ +exports.GET_KAIZEN_SHOPS = `query GET_KAIZEN_SHOPS($imexshopid: [String]) { + bodyshops(where: {imexshopid: {_in: $imexshopid}}){ id shopname address1 From 7245d4eab24cdb682c4fa9bca639eabff9e8be02 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Mon, 15 Jan 2024 18:54:28 -0800 Subject: [PATCH 13/67] IO-2603 Open Orders Excel --- client/src/translations/en_us/common.json | 1 + client/src/translations/es/common.json | 1 + client/src/translations/fr/common.json | 1 + client/src/utils/TemplateConstants.js | 13 +++++++++++++ 4 files changed, 16 insertions(+) diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 09798f0a5..46dfbe01f 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -2621,6 +2621,7 @@ "open_orders": "Open Orders by Date", "open_orders_csr": "Open Orders by CSR", "open_orders_estimator": "Open Orders by Estimator", + "open_orders_excel": "Open Orders - Excel", "open_orders_ins_co": "Open Orders by Insurance Company", "open_orders_referral": "Open Orders by Referral Source", "open_orders_specific_csr": "Open Orders filtered by CSR", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index da9f6b4e0..e7688ca48 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -2621,6 +2621,7 @@ "open_orders": "", "open_orders_csr": "", "open_orders_estimator": "", + "open_orders_excel": "", "open_orders_ins_co": "", "open_orders_referral": "", "open_orders_specific_csr": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index a4c1dc686..4121c0c21 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -2621,6 +2621,7 @@ "open_orders": "", "open_orders_csr": "", "open_orders_estimator": "", + "open_orders_excel": "", "open_orders_ins_co": "", "open_orders_referral": "", "open_orders_specific_csr": "", diff --git a/client/src/utils/TemplateConstants.js b/client/src/utils/TemplateConstants.js index 365b2431e..eeb937c3a 100644 --- a/client/src/utils/TemplateConstants.js +++ b/client/src/utils/TemplateConstants.js @@ -2026,6 +2026,19 @@ export const TemplateList = (type, context) => { }, group: "customers", }, + open_orders_excel: { + title: i18n.t("reportcenter.templates.open_orders_excel"), + subject: i18n.t("reportcenter.templates.open_orders_excel"), + key: "open_orders_excel", + //idtype: "vendor", + reporttype: "excel", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open"), + }, + group: "jobs", + }, } : {}), ...(!type || type === "courtesycarcontract" From 2ce85495021b9a806cbad18883c1a8998e9091c8 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Tue, 16 Jan 2024 12:11:18 -0800 Subject: [PATCH 14/67] IO-2531 Retain Filtered Statue for Jobs Pages --- .../jobs-list-paginated.component.jsx | 7 +- .../jobs-list/jobs-list.component.jsx | 725 +++++++++--------- .../jobs-ready-list.component.jsx | 44 +- 3 files changed, 397 insertions(+), 379 deletions(-) diff --git a/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx b/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx index b5048d0ef..1b4c6f435 100644 --- a/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx +++ b/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx @@ -10,9 +10,10 @@ import { Link, useHistory, useLocation } from "react-router-dom"; import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; +import { pageLimit } from "../../utils/config"; +import useLocalStorage from "../../utils/useLocalStorage"; import StartChatButton from "../chat-open-button/chat-open-button.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; -import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser bodyshop: selectBodyshop, @@ -25,6 +26,8 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) { const search = queryString.parse(useLocation().search); const [openSearchResults, setOpenSearchResults] = useState([]); const [searchLoading, setSearchLoading] = useState(false); + const [filter, setFilter] = useLocalStorage("filter_jobs_all", null); + console.log("filter", filter); const { page, sortcolumn, sortorder } = search; const history = useHistory(); @@ -93,6 +96,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) { render: (text, record) => { return record.status || t("general.labels.na"); }, + filteredValue: filter?.status || null, filters: bodyshop.md_ro_statuses.statuses.map((s) => { return { text: s, value: [s] }; }), @@ -189,6 +193,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) { } else { delete search.statusFilters; } + setFilter(filters); history.push({ search: queryString.stringify(search) }); }; diff --git a/client/src/components/jobs-list/jobs-list.component.jsx b/client/src/components/jobs-list/jobs-list.component.jsx index fb2e1daa6..7761c0ed9 100644 --- a/client/src/components/jobs-list/jobs-list.component.jsx +++ b/client/src/components/jobs-list/jobs-list.component.jsx @@ -1,8 +1,8 @@ import { - SyncOutlined, + BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, - BranchesOutlined, + SyncOutlined, } from "@ant-design/icons"; import { useQuery } from "@apollo/client"; import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd"; @@ -14,382 +14,389 @@ import { Link, useHistory, useLocation } from "react-router-dom"; import { createStructuredSelector } from "reselect"; import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries"; import { selectBodyshop } from "../../redux/user/user.selectors"; -import { onlyUnique } from "../../utils/arrayHelper"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import { alphaSort } from "../../utils/sorters"; +import { onlyUnique } from "../../utils/arrayHelper"; +import { alphaSort, statusSort } from "../../utils/sorters"; +import useLocalStorage from "../../utils/useLocalStorage"; import AlertComponent from "../alert/alert.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, + bodyshop: selectBodyshop, }); export function JobsList({ bodyshop }) { - const searchParams = queryString.parse(useLocation().search); - const { selected } = searchParams; - const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) - .filter((screen) => !!screen[1]) - .slice(-1)[0]; - const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, { - variables: { - statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"], - }, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); + const searchParams = queryString.parse(useLocation().search); + const { selected } = searchParams; + const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) + .filter((screen) => !!screen[1]) + .slice(-1)[0]; + const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, { + variables: { + statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"], + }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + }); - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: { text: "" }, - }); + const [state, setState] = useState({ sortedInfo: {} }); + const [filter, setFilter] = useLocalStorage("filter_jobs_list", null); - const { t } = useTranslation(); - const history = useHistory(); - const [searchText, setSearchText] = useState(""); + const { t } = useTranslation(); + const history = useHistory(); + const [searchText, setSearchText] = useState(""); - if (error) return ; + if (error) return ; - const jobs = data - ? searchText === "" - ? data.jobs - : data.jobs.filter( - (j) => - (j.ro_number || "") - .toString() - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_co_nm || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.comments || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_fn || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.ownr_ln || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) || - (j.plate_no || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.v_model_desc || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.est_ct_fn || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.est_ct_ln || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.v_make_desc || "") - .toLowerCase() - .includes(searchText.toLowerCase()) - ) - : []; + const jobs = data + ? searchText === "" + ? data.jobs + : data.jobs.filter( + (j) => + (j.ro_number || "") + .toString() + .toLowerCase() + .includes(searchText.toLowerCase()) || + (j.ownr_co_nm || "") + .toLowerCase() + .includes(searchText.toLowerCase()) || + (j.comments || "") + .toLowerCase() + .includes(searchText.toLowerCase()) || + (j.ownr_fn || "") + .toLowerCase() + .includes(searchText.toLowerCase()) || + (j.ownr_ln || "") + .toLowerCase() + .includes(searchText.toLowerCase()) || + (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.plate_no || "") + .toLowerCase() + .includes(searchText.toLowerCase()) || + (j.v_model_desc || "") + .toLowerCase() + .includes(searchText.toLowerCase()) || + (j.est_ct_fn || "") + .toLowerCase() + .includes(searchText.toLowerCase()) || + (j.est_ct_ln || "") + .toLowerCase() + .includes(searchText.toLowerCase()) || + (j.v_make_desc || "") + .toLowerCase() + .includes(searchText.toLowerCase()) + ) + : []; - const handleTableChange = (pagination, filters, sorter) => { - setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); - }; + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, sortedInfo: sorter }); + setFilter(filters); + }; - const handleOnRowClick = (record) => { - if (record) { - if (record.id) { - history.push({ - search: queryString.stringify({ - ...searchParams, - selected: record.id, - }), - }); - } - } - }; + const handleOnRowClick = (record) => { + if (record) { + if (record.id) { + history.push({ + search: queryString.stringify({ + ...searchParams, + selected: record.id, + }), + }); + } + } + }; - const columns = [ - { - title: t("jobs.fields.ro_number"), - dataIndex: "ro_number", - key: "ro_number", - sorter: (a, b) => - parseInt((a.ro_number || "0").replace(/\D/g, "")) - - parseInt((b.ro_number || "0").replace(/\D/g, "")), - sortOrder: - state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, - - render: (text, record) => ( - e.stopPropagation()} - > - - {record.ro_number || t("general.labels.na")} - {record.production_vars && record.production_vars.alert ? ( - - ) : null} - {record.suspended && ( - - )} - {record.iouparent && ( - - - - )} - - - ), - }, - { - title: t("jobs.fields.owner"), - dataIndex: "owner", - key: "owner", - ellipsis: true, - - responsive: ["md"], - sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), - sortOrder: - state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, - render: (text, record) => { - return record.ownerid ? ( - e.stopPropagation()} - > - - - ) : ( - + const columns = [ + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + sorter: (a, b) => + parseInt((a.ro_number || "0").replace(/\D/g, "")) - + parseInt((b.ro_number || "0").replace(/\D/g, "")), + sortOrder: + state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + render: (text, record) => ( + e.stopPropagation()} + > + + {record.ro_number || t("general.labels.na")} + {record.production_vars && record.production_vars.alert ? ( + + ) : null} + {record.suspended && ( + + )} + {record.iouparent && ( + + + + )} + + + ), + }, + { + title: t("jobs.fields.owner"), + dataIndex: "owner", + key: "owner", + ellipsis: true, + responsive: ["md"], + sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), + sortOrder: + state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, + render: (text, record) => { + return record.ownerid ? ( + e.stopPropagation()} + > + + + ) : ( + - ); + ); + }, + }, + { + title: t("jobs.fields.ownr_ph1"), + dataIndex: "ownr_ph1", + key: "ownr_ph1", + ellipsis: true, + responsive: ["md"], + render: (text, record) => ( + + ), + }, + { + title: t("jobs.fields.ownr_ph2"), + dataIndex: "ownr_ph2", + key: "ownr_ph2", + ellipsis: true, + responsive: ["md"], + render: (text, record) => ( + + ), + }, + { + title: t("jobs.fields.status"), + dataIndex: "status", + key: "status", + ellipsis: true, + sorter: (a, b) => alphaSort(a.status, b.status), + sortOrder: + state.sortedInfo.columnKey === "status" && state.sortedInfo.order, + filteredValue: filter?.status || null, + filters: + (jobs && + jobs + .map((j) => j.status) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Status*", + value: [s], + }; + }) + .sort((a, b) => + statusSort( + a.text, + b.text, + bodyshop.md_ro_statuses.active_statuses + ) + )) || + [], + onFilter: (value, record) => value.includes(record.status), + }, + + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + ellipsis: true, + render: (text, record) => { + return record.vehicleid ? ( + e.stopPropagation()} + > + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ + record.v_model_desc || "" + }`} + + ) : ( + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ + record.v_model_desc || "" + }`} + ); + }, + }, + { + title: t("vehicles.fields.plate_no"), + dataIndex: "plate_no", + key: "plate_no", + ellipsis: true, + + responsive: ["md"], + sorter: (a, b) => alphaSort(a.plate_no, b.plate_no), + sortOrder: + state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order, + }, + { + title: t("jobs.fields.clm_no"), + dataIndex: "clm_no", + key: "clm_no", + ellipsis: true, + responsive: ["md"], + sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), + sortOrder: + state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, + render: (text, record) => + `${record.clm_no || ""}${ + record.po_number ? ` (PO: ${record.po_number})` : "" + }`, + }, + { + title: t("jobs.fields.ins_co_nm"), + dataIndex: "ins_co_nm", + key: "ins_co_nm", + ellipsis: true, + filteredValue: filter?.ins_co_nm || null, + filters: + (jobs && + jobs + .map((j) => j.ins_co_nm) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Ins. Co.*", + value: [s], + }; + }) + .sort((a, b) => alphaSort(a.text, b.text))) || + [], + onFilter: (value, record) => value.includes(record.ins_co_nm), + responsive: ["md"], + }, + { + title: t("jobs.fields.clm_total"), + dataIndex: "clm_total", + key: "clm_total", + responsive: ["md"], + ellipsis: true, + sorter: (a, b) => a.clm_total - b.clm_total, + sortOrder: + state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order, + render: (text, record) => ( + {record.clm_total} + ), + }, + { + title: t("jobs.labels.estimator"), + dataIndex: "jobs.labels.estimator", + key: "estimator", + ellipsis: true, + responsive: ["xl"], + filterSearch: true, + filteredValue: filter?.estimator || null, + filters: + (jobs && + jobs + .map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim()) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Estimator*", + value: [s], + }; + }) + .sort((a, b) => alphaSort(a.text, b.text))) || + [], + onFilter: (value, record) => + value.includes( + `${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim() + ), + render: (text, record) => + `${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim(), + }, + { + title: t("jobs.fields.comment"), + dataIndex: "comment", + key: "comment", + ellipsis: true, + responsive: ["md"], + }, + // { + // title: t("jobs.fields.owner_owing"), + // dataIndex: "owner_owing", + // key: "owner_owing", + // responsive: ["md"], + // render: (text, record) => ( + // {record.owner_owing} + // ), + // }, + ]; + + const scrollMapper = { + xs: true, + sm: true, + md: true, + lg: "100%", + xl: "100%", + xxl: "100%", + }; + + return ( + + + { + setSearchText(e.target.value); + }} + value={searchText} + enterButton + /> + + } + > + { + handleOnRowClick(record); + }, + selectedRowKeys: [selected], + type: "radio", + }} + onChange={handleTableChange} + onRow={(record, rowIndex) => { + return { + onClick: (event) => { + handleOnRowClick(record); }, - }, - { - title: t("jobs.fields.ownr_ph1"), - dataIndex: "ownr_ph1", - key: "ownr_ph1", - ellipsis: true, - responsive: ["md"], - render: (text, record) => ( - - ), - }, - { - title: t("jobs.fields.ownr_ph2"), - dataIndex: "ownr_ph2", - key: "ownr_ph2", - ellipsis: true, - responsive: ["md"], - render: (text, record) => ( - - ), - }, - - { - title: t("jobs.fields.status"), - dataIndex: "status", - key: "status", - ellipsis: true, - - sorter: (a, b) => alphaSort(a.status, b.status), - sortOrder: - state.sortedInfo.columnKey === "status" && state.sortedInfo.order, - filters: - (jobs && - jobs - .map((j) => j.status) - .filter(onlyUnique) - .map((s) => { - return { - text: s || "No Status*", - value: [s], - }; - })) || - [], - onFilter: (value, record) => value.includes(record.status), - }, - - { - title: t("jobs.fields.vehicle"), - dataIndex: "vehicle", - key: "vehicle", - ellipsis: true, - render: (text, record) => { - return record.vehicleid ? ( - e.stopPropagation()} - > - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} - - ) : ( - {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ - record.v_model_desc || "" - }`} - ); - }, - }, - { - title: t("vehicles.fields.plate_no"), - dataIndex: "plate_no", - key: "plate_no", - ellipsis: true, - - responsive: ["md"], - sorter: (a, b) => alphaSort(a.plate_no, b.plate_no), - sortOrder: - state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order, - }, - { - title: t("jobs.fields.clm_no"), - dataIndex: "clm_no", - key: "clm_no", - ellipsis: true, - responsive: ["md"], - sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), - sortOrder: - state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, - render: (text, record) => - `${record.clm_no || ""}${ - record.po_number ? ` (PO: ${record.po_number})` : "" - }`, - }, - { - title: t("jobs.fields.ins_co_nm"), - dataIndex: "ins_co_nm", - key: "ins_co_nm", - ellipsis: true, - filters: - (jobs && - jobs - .map((j) => j.ins_co_nm) - .filter(onlyUnique) - .map((s) => { - return { - text: s, - value: [s], - }; - })) || - [], - onFilter: (value, record) => value.includes(record.ins_co_nm), - responsive: ["md"], - }, - { - title: t("jobs.fields.clm_total"), - dataIndex: "clm_total", - key: "clm_total", - responsive: ["md"], - ellipsis: true, - - sorter: (a, b) => a.clm_total - b.clm_total, - sortOrder: - state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order, - render: (text, record) => ( - {record.clm_total} - ), - }, - { - title: t("jobs.labels.estimator"), - dataIndex: "jobs.labels.estimator", - key: "jobs.labels.estimator", - ellipsis: true, - responsive: ["xl"], - filterSearch: true, - filters: - (jobs && - jobs - .map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim()) - .filter(onlyUnique) - .map((s) => { - return { - text: s || "N/A", - value: [s], - }; - })) || - [], - onFilter: (value, record) => - value.includes( - `${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim() - ), - render: (text, record) => - `${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim(), - }, - { - title: t("jobs.fields.comment"), - dataIndex: "comment", - key: "comment", - ellipsis: true, - responsive: ["md"], - }, - // { - // title: t("jobs.fields.owner_owing"), - // dataIndex: "owner_owing", - // key: "owner_owing", - // responsive: ["md"], - // render: (text, record) => ( - // {record.owner_owing} - // ), - // }, - ]; - - const scrollMapper = { - xs: true, - sm: true, - md: true, - lg: "100%", - xl: "100%", - xxl: "100%", - }; - - return ( - - - { - setSearchText(e.target.value); - }} - value={searchText} - enterButton - /> - - } - > -
{ - handleOnRowClick(record); - }, - selectedRowKeys: [selected], - type: "radio", - }} - onChange={handleTableChange} - onRow={(record, rowIndex) => { - return { - onClick: (event) => { - handleOnRowClick(record); - }, - }; - }} - /> - - ); + }; + }} + /> + + ); } export default connect(mapStateToProps, null)(JobsList); diff --git a/client/src/components/jobs-ready-list/jobs-ready-list.component.jsx b/client/src/components/jobs-ready-list/jobs-ready-list.component.jsx index fee461df2..147f6ea23 100644 --- a/client/src/components/jobs-ready-list/jobs-ready-list.component.jsx +++ b/client/src/components/jobs-ready-list/jobs-ready-list.component.jsx @@ -16,11 +16,12 @@ import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries"; import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; import { onlyUnique } from "../../utils/arrayHelper"; -import { alphaSort } from "../../utils/sorters"; +import { pageLimit } from "../../utils/config"; +import { alphaSort, statusSort } from "../../utils/sorters"; +import useLocalStorage from "../../utils/useLocalStorage"; import AlertComponent from "../alert/alert.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; -import {pageLimit} from "../../utils/config"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -53,10 +54,8 @@ export function JobsReadyList({ bodyshop }) { nextFetchPolicy: "network-only", }); - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: { text: "" }, - }); + const [state, setState] = useState({ sortedInfo: {} }); + const [filter, setFilter] = useLocalStorage("filter_jobs_ready", null); const { t } = useTranslation(); const history = useHistory(); @@ -105,7 +104,8 @@ export function JobsReadyList({ bodyshop }) { : []; const handleTableChange = (pagination, filters, sorter) => { - setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + setState({ ...state, sortedInfo: sorter }); + setFilter(filters); }; const handleOnRowClick = (record) => { @@ -129,7 +129,6 @@ export function JobsReadyList({ bodyshop }) { sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, - render: (text, record) => ( alphaSort(a.ownr_ln, b.ownr_ln), sortOrder: @@ -197,16 +195,15 @@ export function JobsReadyList({ bodyshop }) { ), }, - { title: t("jobs.fields.status"), dataIndex: "status", key: "status", ellipsis: true, - sorter: (a, b) => alphaSort(a.status, b.status), sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order, + filteredValue: filter?.status || null, filters: (jobs && jobs @@ -217,11 +214,17 @@ export function JobsReadyList({ bodyshop }) { text: s || "No Status*", value: [s], }; - })) || + }) + .sort((a, b) => + statusSort( + a.text, + b.text, + bodyshop.md_ro_statuses.active_statuses + ) + )) || [], onFilter: (value, record) => value.includes(record.status), }, - { title: t("jobs.fields.vehicle"), dataIndex: "vehicle", @@ -274,6 +277,7 @@ export function JobsReadyList({ bodyshop }) { dataIndex: "ins_co_nm", key: "ins_co_nm", ellipsis: true, + filteredValue: filter?.ins_co_nm || null, filters: (jobs && jobs @@ -281,10 +285,11 @@ export function JobsReadyList({ bodyshop }) { .filter(onlyUnique) .map((s) => { return { - text: s, + text: s || "No Ins Co.*", value: [s], }; - })) || + }) + .sort((a, b) => alphaSort(a.text, b.text))) || [], onFilter: (value, record) => value.includes(record.ins_co_nm), responsive: ["md"], @@ -295,7 +300,6 @@ export function JobsReadyList({ bodyshop }) { key: "clm_total", responsive: ["md"], ellipsis: true, - sorter: (a, b) => a.clm_total - b.clm_total, sortOrder: state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order, @@ -306,9 +310,10 @@ export function JobsReadyList({ bodyshop }) { { title: t("jobs.labels.estimator"), dataIndex: "jobs.labels.estimator", - key: "jobs.labels.estimator", + key: "estimator", ellipsis: true, responsive: ["xl"], + filteredValue: filter?.estimator || null, filterSearch: true, filters: (jobs && @@ -317,10 +322,11 @@ export function JobsReadyList({ bodyshop }) { .filter(onlyUnique) .map((s) => { return { - text: s || "N/A", + text: s || "No Estimator*", value: [s], }; - })) || + }) + .sort((a, b) => alphaSort(a.text, b.text))) || [], onFilter: (value, record) => value.includes( From 0cc367b25e9c78550ba65e7a19595b9f43a915db Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Tue, 16 Jan 2024 12:12:53 -0800 Subject: [PATCH 15/67] IO-2531 Remove Console Log --- .../jobs-list-paginated/jobs-list-paginated.component.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx b/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx index 1b4c6f435..e247f5450 100644 --- a/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx +++ b/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx @@ -27,7 +27,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) { const [openSearchResults, setOpenSearchResults] = useState([]); const [searchLoading, setSearchLoading] = useState(false); const [filter, setFilter] = useLocalStorage("filter_jobs_all", null); - console.log("filter", filter); const { page, sortcolumn, sortorder } = search; const history = useHistory(); From dea7fd71efb5cc3d8952509f8b55386de9174eeb Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 17 Jan 2024 09:41:57 -0800 Subject: [PATCH 16/67] IO-2600 Tech Console Job Logout Console Log --- .../tech-job-clock-out-button.component.jsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/src/components/tech-job-clock-out-button/tech-job-clock-out-button.component.jsx b/client/src/components/tech-job-clock-out-button/tech-job-clock-out-button.component.jsx index d1c5c3528..98369c860 100644 --- a/client/src/components/tech-job-clock-out-button/tech-job-clock-out-button.component.jsx +++ b/client/src/components/tech-job-clock-out-button/tech-job-clock-out-button.component.jsx @@ -166,9 +166,6 @@ export function TechClockOffButton({ }, ({ getFieldValue }) => ({ validator(rule, value) { - console.log( - bodyshop.tt_enforce_hours_for_tech_console - ); if (!bodyshop.tt_enforce_hours_for_tech_console) { return Promise.resolve(); } From 636be8989e952f351b2195d06a6bbc70993937b6 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 17 Jan 2024 14:22:47 -0800 Subject: [PATCH 17/67] IO-2589 Allow Company Only for Customer Creation --- .../jobs-create-owner-info.new.component.jsx | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/client/src/components/jobs-create-owner-info/jobs-create-owner-info.new.component.jsx b/client/src/components/jobs-create-owner-info/jobs-create-owner-info.new.component.jsx index 2dea9a657..da2a1b1a5 100644 --- a/client/src/components/jobs-create-owner-info/jobs-create-owner-info.new.component.jsx +++ b/client/src/components/jobs-create-owner-info/jobs-create-owner-info.new.component.jsx @@ -19,10 +19,13 @@ export default function JobsCreateOwnerInfoNewComponent() { label={t("owners.fields.ownr_ln")} name={["owner", "data", "ownr_ln"]} rules={[ - { - required: state.owner.new, + ({ getFieldValue }) => ({ + required: + state.owner.new && + (!getFieldValue(["owner", "data", "ownr_co_nm"]) || + getFieldValue(["owner", "data", "ownr_co_nm"]) === ""), //message: t("general.validation.required"), - }, + }), ]} > @@ -31,10 +34,13 @@ export default function JobsCreateOwnerInfoNewComponent() { label={t("owners.fields.ownr_fn")} name={["owner", "data", "ownr_fn"]} rules={[ - { - required: state.owner.new, + ({ getFieldValue }) => ({ + required: + state.owner.new && + (!getFieldValue(["owner", "data", "ownr_co_nm"]) || + getFieldValue(["owner", "data", "ownr_co_nm"]) === ""), //message: t("general.validation.required"), - }, + }), ]} > @@ -51,6 +57,17 @@ export default function JobsCreateOwnerInfoNewComponent() { ({ + required: + state.owner.new && + (!getFieldValue(["owner", "data", "ownr_ln"]) || + !getFieldValue(["owner", "data", "ownr_fn"]) || + getFieldValue(["owner", "data", "ownr_ln"]) === "" || + getFieldValue(["owner", "data", "ownr_fn"]) === ""), + //message: t("general.validation.required"), + }), + ]} > From 572963d9871dfb593f497ce1b9fd9659f6dd37e4 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 17 Jan 2024 17:09:17 -0800 Subject: [PATCH 18/67] IO-2606 Modal Closeable for Schedule Appointment --- .../schedule-job-modal/schedule-job-modal.container.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx b/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx index 19c360a21..19d6d4a37 100644 --- a/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx +++ b/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx @@ -216,6 +216,7 @@ export function ScheduleJobModalContainer({ okButtonProps={{ loading: loading, }} + closable={false} >
Date: Wed, 17 Jan 2024 17:52:14 -0800 Subject: [PATCH 19/67] IO-2601 Tech Console Titles --- .../src/components/tech-login/tech-login.component.jsx | 8 ++++++-- .../pages/tech-job-clock/tech-job-clock.component.jsx | 9 ++++++++- client/src/pages/tech-lookup/tech-lookup.container.jsx | 9 ++++++++- .../tech-shift-clock/tech-shift-clock.component.jsx | 9 ++++++++- client/src/translations/en_us/common.json | 8 ++++++-- client/src/translations/es/common.json | 6 +++++- client/src/translations/fr/common.json | 6 +++++- 7 files changed, 46 insertions(+), 9 deletions(-) diff --git a/client/src/components/tech-login/tech-login.component.jsx b/client/src/components/tech-login/tech-login.component.jsx index 502b20cbf..c266803a5 100644 --- a/client/src/components/tech-login/tech-login.component.jsx +++ b/client/src/components/tech-login/tech-login.component.jsx @@ -1,7 +1,8 @@ import { Button, Form, Input } from "antd"; -import React from "react"; +import React, { useEffect } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; +import { Redirect } from "react-router-dom"; import { createStructuredSelector } from "reselect"; import { techLoginStart } from "../../redux/tech/tech.actions"; import { @@ -11,7 +12,6 @@ import { } from "../../redux/tech/tech.selectors"; import AlertComponent from "../alert/alert.component"; import "./tech-login.styles.scss"; -import { Redirect } from "react-router-dom"; const mapStateToProps = createStructuredSelector({ technician: selectTechnician, @@ -35,6 +35,10 @@ export function TechLogin({ techLoginStart(values); }; + useEffect(() => { + document.title = t("titles.techconsole"); + }, [t]); + return (
{technician ? : null} diff --git a/client/src/pages/tech-job-clock/tech-job-clock.component.jsx b/client/src/pages/tech-job-clock/tech-job-clock.component.jsx index 0d64068eb..c22b68eed 100644 --- a/client/src/pages/tech-job-clock/tech-job-clock.component.jsx +++ b/client/src/pages/tech-job-clock/tech-job-clock.component.jsx @@ -1,10 +1,17 @@ import { Divider } from "antd"; -import React from "react"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; import TechClockInFormContainer from "../../components/tech-job-clock-in-form/tech-job-clock-in-form.container"; import TechClockedInList from "../../components/tech-job-clocked-in-list/tech-job-clocked-in-list.component"; import TechJobStatistics from "../../components/tech-job-statistics/tech-job-statistics.component"; export default function TechClockComponent() { + const { t } = useTranslation(); + + useEffect(() => { + document.title = t("titles.techjobclock"); + }, [t]); + return (
diff --git a/client/src/pages/tech-lookup/tech-lookup.container.jsx b/client/src/pages/tech-lookup/tech-lookup.container.jsx index 1297220b9..e24f8f661 100644 --- a/client/src/pages/tech-lookup/tech-lookup.container.jsx +++ b/client/src/pages/tech-lookup/tech-lookup.container.jsx @@ -1,9 +1,16 @@ -import React from "react"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; import RbacWrapperComponent from "../../components/rbac-wrapper/rbac-wrapper.component"; import TechLookupJobsDrawer from "../../components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component"; import TechLookupJobsList from "../../components/tech-lookup-jobs-list/tech-lookup-jobs-list.component"; export default function TechLookupContainer() { + const { t } = useTranslation(); + + useEffect(() => { + document.title = t("titles.techjoblookup"); + }, [t]); + return (
diff --git a/client/src/pages/tech-shift-clock/tech-shift-clock.component.jsx b/client/src/pages/tech-shift-clock/tech-shift-clock.component.jsx index 52e0e99c7..2dad65e64 100644 --- a/client/src/pages/tech-shift-clock/tech-shift-clock.component.jsx +++ b/client/src/pages/tech-shift-clock/tech-shift-clock.component.jsx @@ -1,7 +1,14 @@ -import React from "react"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; import TimeTicketShift from "../../components/time-ticket-shift/time-ticket-shift.container"; export default function TechShiftClock() { + const { t } = useTranslation(); + + useEffect(() => { + document.title = t("titles.techshiftclock"); + }, [t]); + return (
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 09798f0a5..13555e364 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -2024,7 +2024,7 @@ "joblookup": "Job Lookup", "login": "Login", "logout": "Logout", - "productionboard": "Production Board - Visual", + "productionboard": "Production Visual", "productionlist": "Production List", "shiftclockin": "Shift Clock" } @@ -2901,7 +2901,7 @@ "parts-queue": "Parts Queue | $t(titles.app)", "payments-all": "Payments | $t(titles.app)", "phonebook": "Phonebook | $t(titles.app)", - "productionboard": "Production - Board", + "productionboard": "Production Board - Visual | $t(titles.app)", "productionlist": "Production Board - List | $t(titles.app)", "profile": "My Profile | $t(titles.app)", "readyjobs": "Ready Jobs | $t(titles.app)", @@ -2913,6 +2913,10 @@ "shop-csi": "CSI Responses | $t(titles.app)", "shop-templates": "Shop Templates | $t(titles.app)", "shop_vendors": "Vendors | $t(titles.app)", + "techconsole": "Technician Console | $t(titles.app)", + "techjoblookup": "Technician Job Lookup | $t(titles.app)", + "techjobclock": "Technician Job Clock | $t(titles.app)", + "techshiftclock": "Technician Shift Clock | $t(titles.app)", "temporarydocs": "Temporary Documents | $t(titles.app)", "timetickets": "Time Tickets | $t(titles.app)", "ttapprovals": "", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index da9f6b4e0..9f522ecce 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -2894,7 +2894,7 @@ "jobs-intake": "", "jobsavailable": "Empleos disponibles | $t(titles.app)", "jobsdetail": "Trabajo {{ro_number}} | $t(titles.app)", - "jobsdocuments": "Documentos de trabajo {{ro_number}} | $ t (títulos.app)", + "jobsdocuments": "Documentos de trabajo {{ro_number}} | $t(titles.app)", "manageroot": "Casa | $t(titles.app)", "owners": "Todos los propietarios | $t(titles.app)", "owners-detail": "", @@ -2913,6 +2913,10 @@ "shop-csi": "", "shop-templates": "", "shop_vendors": "Vendedores | $t(titles.app)", + "techconsole": "$t(titles.app)", + "techjoblookup": "$t(titles.app)", + "techjobclock": "$t(titles.app)", + "techshiftclock": "$t(titles.app)", "temporarydocs": "", "timetickets": "", "ttapprovals": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index a4c1dc686..2543ae722 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -2894,7 +2894,7 @@ "jobs-intake": "", "jobsavailable": "Emplois disponibles | $t(titles.app)", "jobsdetail": "Travail {{ro_number}} | $t(titles.app)", - "jobsdocuments": "Documents de travail {{ro_number}} | $ t (titres.app)", + "jobsdocuments": "Documents de travail {{ro_number}} | $t(titles.app)", "manageroot": "Accueil | $t(titles.app)", "owners": "Tous les propriétaires | $t(titles.app)", "owners-detail": "", @@ -2913,6 +2913,10 @@ "shop-csi": "", "shop-templates": "", "shop_vendors": "Vendeurs | $t(titles.app)", + "techconsole": "$t(titles.app)", + "techjoblookup": "$t(titles.app)", + "techjobclock": "$t(titles.app)", + "techshiftclock": "$t(titles.app)", "temporarydocs": "", "timetickets": "", "ttapprovals": "", From 430823dde0158387fcdbca1c26743919db67c0e1 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 18 Jan 2024 13:20:04 -0500 Subject: [PATCH 20/67] - remove source maps from prod Signed-off-by: Dave Richer --- client/src/components/header/header.component.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx index 404792639..d22e37d89 100644 --- a/client/src/components/header/header.component.jsx +++ b/client/src/components/header/header.component.jsx @@ -445,7 +445,7 @@ function Header({ ))} - + Try the new ImEX Online Date: Thu, 18 Jan 2024 11:30:58 -0800 Subject: [PATCH 21/67] IO-2599 Tech Station Linking --- .../job-detail-cards.template.component.jsx | 28 +++- .../jobs-detail-header.component.jsx | 67 ++++++--- .../jobs-related-ros.component.jsx | 14 +- .../production-list-columns.data.js | 28 ++-- .../production-list-detail.component.jsx | 134 ++++++++++-------- client/src/graphql/jobs.queries.js | 7 +- client/src/translations/en_us/common.json | 1 + client/src/translations/es/common.json | 1 + client/src/translations/fr/common.json | 1 + 9 files changed, 187 insertions(+), 94 deletions(-) diff --git a/client/src/components/job-detail-cards/job-detail-cards.template.component.jsx b/client/src/components/job-detail-cards/job-detail-cards.template.component.jsx index 4aa6e0d6e..0fa8d3c20 100644 --- a/client/src/components/job-detail-cards/job-detail-cards.template.component.jsx +++ b/client/src/components/job-detail-cards/job-detail-cards.template.component.jsx @@ -1,15 +1,37 @@ -import React from "react"; import { Card } from "antd"; +import React from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; import { Link } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; -export default function JobDetailCardTemplate({ +const mapStateToProps = createStructuredSelector({ + technician: selectTechnician, +}); +const mapDispatchToProps = (dispatch) => ({ + //setUserLanguage: language => dispatch(setUserLanguage(language)) +}); + +export default connect( + mapStateToProps, + mapDispatchToProps +)(JobDetailCardTemplate); + +export function JobDetailCardTemplate({ loading, title, extraLink, + technician, ...otherProps }) { + const { t } = useTranslation(); + let extra; - if (extraLink) extra = { extra: More }; + if (extraLink && !technician) + extra = { + extra: {t("jobs.labels.cards.more")}, + }; return ( - {ownerTitle.length > 0 - ? ownerTitle - : t("owner.labels.noownerinfo")} - + disabled ? ( + <> + {ownerTitle.length > 0 + ? ownerTitle + : t("owner.labels.noownerinfo")} + + ) : ( + + {ownerTitle.length > 0 + ? ownerTitle + : t("owner.labels.noownerinfo")} + + ) } >
- + {disabled ? ( + {job.ownr_ph1} + ) : ( + + )} - + {disabled ? ( + {job.ownr_ph2} + ) : ( + + )} {`${job.ownr_addr1 || ""} ${job.ownr_addr2 || ""} ${ @@ -180,7 +197,11 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) { } ${job.ownr_st || ""} ${job.ownr_zip || ""}`} - {job.ownr_ea || ""} + {disabled ? ( + <>{job.ownr_ea || ""} + ) : job.ownr_ea ? ( + {job.ownr_ea} + ) : null} {job.owner?.tax_number && ( @@ -195,17 +216,19 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) { style={{ height: "100%" }} title={ job.vehicle ? ( - - {vehicleTitle.length > 0 - ? vehicleTitle - : t("vehicles.labels.novehinfo")} - + disabled ? ( + <> + {vehicleTitle.length > 0 + ? vehicleTitle + : t("vehicles.labels.novehinfo")}{" "} + + ) : ( + + {vehicleTitle.length > 0 + ? vehicleTitle + : t("vehicles.labels.novehinfo")} + + ) ) : ( ) @@ -223,7 +246,9 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) { {bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? ( job.v_vin?.length !== 17 ? ( - + ) : null ) : null} @@ -231,7 +256,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) { {job.regie_number || t("general.labels.na")} - + {job.vehicle && job.vehicle.notes && ( @@ -10,9 +10,15 @@ export default function JobsRelatedRos({ jobid, job }) { .filter((j) => j.id !== job.id) .map((j) => ( - {`${j.ro_number || "N/A"}${ - j.clm_no ? ` | ${j.clm_no}` : "" - }${j.status ? ` | ${j.status}` : ""}`} + {disabled ? ( + <>{`${j.ro_number || "N/A"}${j.clm_no ? ` | ${j.clm_no}` : ""}${ + j.status ? ` | ${j.status}` : "" + }`} + ) : ( + {`${j.ro_number || "N/A"}${ + j.clm_no ? ` | ${j.clm_no}` : "" + }${j.status ? ` | ${j.status}` : ""}`} + )} ))} diff --git a/client/src/components/production-list-columns/production-list-columns.data.js b/client/src/components/production-list-columns/production-list-columns.data.js index 8e584aa2e..271ed2f15 100644 --- a/client/src/components/production-list-columns/production-list-columns.data.js +++ b/client/src/components/production-list-columns/production-list-columns.data.js @@ -76,7 +76,14 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => { dataIndex: "ownr", key: "ownr", ellipsis: true, - render: (text, record) => , + render: (text, record) => + technician ? ( + + ) : ( + + + + ), sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), sortOrder: state.sortedInfo.columnKey === "ownr" && state.sortedInfo.order, @@ -93,13 +100,18 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => { ), sortOrder: state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order, - render: (text, record) => ( - {`${ - record.v_model_yr || "" - } ${record.v_make_desc || ""} ${record.v_model_desc || ""} ${ - record.v_color || "" - } ${record.plate_no || ""}`} - ), + render: (text, record) => + technician ? ( + <>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ + record.v_model_desc || "" + } ${record.v_color || ""} ${record.plate_no || ""}`} + ) : ( + {`${ + record.v_model_yr || "" + } ${record.v_make_desc || ""} ${record.v_model_desc || ""} ${ + record.v_color || "" + } ${record.plate_no || ""}`} + ), }, { title: i18n.t("jobs.fields.actual_in"), diff --git a/client/src/components/production-list-detail/production-list-detail.component.jsx b/client/src/components/production-list-detail/production-list-detail.component.jsx index 170b31b46..2963176b9 100644 --- a/client/src/components/production-list-detail/production-list-detail.component.jsx +++ b/client/src/components/production-list-detail/production-list-detail.component.jsx @@ -13,12 +13,14 @@ import { selectTechnician } from "../../redux/tech/tech.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; import { DateFormatter } from "../../utils/DateFormatter"; +import PhoneNumberFormatter from "../../utils/PhoneFormatter"; import AlertComponent from "../alert/alert.component"; import StartChatButton from "../chat-open-button/chat-open-button.component"; import JobAtChange from "../job-at-change/job-at-change.component"; import JobDetailCardsDocumentsComponent from "../job-detail-cards/job-detail-cards.documents.component"; import JobDetailCardsNotesComponent from "../job-detail-cards/job-detail-cards.notes.component"; import JobDetailCardsPartsComponent from "../job-detail-cards/job-detail-cards.parts.component"; +import CardTemplate from "../job-detail-cards/job-detail-cards.template.component"; import JobEmployeeAssignments from "../job-employee-assignments/job-employee-assignments.container"; import ScoreboardAddButton from "../job-scoreboard-add-button/job-scoreboard-add-button.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; @@ -103,65 +105,85 @@ export function ProductionListDetail({ {error && } {!loading && data && (
- - - - {theJob.ro_number || ""} - - - - {data.jobs_by_pk.alt_transport || ""} - - - - - {theJob.clm_no || ""} - - - {theJob.ins_co_nm || ""} - - - - - - - - {`${theJob.v_model_yr || ""} ${theJob.v_color || ""} ${ - theJob.v_make_desc || "" - } ${theJob.v_model_desc || ""}`} - - - {theJob.clm_total} - - - {theJob.actual_in} - - - {theJob.scheduled_completion} - - - - - - - {!bodyshop.uselocalmediaserver && ( - + + + + + + {theJob.ro_number || ""} + + + + {data.jobs_by_pk.alt_transport || ""} + + + + + {theJob.clm_no || ""} + + + {theJob.ins_co_nm || ""} + + + + + {!technician ? ( + <> + + + + ) : ( + <> + + {data.jobs_by_pk.ownr_ph1} + + + {data.jobs_by_pk.ownr_ph2} + + + )} + + + + {`${theJob.v_model_yr || ""} ${theJob.v_color || ""} ${ + theJob.v_make_desc || "" + } ${theJob.v_model_desc || ""}`} + + + {theJob.clm_total} + + + {theJob.actual_in} + + + {theJob.scheduled_completion} + + + - )} + + {!bodyshop.uselocalmediaserver && ( + + )} +
)} diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index fbeb8740d..4a1c85036 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -5,7 +5,7 @@ export const QUERY_ALL_ACTIVE_JOBS_PAGINATED = gql` $offset: Int $limit: Int $order: [jobs_order_by!] - $statuses: [String!]!, + $statuses: [String!]! $isConverted: Boolean ) { jobs( @@ -120,7 +120,9 @@ export const QUERY_PARTS_QUEUE = gql` } } jobs( - where: { _and: [{ status: { _in: $statuses }, converted: { _eq: true } }] } + where: { + _and: [{ status: { _in: $statuses }, converted: { _eq: true } }] + } offset: $offset limit: $limit order_by: $order @@ -336,6 +338,7 @@ export const QUERY_JOBS_IN_PRODUCTION = gql` category iouparent ro_number + ownerid ownr_fn ownr_ln ownr_co_nm diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 09798f0a5..f81d5ff0b 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -1704,6 +1704,7 @@ "estimator": "Estimator", "filehandler": "File Handler", "insurance": "Insurance Details", + "more": "More", "notes": "Notes", "parts": "Parts", "totals": "Totals", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index da9f6b4e0..710db723d 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -1704,6 +1704,7 @@ "estimator": "Estimador", "filehandler": "File Handler", "insurance": "detalles del seguro", + "more": "Más", "notes": "Notas", "parts": "Partes", "totals": "Totales", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index a4c1dc686..0094ac983 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -1704,6 +1704,7 @@ "estimator": "Estimateur", "filehandler": "Gestionnaire de fichiers", "insurance": "Détails de l'assurance", + "more": "Plus", "notes": "Remarques", "parts": "les pièces", "totals": "Totaux", From 23c0f8e383a588dc153a41994fdefeacafc1fc2f Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 18 Jan 2024 16:07:17 -0500 Subject: [PATCH 22/67] - update handleBeta Signed-off-by: Dave Richer --- client/src/App/App.jsx | 7 +------ client/src/utils/handleBeta.js | 6 ++++-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/client/src/App/App.jsx b/client/src/App/App.jsx index 7d396eef8..7a4f72c41 100644 --- a/client/src/App/App.jsx +++ b/client/src/App/App.jsx @@ -54,17 +54,12 @@ export function App({ }) { const client = useClient(); - // Handle The Beta Switch. - useEffect(() => { - handleBeta(); - }, []) - - useEffect(() => { if (!navigator.onLine) { setOnline(false); } + handleBeta(); checkUserSession(); }, [checkUserSession, setOnline]); diff --git a/client/src/utils/handleBeta.js b/client/src/utils/handleBeta.js index 428d4fb49..8a1fba468 100644 --- a/client/src/utils/handleBeta.js +++ b/client/src/utils/handleBeta.js @@ -24,12 +24,14 @@ export const handleBeta = () => { // Beta is enabled, but the current host name does start with beta. if (isBeta && !currentHostName.startsWith('beta')) { - window.location.href = `${window.location.protocol}//beta.${currentHostName}${window.location.pathname}${window.location.search}${window.location.hash}`; + 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')) { - window.location.href = `${window.location.protocol}//${currentHostName.replace('beta.', '')}${window.location.pathname}${window.location.search}${window.location.hash}`; + const href = `${window.location.protocol}//${currentHostName.replace('beta.', '')}${window.location.pathname}${window.location.search}${window.location.hash}`; + window.location.replace(href); } } export default handleBeta; From e0e62a52be9d082eaa34d355818b8dbf57a4286e Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Thu, 18 Jan 2024 13:08:39 -0800 Subject: [PATCH 23/67] Cherry pick schema chengs from IO-2477 --- hasura/metadata/tables.yaml | 74 +++++++++++++++++++ .../down.sql | 1 + .../up.sql | 18 +++++ .../down.sql | 1 + .../up.sql | 18 +++++ 5 files changed, 112 insertions(+) create mode 100644 hasura/migrations/1705522419599_create_table_public_eulas/down.sql create mode 100644 hasura/migrations/1705522419599_create_table_public_eulas/up.sql create mode 100644 hasura/migrations/1705522869369_create_table_public_eula_acceptances/down.sql create mode 100644 hasura/migrations/1705522869369_create_table_public_eula_acceptances/up.sql diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml index 9867b3f8e..f4e4d99a7 100644 --- a/hasura/metadata/tables.yaml +++ b/hasura/metadata/tables.yaml @@ -2423,6 +2423,73 @@ _eq: X-Hasura-User-Id - active: _eq: true +- table: + name: eula_acceptances + schema: public + object_relationships: + - name: eula + using: + foreign_key_constraint_on: eulaid + - name: user + using: + foreign_key_constraint_on: useremail + insert_permissions: + - role: user + permission: + check: + user: + authid: + _eq: X-Hasura-User-Id + columns: + - address + - buisness_name + - date_accepted + - eulaid + - first_name + - last_name + - phone_number + - useremail + select_permissions: + - role: user + permission: + columns: + - address + - buisness_name + - first_name + - last_name + - phone_number + - useremail + - created_at + - date_accepted + - updated_at + - eulaid + - id + filter: + user: + authid: + _eq: X-Hasura-User-Id +- table: + name: eulas + schema: public + array_relationships: + - name: eula_acceptances + using: + foreign_key_constraint_on: + column: eulaid + table: + name: eula_acceptances + schema: public + select_permissions: + - role: user + permission: + columns: + - id + - created_at + - updated_at + - effective_date + - end_date + - content + filter: {} - table: name: exportlog schema: public @@ -5888,6 +5955,13 @@ table: name: email_audit_trail schema: public + - name: eula_acceptances + using: + foreign_key_constraint_on: + column: useremail + table: + name: eula_acceptances + schema: public - name: exportlogs using: foreign_key_constraint_on: diff --git a/hasura/migrations/1705522419599_create_table_public_eulas/down.sql b/hasura/migrations/1705522419599_create_table_public_eulas/down.sql new file mode 100644 index 000000000..bea9117e2 --- /dev/null +++ b/hasura/migrations/1705522419599_create_table_public_eulas/down.sql @@ -0,0 +1 @@ +DROP TABLE "public"."eulas"; diff --git a/hasura/migrations/1705522419599_create_table_public_eulas/up.sql b/hasura/migrations/1705522419599_create_table_public_eulas/up.sql new file mode 100644 index 000000000..31eaa5f8d --- /dev/null +++ b/hasura/migrations/1705522419599_create_table_public_eulas/up.sql @@ -0,0 +1,18 @@ +CREATE TABLE "public"."eulas" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "effective_date" timestamptz NOT NULL, "end_date" timestamptz, "content" text NOT NULL, PRIMARY KEY ("id") ); +CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"() +RETURNS TRIGGER AS $$ +DECLARE + _new record; +BEGIN + _new := NEW; + _new."updated_at" = NOW(); + RETURN _new; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER "set_public_eulas_updated_at" +BEFORE UPDATE ON "public"."eulas" +FOR EACH ROW +EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"(); +COMMENT ON TRIGGER "set_public_eulas_updated_at" ON "public"."eulas" +IS 'trigger to set value of column "updated_at" to current timestamp on row update'; +CREATE EXTENSION IF NOT EXISTS pgcrypto; diff --git a/hasura/migrations/1705522869369_create_table_public_eula_acceptances/down.sql b/hasura/migrations/1705522869369_create_table_public_eula_acceptances/down.sql new file mode 100644 index 000000000..29d08ee95 --- /dev/null +++ b/hasura/migrations/1705522869369_create_table_public_eula_acceptances/down.sql @@ -0,0 +1 @@ +DROP TABLE "public"."eula_acceptances"; diff --git a/hasura/migrations/1705522869369_create_table_public_eula_acceptances/up.sql b/hasura/migrations/1705522869369_create_table_public_eula_acceptances/up.sql new file mode 100644 index 000000000..18dc09e8e --- /dev/null +++ b/hasura/migrations/1705522869369_create_table_public_eula_acceptances/up.sql @@ -0,0 +1,18 @@ +CREATE TABLE "public"."eula_acceptances" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "eulaid" uuid NOT NULL, "date_accepted" timestamptz NOT NULL, "first_name" text NOT NULL, "last_name" text NOT NULL, "address" text NOT NULL, "phone_number" Text NOT NULL, "buisness_name" Text NOT NULL, "useremail" text NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("eulaid") REFERENCES "public"."eulas"("id") ON UPDATE restrict ON DELETE restrict, FOREIGN KEY ("useremail") REFERENCES "public"."users"("email") ON UPDATE restrict ON DELETE restrict); +CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"() +RETURNS TRIGGER AS $$ +DECLARE + _new record; +BEGIN + _new := NEW; + _new."updated_at" = NOW(); + RETURN _new; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER "set_public_eula_acceptances_updated_at" +BEFORE UPDATE ON "public"."eula_acceptances" +FOR EACH ROW +EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"(); +COMMENT ON TRIGGER "set_public_eula_acceptances_updated_at" ON "public"."eula_acceptances" +IS 'trigger to set value of column "updated_at" to current timestamp on row update'; +CREATE EXTENSION IF NOT EXISTS pgcrypto; From 829e6116922665c198cf858f99e48f5b3f258012 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 18 Jan 2024 16:16:06 -0500 Subject: [PATCH 24/67] - update handleBeta Signed-off-by: Dave Richer --- client/src/App/App.jsx | 3 +-- client/src/utils/handleBeta.js | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/client/src/App/App.jsx b/client/src/App/App.jsx index 7a4f72c41..2aae995c8 100644 --- a/client/src/App/App.jsx +++ b/client/src/App/App.jsx @@ -58,9 +58,8 @@ export function App({ if (!navigator.onLine) { setOnline(false); } - - handleBeta(); checkUserSession(); + handleBeta(); }, [checkUserSession, setOnline]); //const b = Grid.useBreakpoint(); diff --git a/client/src/utils/handleBeta.js b/client/src/utils/handleBeta.js index 8a1fba468..e1d8199c4 100644 --- a/client/src/utils/handleBeta.js +++ b/client/src/utils/handleBeta.js @@ -26,12 +26,14 @@ export const handleBeta = () => { 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); + window.location.reload(true); } // 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); + window.location.reload(true) } } export default handleBeta; From 0cab47f9849be095c82be5cd14eb12a4e89e836e Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 18 Jan 2024 16:22:58 -0500 Subject: [PATCH 25/67] - update handleBeta Signed-off-by: Dave Richer --- client/src/App/App.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/App/App.jsx b/client/src/App/App.jsx index 2aae995c8..2def5eaf2 100644 --- a/client/src/App/App.jsx +++ b/client/src/App/App.jsx @@ -59,7 +59,6 @@ export function App({ setOnline(false); } checkUserSession(); - handleBeta(); }, [checkUserSession, setOnline]); //const b = Grid.useBreakpoint(); @@ -108,6 +107,8 @@ export function App({ /> ); + handleBeta(); + return ( }> From bf6b1c202fa4292333302fcaae28da97b1ee5554 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 18 Jan 2024 16:35:30 -0500 Subject: [PATCH 26/67] - update handleBeta Signed-off-by: Dave Richer --- client/src/App/App.jsx | 1 + client/src/utils/handleBeta.js | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/client/src/App/App.jsx b/client/src/App/App.jsx index 2def5eaf2..4f074e5e9 100644 --- a/client/src/App/App.jsx +++ b/client/src/App/App.jsx @@ -73,6 +73,7 @@ export function App({ window.addEventListener("online", function (e) { setOnline(true); }); + useEffect(() => { if (currentUser.authorized && bodyshop) { client.setAttribute("imexshopid", bodyshop.imexshopid); diff --git a/client/src/utils/handleBeta.js b/client/src/utils/handleBeta.js index e1d8199c4..8a1fba468 100644 --- a/client/src/utils/handleBeta.js +++ b/client/src/utils/handleBeta.js @@ -26,14 +26,12 @@ export const handleBeta = () => { 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); - window.location.reload(true); } // 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); - window.location.reload(true) } } export default handleBeta; From cb8632641e0894fa1e7973e6bc13067297209bea Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 18 Jan 2024 17:28:46 -0800 Subject: [PATCH 27/67] IO-2608 Filter and Sort columns in Employee table --- .../shop-employees-list.component.jsx | 77 ++++++++++++++++--- client/src/graphql/employees.queries.js | 7 +- client/src/translations/en_us/common.json | 3 + client/src/translations/es/common.json | 3 + client/src/translations/fr/common.json | 3 + 5 files changed, 80 insertions(+), 13 deletions(-) diff --git a/client/src/components/shop-employees/shop-employees-list.component.jsx b/client/src/components/shop-employees/shop-employees-list.component.jsx index 416e875f9..dd540745f 100644 --- a/client/src/components/shop-employees/shop-employees-list.component.jsx +++ b/client/src/components/shop-employees/shop-employees-list.component.jsx @@ -1,14 +1,20 @@ import { Button, Table } from "antd"; import queryString from "query-string"; -import React from "react"; +import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { useHistory, useLocation } from "react-router-dom"; +import { alphaSort } from "../../utils/sorters"; export default function ShopEmployeesListComponent({ loading, employees }) { const { t } = useTranslation(); const history = useHistory(); const search = queryString.parse(useLocation().search); + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: { text: "" }, + }); + const handleOnRowClick = (record) => { if (record) { search.employeeId = record.id; @@ -18,32 +24,82 @@ export default function ShopEmployeesListComponent({ loading, employees }) { history.push({ search: queryString.stringify(search) }); } }; + + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + const columns = [ { title: t("employees.fields.employee_number"), dataIndex: "employee_number", key: "employee_number", + sorter: (a, b) => alphaSort(a.employee_number, b.employee_number), + sortOrder: + state.sortedInfo.columnKey === "employee_number" && + state.sortedInfo.order, }, { - title: t("employees.fields.first_name"), - dataIndex: "first_name", - key: "first_name", + title: t("employees.labels.name"), + dataIndex: "employee_name", + key: "employee_name", + sorter: (a, b) => + alphaSort( + `${a.first_name || ""} ${a.last_name || ""}`.trim(), + `${b.first_name || ""} ${b.last_name || ""}`.trim() + ), + sortOrder: + state.sortedInfo.columnKey === "employee_name" && + state.sortedInfo.order, + render: (text, record) => + `${record.first_name || ""} ${record.last_name || ""}`.trim(), }, - { - title: t("employees.fields.last_name"), - dataIndex: "last_name", - key: "last_name", - }, - { title: t("employees.labels.rate_type"), dataIndex: "rate_type", key: "rate_type", + sorter: (a, b) => Number(a.flat_rate) - Number(b.flat_rate), + sortOrder: + state.sortedInfo.columnKey === "rate_type" && state.sortedInfo.order, + filters: [ + { + text: t("employees.labels.flat_rate"), + value: true, + }, + { + text: t("employees.labels.straight_time"), + value: false, + }, + ], + onFilter: (value, record) => value === record.flate_rate, render: (text, record) => record.flat_rate ? t("employees.labels.flat_rate") : t("employees.labels.straight_time"), }, + { + title: t("employees.labels.status"), + dataIndex: "active", + key: "active", + sorter: (a, b) => Number(a.active) - Number(b.active), + sortOrder: + state.sortedInfo.columnKey === "active" && state.sortedInfo.order, + filters: [ + { + text: t("employees.labels.active"), + value: true, + }, + { + text: t("employees.labels.inactive"), + value: false, + }, + ], + onFilter: (value, record) => value === record.active, + render: (text, record) => + record.active + ? t("employees.labels.active") + : t("employees.labels.inactive"), + }, ]; return (
@@ -74,6 +130,7 @@ export default function ShopEmployeesListComponent({ loading, employees }) { type: "radio", selectedRowKeys: [search.employeeId], }} + onChange={handleTableChange} onRow={(record, rowIndex) => { return { onClick: (event) => { diff --git a/client/src/graphql/employees.queries.js b/client/src/graphql/employees.queries.js index 207c10bab..e34d2be3b 100644 --- a/client/src/graphql/employees.queries.js +++ b/client/src/graphql/employees.queries.js @@ -3,11 +3,12 @@ import { gql } from "@apollo/client"; export const QUERY_EMPLOYEES = gql` query QUERY_EMPLOYEES { employees(order_by: { employee_number: asc }) { - last_name - id + active + employee_number first_name flat_rate - employee_number + id + last_name } } `; diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 09798f0a5..4842927d6 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -1001,10 +1001,13 @@ }, "labels": { "actions": "Actions", + "active": "Active", "endmustbeafterstart": "End date must be after start date.", "flat_rate": "Flat Rate", + "inactive": "Inactive", "name": "Name", "rate_type": "Rate Type", + "status": "Status", "straight_time": "Straight Time" }, "successes": { diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index da9f6b4e0..4e96502e2 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -1001,10 +1001,13 @@ }, "labels": { "actions": "", + "active": "", "endmustbeafterstart": "", "flat_rate": "", + "inactive": "", "name": "", "rate_type": "", + "status": "", "straight_time": "" }, "successes": { diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index a4c1dc686..707f2f16a 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -1001,10 +1001,13 @@ }, "labels": { "actions": "", + "active": "", "endmustbeafterstart": "", "flat_rate": "", + "inactive": "", "name": "", "rate_type": "", + "status": "", "straight_time": "" }, "successes": { From d06037df1fea4e825e808173570bd2717c88ff7c Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Fri, 19 Jan 2024 08:47:17 -0800 Subject: [PATCH 28/67] IO-2598 Restrict IOU from Tech Console --- .../job-create-iou.component.jsx | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/client/src/components/job-create-iou/job-create-iou.component.jsx b/client/src/components/job-create-iou/job-create-iou.component.jsx index 8ebc8fb75..930f27e25 100644 --- a/client/src/components/job-create-iou/job-create-iou.component.jsx +++ b/client/src/components/job-create-iou/job-create-iou.component.jsx @@ -7,21 +7,31 @@ import { connect } from "react-redux"; import { useHistory } from "react-router"; import { createStructuredSelector } from "reselect"; import { UPDATE_JOB_LINES_IOU } from "../../graphql/jobs-lines.queries"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; import { selectBodyshop, selectCurrentUser, } from "../../redux/user/user.selectors"; import { CreateIouForJob } from "../jobs-detail-header-actions/jobs-detail-header-actions.duplicate.util"; + const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, currentUser: selectCurrentUser, + technician: selectTechnician, }); + const mapDispatchToProps = (dispatch) => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(JobCreateIOU); -export function JobCreateIOU({ bodyshop, currentUser, job, selectedJobLines }) { +export function JobCreateIOU({ + bodyshop, + currentUser, + job, + selectedJobLines, + technician, +}) { const { t } = useTranslation(); const [loading, setLoading] = useState(false); const client = useApolloClient(); @@ -79,13 +89,19 @@ export function JobCreateIOU({ bodyshop, currentUser, job, selectedJobLines }) { title={t("jobs.labels.createiouwarning")} onConfirm={handleCreateIou} disabled={ - !selectedJobLines || selectedJobLines.length === 0 || !job.converted + !selectedJobLines || + selectedJobLines.length === 0 || + !job.converted || + technician } >
+ + + + {lifecycleData.map((item, index) => ( + + {item.value} - {new Date(item.start).toLocaleString()} + + ))} + + + + + ); +} + +export default connect(mapStateToProps, mapDispatchToProps)(JobLifecycleComponent); diff --git a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx index 001a18166..a7d51d5e3 100644 --- a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx +++ b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx @@ -54,6 +54,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; import UndefinedToNull from "../../utils/undefinedtonull"; import { DateTimeFormat } from "./../../utils/DateFormatter"; +import JobLifecycleComponent from "../../components/job-lifecycle/job-lifecycle.component"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -288,6 +289,13 @@ export function JobsDetailPage({ form={form} /> + Lifecycle} + key="lifecycle" + > + + { const {jobids} = req.body; @@ -11,11 +12,17 @@ const handleMultipleJobs = (jobIDs, req, res) => { return res.status(200).send(jobIDs); } -const handleSingleJob = (req, res) => { - +const handleSingleJob = async (jobIds, req, res) => { const client = req.userGraphQLClient; - return res.status(200).send(req.body); + const resp = await client.request(queries.QUERY_TRANSITIONS_BY_JOBID, {jobid: jobIds,}); + + const response = { + jobIds, + data: resp + } + + return res.status(200).json(response); } module.exports = jobLifecycle; \ No newline at end of file diff --git a/server/middleware/eventAuthorizationMIddleware.js b/server/middleware/eventAuthorizationMIddleware.js index c766ceda5..423fbc73f 100644 --- a/server/middleware/eventAuthorizationMIddleware.js +++ b/server/middleware/eventAuthorizationMIddleware.js @@ -1,3 +1,11 @@ +const path = require("path"); +require("dotenv").config({ + path: path.resolve( + process.cwd(), + `.env.${process.env.NODE_ENV || "development"}` + ), +}); + /** * Checks if the event secret is correct * It adds the following properties to the request object: diff --git a/server/routes/jobRoutes.js b/server/routes/jobRoutes.js index e11187041..9b4a5e9f6 100644 --- a/server/routes/jobRoutes.js +++ b/server/routes/jobRoutes.js @@ -7,14 +7,12 @@ const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebas const {totals, statustransition, totalsSsu, costing, lifecycle, costingmulti} = require("../job/job"); const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware"); -router.use(validateFirebaseIdTokenMiddleware); - -router.post('/totals', withUserGraphQLClientMiddleware, totals); +router.post('/totals', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, totals); router.post('/statustransition', eventAuthorizationMiddleware, statustransition); -router.post('/totalsssu', withUserGraphQLClientMiddleware,totalsSsu); -router.post('/costing', withUserGraphQLClientMiddleware,costing); -router.get('/lifecycle', withUserGraphQLClientMiddleware, lifecycle); -router.post('/costingmulti', withUserGraphQLClientMiddleware, costingmulti); -router.post('/partsscan', withUserGraphQLClientMiddleware, partsScan); +router.post('/totalsssu', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware,totalsSsu); +router.post('/costing', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware,costing); +router.post('/lifecycle', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, lifecycle); +router.post('/costingmulti', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, costingmulti); +router.post('/partsscan', validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, partsScan); module.exports = router; From cfe072744711fb98826e1b43ce70383e62278586 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 23 Jan 2024 10:20:26 -0500 Subject: [PATCH 44/67] - Rough in front end / backend Signed-off-by: Dave Richer --- .../job-lifecycle/job-lifecycle.component.jsx | 34 +++++++++++-------- .../eventAuthorizationMIddleware.js | 6 ---- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/client/src/components/job-lifecycle/job-lifecycle.component.jsx b/client/src/components/job-lifecycle/job-lifecycle.component.jsx index dc6925055..c9303177e 100644 --- a/client/src/components/job-lifecycle/job-lifecycle.component.jsx +++ b/client/src/components/job-lifecycle/job-lifecycle.component.jsx @@ -13,7 +13,7 @@ const mapDispatchToProps = (dispatch) => ({ }); export function JobLifecycleComponent({bodyshop, job, ...rest}) { - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(true); const [lifecycleData, setLifecycleData] = useState(null); @@ -56,20 +56,26 @@ export function JobLifecycleComponent({bodyshop, job, ...rest}) { return ( - - -
+ {!loading ? ( + + +
+ + + + {lifecycleData.map((item, index) => ( + + {item.value} - {new Date(item.start).toLocaleString()} + + ))} + + + + ) : ( + + Loading Job Timelines.... - - - {lifecycleData.map((item, index) => ( - - {item.value} - {new Date(item.start).toLocaleString()} - - ))} - - - + )} ); } diff --git a/server/middleware/eventAuthorizationMIddleware.js b/server/middleware/eventAuthorizationMIddleware.js index 423fbc73f..9dd4dfd3a 100644 --- a/server/middleware/eventAuthorizationMIddleware.js +++ b/server/middleware/eventAuthorizationMIddleware.js @@ -1,10 +1,4 @@ const path = require("path"); -require("dotenv").config({ - path: path.resolve( - process.cwd(), - `.env.${process.env.NODE_ENV || "development"}` - ), -}); /** * Checks if the event secret is correct From f59bdf90303fb6a931c7fa6d16424d6a2da682df Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 23 Jan 2024 12:35:58 -0500 Subject: [PATCH 45/67] - Progress Update Signed-off-by: Dave Richer --- .../job-lifecycle/job-lifecycle.component.jsx | 100 +++++++++++++++--- server/graphql-client/queries.js | 5 +- server/job/job-lifecycle.js | 75 ++++++++++--- 3 files changed, 147 insertions(+), 33 deletions(-) diff --git a/client/src/components/job-lifecycle/job-lifecycle.component.jsx b/client/src/components/job-lifecycle/job-lifecycle.component.jsx index c9303177e..3d356c575 100644 --- a/client/src/components/job-lifecycle/job-lifecycle.component.jsx +++ b/client/src/components/job-lifecycle/job-lifecycle.component.jsx @@ -1,9 +1,10 @@ import {createStructuredSelector} from "reselect"; import {selectBodyshop} from "../../redux/user/user.selectors"; import {connect} from "react-redux"; -import {useEffect, useState} from "react"; +import {useCallback, useEffect, useState} from "react"; import axios from "axios"; import {Card, Space, Table, Timeline} from "antd"; +import {Cell, LabelList, Legend, Pie, PieChart, Tooltip} from "recharts"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -12,6 +13,9 @@ const mapDispatchToProps = (dispatch) => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) }); +const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884d8']; + + export function JobLifecycleComponent({bodyshop, job, ...rest}) { const [loading, setLoading] = useState(true); const [lifecycleData, setLifecycleData] = useState(null); @@ -24,8 +28,8 @@ export function JobLifecycleComponent({bodyshop, job, ...rest}) { const response = await axios.post("/job/lifecycle", { jobids: job.id, }); - console.dir(response.data.data.transitions, {depth: null}); - setLifecycleData(response.data.data.transitions); + const data = response.data.transition[job.id]; + setLifecycleData(data); setLoading(false); } } @@ -36,6 +40,11 @@ export function JobLifecycleComponent({bodyshop, job, ...rest}) { }); }, [job]); + // // TODO - Delete this useEffect, it is for testing + // useEffect(() => { + // console.dir(lifecycleData) + // }, [lifecycleData]); + const columnKeys = [ 'start', 'end', @@ -54,23 +63,82 @@ export function JobLifecycleComponent({bodyshop, job, ...rest}) { key: key, })); + /** + * Returns an array of cells for the Pie Chart + * @type {function(): *[]} + */ + const renderCells = useCallback(() => { + const entires = Object + .entries(lifecycleData.durations) + .filter(([name, value]) => { + return value !== 0; + }) + + return entires.map(([name, value], index) => ( + + + + + )); + }, [lifecycleData, job]); + + /** + * Returns an array of objects with the name and value of the duration + * @type {function(): {name: *, value}[]} + */ + const durationsData = useCallback(() => { + return Object.entries(lifecycleData.durations) .filter(([name, value]) => { + return value !== 0; + }).map(([name, value]) => ({ + name, + value: value / 1000 + })) + }, [lifecycleData, job]); + return ( {!loading ? ( - - -
+ lifecycleData ? ( + + +
+ + + + + {lifecycleData.lifecycle.map((item, index) => ( + + {item.value} - {new Date(item.start).toLocaleString()} + + ))} + + + + + `${name}: ${(percent * 100).toFixed(0)}%`} + outerRadius={80} + fill="#8884d8" + dataKey="value" + > + {renderCells()} + + + + + + + + + ) : ( + + There is currently no lifecycle data for this job. - - - {lifecycleData.map((item, index) => ( - - {item.value} - {new Date(item.start).toLocaleString()} - - ))} - - - + ) ) : ( Loading Job Timelines.... diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index a642a4988..782bd3100 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -518,8 +518,8 @@ exports.QUERY_PAYMENTS_FOR_EXPORT = ` } }`; -exports.QUERY_TRANSITIONS_BY_JOBID = `query QUERY_TRANSITIONS_BY_JOBID($jobid: uuid!) { - transitions(where: {jobid: {_eq: $jobid}}, order_by: {id: asc}) { +exports.QUERY_TRANSITIONS_BY_JOBID = `query QUERY_TRANSITIONS_BY_JOBID($jobids: [uuid!]!) { + transitions(where: {jobid: {_in: $jobids}}, order_by: {created_at: asc}) { start end value @@ -529,6 +529,7 @@ exports.QUERY_TRANSITIONS_BY_JOBID = `query QUERY_TRANSITIONS_BY_JOBID($jobid: u type created_at updated_at + jobid } }`; diff --git a/server/job/job-lifecycle.js b/server/job/job-lifecycle.js index 207a514e3..8eddd86a5 100644 --- a/server/job/job-lifecycle.js +++ b/server/job/job-lifecycle.js @@ -1,28 +1,73 @@ const _ = require("lodash"); const queries = require("../graphql-client/queries"); -const jobLifecycle = (req, res) => { + +const calculateStatusDuration = (transitions) => { + let statusDuration = {}; + + transitions.forEach((transition, index) => { + let duration = transition.duration; + + // If there is no prev_value, it is the first transition + if (!transition.prev_value) { + statusDuration[transition.value] = duration; + } + // If there is no next_value, it is the last transition (the active one) + else if (!transition.next_value) { + if (statusDuration[transition.value]) { + statusDuration[transition.value] += duration; + } else { + statusDuration[transition.value] = duration; + } + } + // For all other transitions + else { + if (statusDuration[transition.value]) { + statusDuration[transition.value] += duration; + } else { + statusDuration[transition.value] = duration; + } + } + }); + + return statusDuration; +} + + +const jobLifecycle = async (req, res) => { const {jobids} = req.body; - return _.isArray(jobids) ? - handleMultipleJobs(jobids, req, res) : - handleSingleJob(jobids, req, res); -}; + const jobIDs = _.isArray(jobids) ? jobids : [jobids]; -const handleMultipleJobs = (jobIDs, req, res) => { - return res.status(200).send(jobIDs); -} - -const handleSingleJob = async (jobIds, req, res) => { const client = req.userGraphQLClient; - const resp = await client.request(queries.QUERY_TRANSITIONS_BY_JOBID, {jobid: jobIds,}); + const resp = await client.request(queries.QUERY_TRANSITIONS_BY_JOBID, {jobids: jobIDs,}); + + const transitions = resp.transitions; + + if (!transitions) { + return res.status(200).json({ + jobIDs, + transitions: [] + }); - const response = { - jobIds, - data: resp } - return res.status(200).json(response); + const transitionsByJobId = _.groupBy(resp.transitions, 'jobid'); + + const groupedTransitions = {}; + + for (let jobId in transitionsByJobId) { + groupedTransitions[jobId] = { + lifecycle: transitionsByJobId[jobId], + durations: calculateStatusDuration(transitionsByJobId[jobId]) + }; + } + + return res.status(200).json({ + jobIDs, + transition: groupedTransitions, + }); } + module.exports = jobLifecycle; \ No newline at end of file From 5de4ef5d837a104ccf018b598b38fab4b87e1fe5 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 23 Jan 2024 12:54:38 -0500 Subject: [PATCH 46/67] - human readable dates Signed-off-by: Dave Richer --- .../job-lifecycle/job-lifecycle.component.jsx | 10 +++++---- server/job/job-lifecycle.js | 22 +++++++++++++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/client/src/components/job-lifecycle/job-lifecycle.component.jsx b/client/src/components/job-lifecycle/job-lifecycle.component.jsx index 3d356c575..626d26545 100644 --- a/client/src/components/job-lifecycle/job-lifecycle.component.jsx +++ b/client/src/components/job-lifecycle/job-lifecycle.component.jsx @@ -54,7 +54,9 @@ export function JobLifecycleComponent({bodyshop, job, ...rest}) { 'duration', 'type', 'created_at', - 'updated_at' + 'updated_at', + 'start_readable', + 'end_readable', ]; const columns = columnKeys.map(key => ({ @@ -71,7 +73,7 @@ export function JobLifecycleComponent({bodyshop, job, ...rest}) { const entires = Object .entries(lifecycleData.durations) .filter(([name, value]) => { - return value !== 0; + return value > 0; }) return entires.map(([name, value], index) => ( @@ -88,7 +90,7 @@ export function JobLifecycleComponent({bodyshop, job, ...rest}) { */ const durationsData = useCallback(() => { return Object.entries(lifecycleData.durations) .filter(([name, value]) => { - return value !== 0; + return value > 0; }).map(([name, value]) => ({ name, value: value / 1000 @@ -108,7 +110,7 @@ export function JobLifecycleComponent({bodyshop, job, ...rest}) { {lifecycleData.lifecycle.map((item, index) => ( - {item.value} - {new Date(item.start).toLocaleString()} + {item.value} - {item.start_readable} ))} diff --git a/server/job/job-lifecycle.js b/server/job/job-lifecycle.js index 8eddd86a5..bc24e9c8a 100644 --- a/server/job/job-lifecycle.js +++ b/server/job/job-lifecycle.js @@ -1,5 +1,6 @@ const _ = require("lodash"); const queries = require("../graphql-client/queries"); +const moment = require("moment"); const calculateStatusDuration = (transitions) => { let statusDuration = {}; @@ -52,17 +53,30 @@ const jobLifecycle = async (req, res) => { } + const transitionsByJobId = _.groupBy(resp.transitions, 'jobid'); const groupedTransitions = {}; - + moment.relativeTimeThreshold('m', 30) for (let jobId in transitionsByJobId) { + let lifecycle = transitionsByJobId[jobId].map(transition => { + if (transition.start) { + transition.start_readable = moment(transition.start).fromNow(); + } + if (transition.end) { + transition.end_readable = moment(transition.end).fromNow(); + } + return transition; + }); + groupedTransitions[jobId] = { - lifecycle: transitionsByJobId[jobId], - durations: calculateStatusDuration(transitionsByJobId[jobId]) + lifecycle: lifecycle, + durations: calculateStatusDuration(lifecycle) }; } - + + console.dir(groupedTransitions, {depth: null}); + return res.status(200).json({ jobIDs, transition: groupedTransitions, From d0a2bb7da0610f2773dbb3abb8dc66966334eb17 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 23 Jan 2024 12:58:57 -0500 Subject: [PATCH 47/67] - human readable dates Signed-off-by: Dave Richer --- client/src/components/job-lifecycle/job-lifecycle.component.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/components/job-lifecycle/job-lifecycle.component.jsx b/client/src/components/job-lifecycle/job-lifecycle.component.jsx index 626d26545..8122229af 100644 --- a/client/src/components/job-lifecycle/job-lifecycle.component.jsx +++ b/client/src/components/job-lifecycle/job-lifecycle.component.jsx @@ -15,7 +15,6 @@ const mapDispatchToProps = (dispatch) => ({ const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884d8']; - export function JobLifecycleComponent({bodyshop, job, ...rest}) { const [loading, setLoading] = useState(true); const [lifecycleData, setLifecycleData] = useState(null); From d740446ccb7ecbe7494f4e8e2f76e40c0c26d6a7 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 24 Jan 2024 10:07:07 -0500 Subject: [PATCH 48/67] - Progress Signed-off-by: Dave Richer --- .../job-lifecycle/job-lifecycle.component.jsx | 165 ++++++++---------- .../jobs-detail.page.component.jsx | 2 +- server/job/job-lifecycle.js | 31 +++- 3 files changed, 92 insertions(+), 106 deletions(-) diff --git a/client/src/components/job-lifecycle/job-lifecycle.component.jsx b/client/src/components/job-lifecycle/job-lifecycle.component.jsx index 8122229af..62ecb920a 100644 --- a/client/src/components/job-lifecycle/job-lifecycle.component.jsx +++ b/client/src/components/job-lifecycle/job-lifecycle.component.jsx @@ -1,139 +1,110 @@ -import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors"; -import {connect} from "react-redux"; -import {useCallback, useEffect, useState} from "react"; -import axios from "axios"; -import {Card, Space, Table, Timeline} from "antd"; -import {Cell, LabelList, Legend, Pie, PieChart, Tooltip} from "recharts"; +import React, {useEffect, useMemo, useState} from 'react'; +import axios from 'axios'; +import {Card, Space, Table, Timeline} from 'antd'; +import {Bar, BarChart, CartesianGrid, LabelList, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis} from 'recharts'; -const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, -}); -const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) -}); +export function JobLifecycleComponent({job, ...rest}) { -const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884d8']; - -export function JobLifecycleComponent({bodyshop, job, ...rest}) { const [loading, setLoading] = useState(true); const [lifecycleData, setLifecycleData] = useState(null); - useEffect(() => { - async function getLifecycleData() { + const getLifecycleData = async () => { if (job && job.id) { - setLoading(true); - const response = await axios.post("/job/lifecycle", { - jobids: job.id, - }); - const data = response.data.transition[job.id]; - setLifecycleData(data); - setLoading(false); + try { + setLoading(true); + const response = await axios.post("/job/lifecycle", {jobids: job.id}); + const data = response.data.transition[job.id]; + setLifecycleData(data); + } catch (err) { + console.error(`Error getting Job Lifecycle Data: ${err.message}`); + } finally { + setLoading(false); + } } - } + }; - getLifecycleData().catch((err) => { - console.log(`Something went wrong getting Job Lifecycle Data: ${err.message}`); - setLoading(false); - }); + getLifecycleData(); }, [job]); - // // TODO - Delete this useEffect, it is for testing - // useEffect(() => { - // console.dir(lifecycleData) - // }, [lifecycleData]); - const columnKeys = [ - 'start', - 'end', - 'value', - 'prev_value', - 'next_value', - 'duration', - 'type', - 'created_at', - 'updated_at', - 'start_readable', - 'end_readable', + 'start', 'end', 'value', 'prev_value', 'next_value', 'duration', 'type', 'created_at', 'updated_at', 'start_readable', 'end_readable','duration' ]; const columns = columnKeys.map(key => ({ - title: key.charAt(0).toUpperCase() + key.slice(1), // Capitalize the first letter for the title + title: key.charAt(0).toUpperCase() + key.slice(1), dataIndex: key, key: key, })); - /** - * Returns an array of cells for the Pie Chart - * @type {function(): *[]} - */ - const renderCells = useCallback(() => { - const entires = Object - .entries(lifecycleData.durations) - .filter(([name, value]) => { - return value > 0; - }) - return entires.map(([name, value], index) => ( - - - - - )); - }, [lifecycleData, job]); + const durationsData = useMemo(() => { + if (!lifecycleData) { + return []; + } - /** - * Returns an array of objects with the name and value of the duration - * @type {function(): {name: *, value}[]} - */ - const durationsData = useCallback(() => { - return Object.entries(lifecycleData.durations) .filter(([name, value]) => { - return value > 0; - }).map(([name, value]) => ({ - name, - value: value / 1000 - })) - }, [lifecycleData, job]); + const transformedData = Object.entries(lifecycleData.durations).map(([name, {value, humanReadable}]) => { + return { + name, + amt: value, + pv: humanReadable, + uv: value, + } + }) + + return [transformedData]; + }, [lifecycleData]); + + + useEffect(() => { + console.dir(lifecycleData, {depth: null}) + console.dir(durationsData, {depth: null}) + + }, [lifecycleData,durationsData]); return ( {!loading ? ( lifecycleData ? ( - -
- {lifecycleData.lifecycle.map((item, index) => ( - + {item.value} - {item.start_readable} ))} - - `${name}: ${(percent * 100).toFixed(0)}%`} - outerRadius={80} - fill="#8884d8" - dataKey="value" + + - {renderCells()} - - - - + + + + + + + + + - + +
+ ) : ( @@ -149,4 +120,4 @@ export function JobLifecycleComponent({bodyshop, job, ...rest}) { ); } -export default connect(mapStateToProps, mapDispatchToProps)(JobLifecycleComponent); +export default JobLifecycleComponent; \ No newline at end of file diff --git a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx index a7d51d5e3..0f335d982 100644 --- a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx +++ b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx @@ -294,7 +294,7 @@ export function JobsDetailPage({ tab={Lifecycle} key="lifecycle" > - + { let statusDuration = {}; transitions.forEach((transition, index) => { - let duration = transition.duration; + let duration = transition.duration_minutes; // If there is no prev_value, it is the first transition if (!transition.prev_value) { - statusDuration[transition.value] = duration; + statusDuration[transition.value] = { + value: duration, + humanReadable: transition.duration_readable + }; } // If there is no next_value, it is the last transition (the active one) else if (!transition.next_value) { if (statusDuration[transition.value]) { - statusDuration[transition.value] += duration; + statusDuration[transition.value].value += duration; + statusDuration[transition.value].humanReadable = transition.duration_readable; } else { - statusDuration[transition.value] = duration; + statusDuration[transition.value] = { + value: duration, + humanReadable: transition.duration_readable + }; } } // For all other transitions else { if (statusDuration[transition.value]) { - statusDuration[transition.value] += duration; + statusDuration[transition.value].value += duration; + statusDuration[transition.value].humanReadable = transition.duration_readable; } else { - statusDuration[transition.value] = duration; + statusDuration[transition.value] = { + value: duration, + humanReadable: transition.duration_readable + }; } } }); @@ -53,7 +64,6 @@ const jobLifecycle = async (req, res) => { } - const transitionsByJobId = _.groupBy(resp.transitions, 'jobid'); const groupedTransitions = {}; @@ -66,6 +76,11 @@ const jobLifecycle = async (req, res) => { if (transition.end) { transition.end_readable = moment(transition.end).fromNow(); } + if(transition.duration){ + transition.duration_seconds = Math.round(transition.duration / 1000); + transition.duration_minutes = Math.round(transition.duration_seconds / 60); + transition.duration_readable = moment.duration(transition.duration).humanize(); + } return transition; }); @@ -75,7 +90,7 @@ const jobLifecycle = async (req, res) => { }; } - console.dir(groupedTransitions, {depth: null}); + console.dir(groupedTransitions, {depth: null}) return res.status(200).json({ jobIDs, From 5ea64ed805533994799e4e023a21dd84905344b1 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 24 Jan 2024 17:18:43 -0500 Subject: [PATCH 49/67] - Progress Signed-off-by: Dave Richer --- .../job-lifecycle/job-lifecycle.component.jsx | 178 +++++++++++------- client/src/utils/DateFormatter.jsx | 3 + server/graphql-client/queries.js | 2 +- server/job/job-lifecycle.js | 74 ++------ server/utils/calculateStatusDuration.js | 59 ++++++ server/utils/durationToHumanReadable.js | 22 +++ 6 files changed, 215 insertions(+), 123 deletions(-) create mode 100644 server/utils/calculateStatusDuration.js create mode 100644 server/utils/durationToHumanReadable.js diff --git a/client/src/components/job-lifecycle/job-lifecycle.component.jsx b/client/src/components/job-lifecycle/job-lifecycle.component.jsx index 62ecb920a..3f48e826e 100644 --- a/client/src/components/job-lifecycle/job-lifecycle.component.jsx +++ b/client/src/components/job-lifecycle/job-lifecycle.component.jsx @@ -1,89 +1,135 @@ -import React, {useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useEffect, useState} from 'react'; +import moment from "moment"; import axios from 'axios'; -import {Card, Space, Table, Timeline} from 'antd'; -import {Bar, BarChart, CartesianGrid, LabelList, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis} from 'recharts'; +import {Card, Space, Table} from 'antd'; +import {gql, useQuery} from "@apollo/client"; +import {DateTimeFormatterFunction} from "../../utils/DateFormatter"; +import {isEmpty} from "lodash"; +import {Bar, BarChart, CartesianGrid, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis} from "recharts"; + +const transformDataForChart = (durations) => { + const output = {}; + output.total = durations.total; + return durations.summations.forEach((summation) => { + output[summation.status] = summation.value + }); +}; +const getColor = (key) => { + // Generate a random color + const randomColor = '#' + Math.floor(Math.random()*16777215).toString(16); + return randomColor; +}; + export function JobLifecycleComponent({job, ...rest}) { const [loading, setLoading] = useState(true); const [lifecycleData, setLifecycleData] = useState(null); - useEffect(() => { - const getLifecycleData = async () => { - if (job && job.id) { - try { - setLoading(true); - const response = await axios.post("/job/lifecycle", {jobids: job.id}); - const data = response.data.transition[job.id]; - setLifecycleData(data); - } catch (err) { - console.error(`Error getting Job Lifecycle Data: ${err.message}`); - } finally { - setLoading(false); - } + // Used for tracking external state changes. + const {data} = useQuery(gql` + query get_job_test($id: uuid!){ + jobs_by_pk(id:$id){ + id + status } - }; + } + `, { + variables: { + id: job.id + }, + fetchPolicy: 'cache-only' + }); - getLifecycleData(); + /** + * Gets the lifecycle data for the job. + * @returns {Promise} + */ + const getLifecycleData = useCallback(async () => { + if (job && job.id) { + try { + setLoading(true); + const response = await axios.post("/job/lifecycle", {jobids: job.id}); + const data = response.data.transition[job.id]; + setLifecycleData(data); + } catch (err) { + console.error(`Error getting Job Lifecycle Data: ${err.message}`); + } finally { + setLoading(false); + } + } }, [job]); - const columnKeys = [ - 'start', 'end', 'value', 'prev_value', 'next_value', 'duration', 'type', 'created_at', 'updated_at', 'start_readable', 'end_readable','duration' + useEffect(() => { + if (!data) return; + setTimeout(() => { + getLifecycleData().catch(err => console.error(`Error getting Job Lifecycle Data: ${err.message}`)); + }, 1000); + }, [data, getLifecycleData]); + + const columns = [ + { + title: 'Value', + dataIndex: 'value', + key: 'value', + }, + { + title: 'Start', + dataIndex: 'start', + key: 'start', + render: (text) => DateTimeFormatterFunction(text), + sorter: (a, b) => moment(a.start).unix() - moment(b.start).unix(), + }, + { + title: 'Relative Start', + dataIndex: 'start_readable', + key: 'start_readable', + }, + { + title: 'End', + dataIndex: 'end', + key: 'end', + sorter: (a, b) => { + if (isEmpty(a.end) || isEmpty(b.end)) { + if (isEmpty(a.end) && isEmpty(b.end)) { + return 0; + } + return isEmpty(a.end) ? 1 : -1; + } + return moment(a.end).unix() - moment(b.end).unix(); + }, + render: (text) => isEmpty(text) ? 'N/A' : DateTimeFormatterFunction(text) + }, + { + title: 'Relative End', + dataIndex: 'end_readable', + key: 'end_readable', + }, + { + title: 'Duration', + dataIndex: 'duration_readable', + key: 'duration_readable', + sorter: (a, b) => a.duration - b.duration, + }, ]; - const columns = columnKeys.map(key => ({ - title: key.charAt(0).toUpperCase() + key.slice(1), - dataIndex: key, - key: key, - })); - - - const durationsData = useMemo(() => { - if (!lifecycleData) { - return []; - } - - const transformedData = Object.entries(lifecycleData.durations).map(([name, {value, humanReadable}]) => { - return { - name, - amt: value, - pv: humanReadable, - uv: value, - } - }) - - return [transformedData]; - }, [lifecycleData]); - - useEffect(() => { + console.log('LifeCycle Data'); console.dir(lifecycleData, {depth: null}) - console.dir(durationsData, {depth: null}) - - }, [lifecycleData,durationsData]); + }, [lifecycleData]); return ( {!loading ? ( - lifecycleData ? ( + lifecycleData && lifecycleData.lifecycle && lifecycleData.durations ? ( - - - {lifecycleData.lifecycle.map((item, index) => ( - - {item.value} - {item.start_readable} - - ))} - - - - + {lifecycleData.durations.summations.map((summation, idx) => { + + return ( + + ); + })} + + - +
diff --git a/client/src/utils/DateFormatter.jsx b/client/src/utils/DateFormatter.jsx index d034266e3..e8137c54d 100644 --- a/client/src/utils/DateFormatter.jsx +++ b/client/src/utils/DateFormatter.jsx @@ -17,6 +17,9 @@ export function DateTimeFormatter(props) { ) : null; } +export function DateTimeFormatterFunction(date) { + return moment(date).format("MM/DD/YYYY hh:mm a"); +} export function TimeFormatter(props) { return props.children ? moment(props.children).format(props.format ? props.format : "hh:mm a") diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index 782bd3100..2bd83d1ab 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -519,7 +519,7 @@ exports.QUERY_PAYMENTS_FOR_EXPORT = ` }`; exports.QUERY_TRANSITIONS_BY_JOBID = `query QUERY_TRANSITIONS_BY_JOBID($jobids: [uuid!]!) { - transitions(where: {jobid: {_in: $jobids}}, order_by: {created_at: asc}) { + transitions(where: {jobid: {_in: $jobids}}, order_by: {end: desc}) { start end value diff --git a/server/job/job-lifecycle.js b/server/job/job-lifecycle.js index 07aaa037a..60d0c0be5 100644 --- a/server/job/job-lifecycle.js +++ b/server/job/job-lifecycle.js @@ -1,57 +1,14 @@ const _ = require("lodash"); const queries = require("../graphql-client/queries"); const moment = require("moment"); - -const calculateStatusDuration = (transitions) => { - let statusDuration = {}; - - transitions.forEach((transition, index) => { - let duration = transition.duration_minutes; - - // If there is no prev_value, it is the first transition - if (!transition.prev_value) { - statusDuration[transition.value] = { - value: duration, - humanReadable: transition.duration_readable - }; - } - // If there is no next_value, it is the last transition (the active one) - else if (!transition.next_value) { - 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 - }; - } - } - // For all other transitions - 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 - }; - } - } - }); - - return statusDuration; -} - +const durationToHumanReadable = require("../utils/durationToHumanReadable"); +const calculateStatusDuration = require("../utils/calculateStatusDuration"); const jobLifecycle = async (req, res) => { const {jobids} = req.body; const jobIDs = _.isArray(jobids) ? jobids : [jobids]; - const client = req.userGraphQLClient; - const resp = await client.request(queries.QUERY_TRANSITIONS_BY_JOBID, {jobids: jobIDs,}); const transitions = resp.transitions; @@ -67,19 +24,21 @@ const jobLifecycle = async (req, res) => { const transitionsByJobId = _.groupBy(resp.transitions, 'jobid'); const groupedTransitions = {}; - moment.relativeTimeThreshold('m', 30) + for (let jobId in transitionsByJobId) { let lifecycle = transitionsByJobId[jobId].map(transition => { - if (transition.start) { - transition.start_readable = moment(transition.start).fromNow(); - } - if (transition.end) { - transition.end_readable = moment(transition.end).fromNow(); - } - if(transition.duration){ - transition.duration_seconds = Math.round(transition.duration / 1000); - transition.duration_minutes = Math.round(transition.duration_seconds / 60); - transition.duration_readable = moment.duration(transition.duration).humanize(); + transition.start_readable = transition.start ? moment(transition.start).fromNow() : 'N/A'; + transition.end_readable = transition.end ? moment(transition.end).fromNow() : 'N/A'; + + if (transition.duration) { + transition.duration_seconds = Math.round(transition.duration / 1000); + transition.duration_minutes = Math.round(transition.duration_seconds / 60); + let duration = moment.duration(transition.duration); + transition.duration_readable = durationToHumanReadable(duration); + } else { + transition.duration_seconds = 0; + transition.duration_minutes = 0; + transition.duration_readable = 'N/A'; } return transition; }); @@ -90,13 +49,10 @@ const jobLifecycle = async (req, res) => { }; } - console.dir(groupedTransitions, {depth: null}) - return res.status(200).json({ jobIDs, transition: groupedTransitions, }); } - module.exports = jobLifecycle; \ No newline at end of file diff --git a/server/utils/calculateStatusDuration.js b/server/utils/calculateStatusDuration.js new file mode 100644 index 000000000..8a74e09f8 --- /dev/null +++ b/server/utils/calculateStatusDuration.js @@ -0,0 +1,59 @@ +const moment = require('moment'); +const durationToHumanReadable = require("./durationToHumanReadable"); +/** + * Calculate the duration of each status of a job + * @param transitions + * @returns {{}} + */ +const calculateStatusDuration = (transitions) => { + let statusDuration = {}; + let totalDuration = 0; + let summations = []; + + transitions.forEach((transition, index) => { + let duration = transition.duration; + totalDuration += duration; + + if (!transition.prev_value) { + statusDuration[transition.value] = { + value: duration, + humanReadable: transition.duration_readable + }; + } else if (!transition.next_value) { + 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 + }; + } + } 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 + }; + } + } + }); + + for (let [status, {value, humanReadable}] of Object.entries(statusDuration)) { + if (status !== 'total') { + summations.push({status, value, humanReadable}); + } + } + + const humanReadableTotal = durationToHumanReadable(moment.duration(totalDuration)); + + return { + summations, + total: totalDuration, + humanReadableTotal + }; +} +module.exports = calculateStatusDuration; \ No newline at end of file diff --git a/server/utils/durationToHumanReadable.js b/server/utils/durationToHumanReadable.js new file mode 100644 index 000000000..f13e24c98 --- /dev/null +++ b/server/utils/durationToHumanReadable.js @@ -0,0 +1,22 @@ +const durationToHumanReadable = (duration) => { + if (!duration) return 'N/A'; + + let parts = []; + + let years = duration.years(); + let months = duration.months(); + let days = duration.days(); + let hours = duration.hours(); + let minutes = duration.minutes(); + let seconds = duration.seconds(); + + if (years) parts.push(years + ' year' + (years > 1 ? 's' : '')); + if (months) parts.push(months + ' month' + (months > 1 ? 's' : '')); + if (days) parts.push(days + ' day' + (days > 1 ? 's' : '')); + if (hours) parts.push(hours + ' hour' + (hours > 1 ? 's' : '')); + if (minutes) parts.push(minutes + ' minute' + (minutes > 1 ? 's' : '')); + if (!minutes && !hours && !days && !months && !years && seconds) parts.push(seconds + ' second' + (seconds > 1 ? 's' : '')); + + return parts.join(', '); +} +module.exports = durationToHumanReadable; \ No newline at end of file From 6489a8666f8c4a6d94fc8be835a14392ea8ab853 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 24 Jan 2024 18:39:59 -0500 Subject: [PATCH 50/67] - Progress Signed-off-by: Dave Richer --- .../job-lifecycle/job-lifecycle.component.jsx | 67 +++++++++---------- server/utils/calculateStatusDuration.js | 2 +- 2 files changed, 33 insertions(+), 36 deletions(-) diff --git a/client/src/components/job-lifecycle/job-lifecycle.component.jsx b/client/src/components/job-lifecycle/job-lifecycle.component.jsx index 3f48e826e..58475e300 100644 --- a/client/src/components/job-lifecycle/job-lifecycle.component.jsx +++ b/client/src/components/job-lifecycle/job-lifecycle.component.jsx @@ -5,24 +5,25 @@ import {Card, Space, Table} from 'antd'; import {gql, useQuery} from "@apollo/client"; import {DateTimeFormatterFunction} from "../../utils/DateFormatter"; import {isEmpty} from "lodash"; -import {Bar, BarChart, CartesianGrid, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis} from "recharts"; +import {Bar, BarChart, CartesianGrid, Legend, Tooltip, YAxis} from "recharts"; + const transformDataForChart = (durations) => { const output = {}; - output.total = durations.total; - return durations.summations.forEach((summation) => { - output[summation.status] = summation.value - }); -}; + // output.amt = durations.total; + // output.name = 'Total'; + durations.summations.forEach((summation) => { + output[summation.status] = summation.value; + }); + return [output]; +} const getColor = (key) => { // Generate a random color - const randomColor = '#' + Math.floor(Math.random()*16777215).toString(16); + const randomColor = '#' + Math.floor(Math.random() * 16777215).toString(16); return randomColor; }; - export function JobLifecycleComponent({job, ...rest}) { - const [loading, setLoading] = useState(true); const [lifecycleData, setLifecycleData] = useState(null); @@ -125,33 +126,29 @@ export function JobLifecycleComponent({job, ...rest}) { - - - - - - - - {lifecycleData.durations.summations.map((summation, idx) => { - + + + + + + { + Object.keys(transformDataForChart(lifecycleData.durations)[0]).map((key) => { return ( - - ); - })} - - - - + + ) + }) + } + diff --git a/server/utils/calculateStatusDuration.js b/server/utils/calculateStatusDuration.js index 8a74e09f8..ae0ef8238 100644 --- a/server/utils/calculateStatusDuration.js +++ b/server/utils/calculateStatusDuration.js @@ -11,7 +11,7 @@ const calculateStatusDuration = (transitions) => { let summations = []; transitions.forEach((transition, index) => { - let duration = transition.duration; + let duration = transition.duration_minutes; totalDuration += duration; if (!transition.prev_value) { From 03d4e4dcd133129dc77dfca0e31807ecff20dbdd Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 24 Jan 2024 16:01:48 -0800 Subject: [PATCH 51/67] IO-2543 AR Aging --- .../jobs-admin-remove-ar.component.jsx | 63 +++ .../report-center-modal.component.jsx | 38 +- client/src/graphql/jobs.queries.js | 450 +++++++++--------- .../src/pages/jobs-admin/jobs-admin.page.jsx | 5 +- client/src/translations/en_us/common.json | 3 + client/src/translations/es/common.json | 3 + client/src/translations/fr/common.json | 3 + client/src/utils/AuditTrailMappings.js | 66 +-- client/src/utils/TemplateConstants.js | 10 + 9 files changed, 366 insertions(+), 275 deletions(-) create mode 100644 client/src/components/jobs-admin-remove-ar/jobs-admin-remove-ar.component.jsx diff --git a/client/src/components/jobs-admin-remove-ar/jobs-admin-remove-ar.component.jsx b/client/src/components/jobs-admin-remove-ar/jobs-admin-remove-ar.component.jsx new file mode 100644 index 000000000..c85404f48 --- /dev/null +++ b/client/src/components/jobs-admin-remove-ar/jobs-admin-remove-ar.component.jsx @@ -0,0 +1,63 @@ +import { gql, useMutation } from "@apollo/client"; +import { Form, Switch, notification } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import AuditTrailMapping from "../../utils/AuditTrailMappings"; + +const mapStateToProps = createStructuredSelector({}); +const mapDispatchToProps = (dispatch) => ({ + insertAuditTrail: ({ jobid, operation }) => + dispatch(insertAuditTrail({ jobid, operation })), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminRemoveAR); + +export function JobsAdminRemoveAR({ insertAuditTrail, job }) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [switchValue, setSwitchValue] = useState(job.remove_from_ar); + + const [updateJob] = useMutation(gql` + mutation REMOVE_FROM_AR_JOB($jobId: uuid!, $remove_from_ar: Boolean!) { + update_jobs_by_pk( + pk_columns: { id: $jobId } + _set: { remove_from_ar: $remove_from_ar } + ) { + id + remove_from_ar + } + } + `); + + const handleChange = async (value) => { + setLoading(true); + const result = await updateJob({ + variables: { jobId: job.id, remove_from_ar: value }, + }); + + if (!result.errors) { + notification["success"]({ message: t("jobs.successes.save") }); + insertAuditTrail({ + jobid: job.id, + operation: AuditTrailMapping.admin_job_remove_from_ar(value), + }); + setSwitchValue(value); + } else { + notification["error"]({ + message: t("jobs.errors.saving", { + error: JSON.stringify(result.errors), + }), + }); + } + setLoading(false); + }; + + return ( + + + + ); +} diff --git a/client/src/components/report-center-modal/report-center-modal.component.jsx b/client/src/components/report-center-modal/report-center-modal.component.jsx index 913d27282..e37e02164 100644 --- a/client/src/components/report-center-modal/report-center-modal.component.jsx +++ b/client/src/components/report-center-modal/report-center-modal.component.jsx @@ -239,20 +239,30 @@ export function ReportCenterModalComponent({ reportCenterModal }) { else return null; }} - - + + {() => { + const key = form.getFieldValue("key"); + const datedisable = Templates[key] && Templates[key].datedisable; + if (datedisable !== true) { + return ( + + + + ); + } else return null; + }} {() => { diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index 4a1c85036..adfc42765 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -545,147 +545,166 @@ export const QUERY_JOB_COSTING_DETAILS = gql` export const GET_JOB_BY_PK = gql` query GET_JOB_BY_PK($id: uuid!) { jobs_by_pk(id: $id) { - updated_at + actual_completion + actual_delivery + actual_in + adjustment_bottom_line + area_of_damage + auto_add_ats + available_jobs { + id + } + alt_transport + ca_bc_pvrt + ca_customer_gst + ca_gst_registrant + category + cccontracts { + agreementnumber + courtesycar { + fleetnumber + id + make + model + plate + year + } + id + scheduledreturn + start + status + } + cieca_ttl + class + clm_no + clm_total + comment + converted + csiinvites { + completedon + id + } + date_estimated + date_exported + date_invoiced + date_last_contacted + date_lost_sale + date_next_contact + date_open + date_rentalresp + date_repairstarted + date_scheduled + date_towin + date_void + ded_amt + ded_note + ded_status + deliverchecklist + depreciation_taxes + driveable + employee_body employee_body_rel { id first_name last_name } - employee_refinish_rel { - id - first_name - last_name - } - employee_prep_rel { - id - first_name - last_name - } + employee_csr employee_csr_rel { id first_name last_name } - employee_csr employee_prep + employee_prep_rel { + id + first_name + last_name + } employee_refinish - employee_body - alt_transport - intakechecklist - invoice_final_note - comment - loss_desc - kmin - kmout - referral_source - referral_source_extra - unit_number - po_number - special_coverage_policy - scheduled_delivery - converted - lbr_adjustments - ro_number - po_number - clm_total + employee_refinish_rel { + id + first_name + last_name + } + est_co_nm + est_ct_fn + est_ct_ln + est_ea + est_ph1 + federal_tax_rate + id inproduction - vehicleid - plate_no - plate_st - v_vin - v_model_yr - v_model_desc - v_make_desc - v_color - vehicleid - driveable - towin - loss_of_use - lost_sale_reason - vehicle { - id - plate_no - plate_st - v_vin - v_model_yr - v_model_desc - v_make_desc - v_color - notes - v_paint_codes - jobs { - id - ro_number - status - clm_no - } - } - available_jobs { - id - } - ins_co_id - policy_no - loss_date - clm_no - area_of_damage - ins_co_nm ins_addr1 ins_city + ins_co_id + ins_co_nm ins_ct_ln ins_ct_fn ins_ea ins_ph1 - est_co_nm - est_ct_fn - est_ct_ln - est_ph1 - est_ea - selling_dealer - servicing_dealer - selling_dealer_contact - servicing_dealer_contact - regie_number - scheduled_completion - id - ded_amt - ded_status - depreciation_taxes - other_amount_payable - towing_payable - storage_payable - adjustment_bottom_line - federal_tax_rate - state_tax_rate - local_tax_rate - tax_tow_rt - tax_str_rt - tax_paint_mat_rt - tax_shop_mat_rt - tax_sub_rt - tax_lbr_rt - tax_levies_rt - parts_tax_rates - job_totals - ownr_fn - ownr_ln - ownr_co_nm - ownr_ea - ownr_addr1 - ownr_addr2 - ownr_city - ownr_st - ownr_zip - ownr_ctry - ownr_ph1 - ownr_ph2 - production_vars - ca_gst_registrant - ownerid - ded_note - materials - auto_add_ats - rate_ats + intakechecklist + invoice_final_note iouparent + job_totals + joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) { + act_price + ah_detail_line + alt_partm + alt_partno + billlines(limit: 1, order_by: { bill: { date: desc } }) { + actual_cost + actual_price + bill { + id + invoice_number + vendor { + id + name + } + } + joblineid + id + quantity + } + convertedtolbr + critical + db_hrs + db_price + db_ref + id + ioucreated + lbr_amt + lbr_op + line_desc + line_ind + line_no + line_ref + location + manual_line + mod_lb_hrs + mod_lbr_ty + notes + oem_partno + op_code_desc + part_qty + part_type + prt_dsmk_m + prt_dsmk_p + status + tax_part + unq_seq + } + kmin + kmout + labor_rate_desc + lbr_adjustments + local_tax_rate + loss_date + loss_desc + loss_of_use + lost_sale_reason + materials + other_amount_payable owner { id ownr_fn @@ -702,7 +721,40 @@ export const GET_JOB_BY_PK = gql` ownr_ph2 tax_number } - labor_rate_desc + owner_owing + ownerid + ownr_addr1 + ownr_addr2 + ownr_ctry + ownr_city + ownr_co_nm + ownr_ea + ownr_fn + ownr_ln + ownr_ph1 + ownr_ph2 + ownr_st + ownr_zip + parts_tax_rates + payments { + amount + created_at + date + exportedat + id + jobid + memo + payer + paymentnum + transactionid + type + } + plate_no + plate_st + po_number + policy_no + production_vars + rate_ats rate_la1 rate_la2 rate_la3 @@ -726,121 +778,64 @@ export const GET_JOB_BY_PK = gql` rate_mapa rate_mash rate_matd - actual_in - federal_tax_rate - local_tax_rate - state_tax_rate + regie_number + referral_source + referral_source_extra + remove_from_ar + ro_number scheduled_completion - scheduled_in - actual_completion scheduled_delivery - actual_delivery - date_estimated - date_open - date_scheduled - date_invoiced - date_last_contacted - date_lost_sale - date_next_contact - date_towin - date_rentalresp - date_exported - date_repairstarted - date_void + scheduled_in + selling_dealer + servicing_dealer + selling_dealer_contact + servicing_dealer_contact + special_coverage_policy + state_tax_rate status - owner_owing - tax_registration_number - class - category - deliverchecklist - voided - ca_bc_pvrt - ca_customer_gst + storage_payable suspended - joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) { + tax_lbr_rt + tax_levies_rt + tax_paint_mat_rt + tax_registration_number + tax_shop_mat_rt + tax_str_rt + tax_sub_rt + tax_tow_rt + towin + towing_payable + unit_number + updated_at + v_vin + v_model_yr + v_model_desc + v_make_desc + v_color + vehicleid + vehicle { id - alt_partm - line_no - unq_seq - line_ind - line_desc - line_ref - part_type - oem_partno - alt_partno - db_price - act_price - part_qty - mod_lbr_ty - db_hrs - mod_lb_hrs - lbr_op - lbr_amt - op_code_desc - status + jobs { + clm_no + id + ro_number + status + } notes - location - tax_part - db_ref - manual_line - prt_dsmk_p - prt_dsmk_m - ioucreated - convertedtolbr - ah_detail_line - critical - billlines(limit: 1, order_by: { bill: { date: desc } }) { - id - quantity - actual_cost - actual_price - joblineid - bill { - id - invoice_number - vendor { - id - name - } - } - } - } - payments { - id - jobid - amount - payer - paymentnum - created_at - transactionid - memo - date - type - exportedat - } - cccontracts { - id - status - start - scheduledreturn - agreementnumber - courtesycar { - id - make - model - year - plate - fleetnumber - } - } - cieca_ttl - csiinvites { - id - completedon + plate_no + plate_st + v_color + v_make_desc + v_model_desc + v_model_yr + v_paint_codes + v_vin } + voided } } `; + export const GET_JOB_RECONCILIATION_BY_PK = gql` query GET_JOB_RECONCILIATION_BY_PK($id: uuid!) { bills(where: { jobid: { _eq: $id } }) { @@ -905,6 +900,7 @@ export const GET_JOB_RECONCILIATION_BY_PK = gql` } } `; + export const QUERY_JOB_CARD_DETAILS = gql` query QUERY_JOB_CARD_DETAILS($id: uuid!) { jobs_by_pk(id: $id) { diff --git a/client/src/pages/jobs-admin/jobs-admin.page.jsx b/client/src/pages/jobs-admin/jobs-admin.page.jsx index 0d08e5114..0c7eb9a33 100644 --- a/client/src/pages/jobs-admin/jobs-admin.page.jsx +++ b/client/src/pages/jobs-admin/jobs-admin.page.jsx @@ -7,16 +7,16 @@ import { useParams } from "react-router-dom"; import AlertComponent from "../../components/alert/alert.component"; import JobCalculateTotals from "../../components/job-calculate-totals/job-calculate-totals.component"; import ScoreboardAddButton from "../../components/job-scoreboard-add-button/job-scoreboard-add-button.component"; +import JobsAdminStatus from "../../components/jobs-admin-change-status/jobs-admin-change.status.component"; import JobsAdminClass from "../../components/jobs-admin-class/jobs-admin-class.component"; import JobsAdminDatesChange from "../../components/jobs-admin-dates/jobs-admin-dates.component"; import JobsAdminDeleteIntake from "../../components/jobs-admin-delete-intake/jobs-admin-delete-intake.component"; import JobsAdminMarkReexport from "../../components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component"; import JobAdminOwnerReassociate from "../../components/jobs-admin-owner-reassociate/jobs-admin-owner-reassociate.component"; +import JobsAdminRemoveAR from "../../components/jobs-admin-remove-ar/jobs-admin-remove-ar.component"; import JobsAdminUnvoid from "../../components/jobs-admin-unvoid/jobs-admin-unvoid.component"; import JobAdminVehicleReassociate from "../../components/jobs-admin-vehicle-reassociate/jobs-admin-vehicle-reassociate.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; -import JobsAdminStatus from "../../components/jobs-admin-change-status/jobs-admin-change.status.component"; - import NotFound from "../../components/not-found/not-found.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; import { GET_JOB_BY_PK } from "../../graphql/jobs.queries"; @@ -104,6 +104,7 @@ export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader }) { + diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 919f7e297..1589e9b5f 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -99,6 +99,7 @@ }, "audit_trail": { "messages": { + "admin_job_remove_from_ar": "ADMIN: Remove from AR updated to: {{status}}", "admin_jobmarkexported": "ADMIN: Job marked as exported.", "admin_jobmarkforreexport": "ADMIN: Job marked for re-export.", "admin_jobuninvoice": "ADMIN: Job has been uninvoiced.", @@ -1833,6 +1834,7 @@ }, "reconciliationheader": "Parts & Sublet Reconciliation", "relatedros": "Related ROs", + "remove_from_ar": "Remove from AR", "returntotals": "Return Totals", "rosaletotal": "RO Parts Total", "sale_additional": "Sales - Additional", @@ -2563,6 +2565,7 @@ }, "templates": { "anticipated_revenue": "Anticipated Revenue", + "ar_aging": "AR Aging", "attendance_detail": "Attendance (All Employees)", "attendance_employee": "Employee Attendance", "attendance_summary": "Attendance Summary (All Employees)", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index a798f790c..fc977e7eb 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -99,6 +99,7 @@ }, "audit_trail": { "messages": { + "admin_job_remove_from_ar": "", "admin_jobmarkexported": "", "admin_jobmarkforreexport": "", "admin_jobuninvoice": "", @@ -1833,6 +1834,7 @@ }, "reconciliationheader": "", "relatedros": "", + "remove_from_ar": "", "returntotals": "", "rosaletotal": "", "sale_additional": "", @@ -2563,6 +2565,7 @@ }, "templates": { "anticipated_revenue": "", + "ar_aging": "", "attendance_detail": "", "attendance_employee": "", "attendance_summary": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index b65abaf04..76e3ce6a9 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -99,6 +99,7 @@ }, "audit_trail": { "messages": { + "admin_job_remove_from_ar": "", "admin_jobmarkexported": "", "admin_jobmarkforreexport": "", "admin_jobuninvoice": "", @@ -1833,6 +1834,7 @@ }, "reconciliationheader": "", "relatedros": "", + "remove_from_ar": "", "returntotals": "", "rosaletotal": "", "sale_additional": "", @@ -2563,6 +2565,7 @@ }, "templates": { "anticipated_revenue": "", + "ar_aging": "", "attendance_detail": "", "attendance_employee": "", "attendance_summary": "", diff --git a/client/src/utils/AuditTrailMappings.js b/client/src/utils/AuditTrailMappings.js index d7098fa2d..eefbb3a11 100644 --- a/client/src/utils/AuditTrailMappings.js +++ b/client/src/utils/AuditTrailMappings.js @@ -1,54 +1,56 @@ import i18n from "i18next"; const AuditTrailMapping = { - alertToggle: (status) => i18n.t("audit_trail.messages.alerttoggle", { status }), + admin_job_remove_from_ar: (status) => + i18n.t("audit_trail.messages.admin_job_remove_from_ar", { status }), + admin_jobfieldchange: (field, value) => + "ADMIN: " + + i18n.t("audit_trail.messages.jobfieldchanged", { field, value }), + admin_jobmarkexported: () => + i18n.t("audit_trail.messages.admin_jobmarkexported"), + admin_jobmarkforreexport: () => + i18n.t("audit_trail.messages.admin_jobmarkforreexport"), + admin_jobstatuschange: (status) => + "ADMIN: " + i18n.t("audit_trail.messages.jobstatuschange", { status }), + admin_jobuninvoice: () => i18n.t("audit_trail.messages.admin_jobuninvoice"), + admin_jobunvoid: () => i18n.t("audit_trail.messages.admin_jobunvoid"), + alertToggle: (status) => + i18n.t("audit_trail.messages.alerttoggle", { status }), appointmentcancel: (lost_sale_reason) => i18n.t("audit_trail.messages.appointmentcancel", { lost_sale_reason }), appointmentinsert: (start) => i18n.t("audit_trail.messages.appointmentinsert", { start }), - jobstatuschange: (status) => - i18n.t("audit_trail.messages.jobstatuschange", { status }), - admin_jobstatuschange: (status) => - "ADMIN: " + i18n.t("audit_trail.messages.jobstatuschange", { status }), - jobsupplement: () => i18n.t("audit_trail.messages.jobsupplement"), - jobimported: () => i18n.t("audit_trail.messages.jobimported"), - jobinvoiced: () => - i18n.t("audit_trail.messages.jobinvoiced"), - jobconverted: (ro_number) => - i18n.t("audit_trail.messages.jobconverted", { ro_number }), - jobfieldchange: (field, value) => - i18n.t("audit_trail.messages.jobfieldchanged", { field, value }), - admin_jobfieldchange: (field, value) => - "ADMIN: " + - i18n.t("audit_trail.messages.jobfieldchanged", { field, value }), - jobspartsorder: (order_number) => - i18n.t("audit_trail.messages.jobspartsorder", { order_number }), - jobspartsreturn: (order_number) => - i18n.t("audit_trail.messages.jobspartsreturn", { order_number }), - jobmodifylbradj: ({ mod_lbr_ty, hours }) => - i18n.t("audit_trail.messages.jobmodifylbradj", { mod_lbr_ty, hours }), billposted: (invoice_number) => i18n.t("audit_trail.messages.billposted", { invoice_number }), billupdated: (invoice_number) => i18n.t("audit_trail.messages.billupdated", { invoice_number }), + failedpayment: () => i18n.t("audit_trail.messages.failedpayment"), jobassignmentchange: (operation, name) => i18n.t("audit_trail.messages.jobassignmentchange", { operation, name }), jobassignmentremoved: (operation) => i18n.t("audit_trail.messages.jobassignmentremoved", { operation }), - jobinproductionchange: (inproduction) => - i18n.t("audit_trail.messages.jobinproductionchange", { inproduction }), jobchecklist: (type, inproduction, status) => i18n.t("audit_trail.messages.jobchecklist", { type, inproduction, status }), + jobconverted: (ro_number) => + i18n.t("audit_trail.messages.jobconverted", { ro_number }), + jobfieldchange: (field, value) => + i18n.t("audit_trail.messages.jobfieldchanged", { field, value }), + jobimported: () => i18n.t("audit_trail.messages.jobimported"), + jobinproductionchange: (inproduction) => + i18n.t("audit_trail.messages.jobinproductionchange", { inproduction }), + jobinvoiced: () => i18n.t("audit_trail.messages.jobinvoiced"), + jobmodifylbradj: ({ mod_lbr_ty, hours }) => + i18n.t("audit_trail.messages.jobmodifylbradj", { mod_lbr_ty, hours }), jobnoteadded: () => i18n.t("audit_trail.messages.jobnoteadded"), - jobnoteupdated: () => i18n.t("audit_trail.messages.jobnoteupdated"), jobnotedeleted: () => i18n.t("audit_trail.messages.jobnotedeleted"), - admin_jobunvoid: () => i18n.t("audit_trail.messages.admin_jobunvoid"), - admin_jobuninvoice: () => i18n.t("audit_trail.messages.admin_jobuninvoice"), - admin_jobmarkforreexport: () => - i18n.t("audit_trail.messages.admin_jobmarkforreexport"), - admin_jobmarkexported: () => - i18n.t("audit_trail.messages.admin_jobmarkexported"), - failedpayment: () => i18n.t("audit_trail.messages.failedpayment"), + jobnoteupdated: () => i18n.t("audit_trail.messages.jobnoteupdated"), + jobspartsorder: (order_number) => + i18n.t("audit_trail.messages.jobspartsorder", { order_number }), + jobspartsreturn: (order_number) => + i18n.t("audit_trail.messages.jobspartsreturn", { order_number }), + jobstatuschange: (status) => + i18n.t("audit_trail.messages.jobstatuschange", { status }), + jobsupplement: () => i18n.t("audit_trail.messages.jobsupplement"), }; export default AuditTrailMapping; diff --git a/client/src/utils/TemplateConstants.js b/client/src/utils/TemplateConstants.js index eeb937c3a..ad614af3b 100644 --- a/client/src/utils/TemplateConstants.js +++ b/client/src/utils/TemplateConstants.js @@ -2020,6 +2020,7 @@ export const TemplateList = (type, context) => { key: "lost_sales", //idtype: "vendor", disabled: false, + datedisable: true, rangeFilter: { object: i18n.t("reportcenter.labels.objects.jobs"), field: i18n.t("jobs.fields.date_lost_sale"), @@ -2039,6 +2040,15 @@ export const TemplateList = (type, context) => { }, group: "jobs", }, + ar_aging: { + title: i18n.t("reportcenter.templates.ar_aging"), + subject: i18n.t("reportcenter.templates.ar_aging"), + key: "ar_aging", + //idtype: "vendor", + disabled: false, + datedisable: true, + group: "customers", + }, } : {}), ...(!type || type === "courtesycarcontract" From 36dd97394f99db593026281ffff88a2d78c3a94c Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 24 Jan 2024 16:15:32 -0800 Subject: [PATCH 52/67] IO-2543 Adjust for having no dates --- .../report-center-modal/report-center-modal.component.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/report-center-modal/report-center-modal.component.jsx b/client/src/components/report-center-modal/report-center-modal.component.jsx index e37e02164..c4bef11c3 100644 --- a/client/src/components/report-center-modal/report-center-modal.component.jsx +++ b/client/src/components/report-center-modal/report-center-modal.component.jsx @@ -68,8 +68,8 @@ export function ReportCenterModalComponent({ reportCenterModal }) { const handleFinish = async (values) => { setLoading(true); - const start = values.dates[0]; - const end = values.dates[1]; + const start = values.dates ? values.dates[0] : null; + const end = values.dates ? values.dates[1] : null; const { id } = values; await GenerateDocument( From eb8519dc1d0ddb982c58a6df0d180f8fedf1cb07 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 24 Jan 2024 18:37:41 -0800 Subject: [PATCH 53/67] IO-2543 Move queries to jobs.queries.js and correct layout --- .../jobs-admin-change.status.component.jsx | 14 ++- .../jobs-admin-delete-intake.component.jsx | 56 ++++----- .../jobs-admin-mark-reexport.component.jsx | 118 +++++++----------- .../jobs-admin-remove-ar.component.jsx | 36 +++--- .../jobs-admin-unvoid.component.jsx | 76 +++-------- client/src/graphql/jobs.queries.js | 117 +++++++++++++++++ 6 files changed, 230 insertions(+), 187 deletions(-) diff --git a/client/src/components/jobs-admin-change-status/jobs-admin-change.status.component.jsx b/client/src/components/jobs-admin-change-status/jobs-admin-change.status.component.jsx index edef81343..54a020d37 100644 --- a/client/src/components/jobs-admin-change-status/jobs-admin-change.status.component.jsx +++ b/client/src/components/jobs-admin-change-status/jobs-admin-change.status.component.jsx @@ -53,12 +53,14 @@ export function JobsAdminStatus({ insertAuditTrail, bodyshop, job }) { ); return ( - - - + + + + ); } diff --git a/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx b/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx index 190778437..ec1bd976b 100644 --- a/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx +++ b/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx @@ -1,34 +1,18 @@ import { useMutation } from "@apollo/client"; -import { Button, notification } from "antd"; -import { gql } from "@apollo/client"; +import { Button, Space, notification } from "antd"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; +import { + DELETE_DELIVERY_CHECKLIST, + DELETE_INTAKE_CHECKLIST, +} from "../../graphql/jobs.queries"; + export default function JobAdminDeleteIntake({ job }) { const { t } = useTranslation(); const [loading, setLoading] = useState(false); - const [deleteIntake] = useMutation(gql` - mutation DELETE_INTAKE($jobId: uuid!) { - update_jobs_by_pk( - pk_columns: { id: $jobId } - _set: { intakechecklist: null } - ) { - id - intakechecklist - } - } - `); - const [DELETE_DELIVERY] = useMutation(gql` - mutation DELETE_DELIVERY($jobId: uuid!) { - update_jobs_by_pk( - pk_columns: { id: $jobId } - _set: { deliverchecklist: null } - ) { - id - deliverchecklist - } - } - `); + const [deleteIntake] = useMutation(DELETE_INTAKE_CHECKLIST); + const [deleteDelivery] = useMutation(DELETE_DELIVERY_CHECKLIST); const handleDelete = async (values) => { setLoading(true); @@ -50,7 +34,7 @@ export default function JobAdminDeleteIntake({ job }) { const handleDeleteDelivery = async (values) => { setLoading(true); - const result = await DELETE_DELIVERY({ + const result = await deleteDelivery({ variables: { jobId: job.id }, }); @@ -68,12 +52,22 @@ export default function JobAdminDeleteIntake({ job }) { return ( <> - - + + + + ); } diff --git a/client/src/components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component.jsx b/client/src/components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component.jsx index c47c30def..ae193fa72 100644 --- a/client/src/components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component.jsx +++ b/client/src/components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component.jsx @@ -1,5 +1,5 @@ -import { gql, useMutation } from "@apollo/client"; -import { Button, notification } from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, Space, notification } from "antd"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; @@ -7,6 +7,11 @@ import moment from "moment"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; +import { + MARK_JOB_AS_EXPORTED, + MARK_JOB_AS_UNINVOICED, + MARK_JOB_FOR_REEXPORT, +} from "../../graphql/jobs.queries"; import { insertAuditTrail } from "../../redux/application/application.actions"; import { selectBodyshop, @@ -35,58 +40,18 @@ export function JobAdminMarkReexport({ const { t } = useTranslation(); const [loading, setLoading] = useState(false); const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); - const [markJobForReexport] = useMutation(gql` - mutation MARK_JOB_FOR_REEXPORT($jobId: uuid!) { - update_jobs_by_pk( - pk_columns: { id: $jobId } - _set: { date_exported: null - status: "${bodyshop.md_ro_statuses.default_invoiced}" - } - ) { - id - date_exported - status - date_invoiced - } - } - `); - const [markJobExported] = useMutation(gql` - mutation MARK_JOB_AS_EXPORTED($jobId: uuid!, $date_exported: timestamptz!) { - update_jobs_by_pk( - pk_columns: { id: $jobId } - _set: { date_exported: $date_exported - status: "${bodyshop.md_ro_statuses.default_exported}" - } - ) { - id - date_exported - date_invoiced - status - } - } - `); - const [markJobUninvoiced] = useMutation(gql` - mutation MARK_JOB_AS_UNINVOICED($jobId: uuid!, ) { - update_jobs_by_pk( - pk_columns: { id: $jobId } - _set: { date_exported: null - date_invoiced: null - status: "${bodyshop.md_ro_statuses.default_delivered}" - } - ) { - id - date_exported - date_invoiced - status - } - } - `); + const [markJobForReexport] = useMutation(MARK_JOB_FOR_REEXPORT); + const [markJobExported] = useMutation(MARK_JOB_AS_EXPORTED); + const [markJobUninvoiced] = useMutation(MARK_JOB_AS_UNINVOICED); const handleMarkForExport = async () => { setLoading(true); const result = await markJobForReexport({ - variables: { jobId: job.id }, + variables: { + jobId: job.id, + default_invoiced: bodyshop.md_ro_statuses.default_invoiced, + }, }); if (!result.errors) { @@ -108,7 +73,11 @@ export function JobAdminMarkReexport({ const handleMarkExported = async () => { setLoading(true); const result = await markJobExported({ - variables: { jobId: job.id, date_exported: moment() }, + variables: { + jobId: job.id, + date_exported: moment(), + default_exported: bodyshop.md_ro_statuses.default_exported, + }, }); await insertExportLog({ @@ -144,7 +113,10 @@ export function JobAdminMarkReexport({ const handleUninvoice = async () => { setLoading(true); const result = await markJobUninvoiced({ - variables: { jobId: job.id }, + variables: { + jobId: job.id, + default_delivered: bodyshop.md_ro_statuses.default_delivered, + }, }); if (!result.errors) { @@ -165,27 +137,29 @@ export function JobAdminMarkReexport({ return ( <> - - - + + + + + ); } diff --git a/client/src/components/jobs-admin-remove-ar/jobs-admin-remove-ar.component.jsx b/client/src/components/jobs-admin-remove-ar/jobs-admin-remove-ar.component.jsx index c85404f48..f1bda15ce 100644 --- a/client/src/components/jobs-admin-remove-ar/jobs-admin-remove-ar.component.jsx +++ b/client/src/components/jobs-admin-remove-ar/jobs-admin-remove-ar.component.jsx @@ -1,9 +1,10 @@ -import { gql, useMutation } from "@apollo/client"; -import { Form, Switch, notification } from "antd"; +import { useMutation } from "@apollo/client"; +import { Switch, notification } from "antd"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; +import { UPDATE_REMOVE_FROM_AR } from "../../graphql/jobs.queries"; import { insertAuditTrail } from "../../redux/application/application.actions"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; @@ -20,21 +21,11 @@ export function JobsAdminRemoveAR({ insertAuditTrail, job }) { const [loading, setLoading] = useState(false); const [switchValue, setSwitchValue] = useState(job.remove_from_ar); - const [updateJob] = useMutation(gql` - mutation REMOVE_FROM_AR_JOB($jobId: uuid!, $remove_from_ar: Boolean!) { - update_jobs_by_pk( - pk_columns: { id: $jobId } - _set: { remove_from_ar: $remove_from_ar } - ) { - id - remove_from_ar - } - } - `); + const [mutationUpdateRemoveFromAR] = useMutation(UPDATE_REMOVE_FROM_AR); const handleChange = async (value) => { setLoading(true); - const result = await updateJob({ + const result = await mutationUpdateRemoveFromAR({ variables: { jobId: job.id, remove_from_ar: value }, }); @@ -56,8 +47,19 @@ export function JobsAdminRemoveAR({ insertAuditTrail, job }) { }; return ( - - - + <> +
+
+ {t("jobs.labels.remove_from_ar")}: +
+
+ +
+
+ ); } diff --git a/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx b/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx index 7963fd05f..2094178c4 100644 --- a/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx +++ b/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx @@ -1,9 +1,10 @@ -import { gql, useMutation } from "@apollo/client"; +import { useMutation } from "@apollo/client"; import { Button, notification } from "antd"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; +import { UNVOID_JOB } from "../../graphql/jobs.queries"; import { insertAuditTrail } from "../../redux/application/application.actions"; import { selectBodyshop, @@ -29,66 +30,17 @@ export function JobsAdminUnvoid({ }) { const { t } = useTranslation(); const [loading, setLoading] = useState(false); - const [updateJob] = useMutation(gql` -mutation UNVOID_JOB($jobId: uuid!) { - update_jobs_by_pk(pk_columns: {id: $jobId}, _set: {voided: false, status: "${ - bodyshop.md_ro_statuses.default_imported - }", date_void: null}) { - id - date_void - voided - status - } - insert_notes(objects: {jobid: $jobId, audit: true, created_by: "${ - currentUser.email - }", text: "${t("jobs.labels.unvoidnote")}"}) { - returning { - id - } - } -} - - `); - - // const result = await voidJob({ - // variables: { - // jobId: job.id, - // job: { - // status: bodyshop.md_ro_statuses.default_void, - // voided: true, - // }, - // note: [ - // { - // jobid: job.id, - // created_by: currentUser.email, - // audit: true, - // text: t("jobs.labels.voidnote", { - // date: moment().format("MM/DD/yyy"), - // time: moment().format("hh:mm a"), - // }), - // }, - // ], - // }, - // }); - - // if (!!!result.errors) { - // notification["success"]({ - // message: t("jobs.successes.voided"), - // }); - // //go back to jobs list. - // history.push(`/manage/`); - // } else { - // notification["error"]({ - // message: t("jobs.errors.voiding", { - // error: JSON.stringify(result.errors), - // }), - // }); - // } + const [mutationUnvoidJob] = useMutation(UNVOID_JOB); const handleUpdate = async (values) => { setLoading(true); - const result = await updateJob({ - variables: { jobId: job.id }, + const result = await mutationUnvoidJob({ + variables: { + jobId: job.id, + default_imported: bodyshop.md_ro_statuses.default_imported, + currentUserEmail: currentUser.email, + text: t("jobs.labels.unvoidnote"), + }, }); if (!result.errors) { @@ -110,8 +62,10 @@ mutation UNVOID_JOB($jobId: uuid!) { }; return ( - + <> + + ); } diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index adfc42765..57bb935f8 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -2221,3 +2221,120 @@ export const GET_JOB_LINE_ORDERS = gql` } } `; + +export const UPDATE_REMOVE_FROM_AR = gql` + mutation UPDATE_REMOVE_FROM_AR($jobId: uuid!, $remove_from_ar: Boolean!) { + update_jobs_by_pk( + pk_columns: { id: $jobId } + _set: { remove_from_ar: $remove_from_ar } + ) { + id + remove_from_ar + } + } +`; + +export const UNVOID_JOB = gql` + mutation UNVOID_JOB( + $jobId: uuid! + $default_imported: String! + $currentUserEmail: String! + $text: String! + ) { + update_jobs_by_pk( + pk_columns: { id: $jobId } + _set: { voided: false, status: $default_imported, date_void: null } + ) { + id + date_void + voided + status + } + insert_notes( + objects: { + jobid: $jobId + audit: true + created_by: $currentUserEmail + text: $text + } + ) { + returning { + id + } + } + } +`; + +export const DELETE_INTAKE_CHECKLIST = gql` + mutation DELETE_INTAKE($jobId: uuid!) { + update_jobs_by_pk( + pk_columns: { id: $jobId } + _set: { intakechecklist: null } + ) { + id + intakechecklist + } + } +`; + +export const DELETE_DELIVERY_CHECKLIST = gql` + mutation DELETE_DELIVERY($jobId: uuid!) { + update_jobs_by_pk( + pk_columns: { id: $jobId } + _set: { deliverchecklist: null } + ) { + id + deliverchecklist + } + } +`; + +export const MARK_JOB_FOR_REEXPORT = gql` + mutation MARK_JOB_FOR_REEXPORT($jobId: uuid!, $default_invoiced: String!) { + update_jobs_by_pk( + pk_columns: { id: $jobId } + _set: { date_exported: null, status: $default_invoiced } + ) { + id + date_exported + status + date_invoiced + } + } +`; + +export const MARK_JOB_AS_EXPORTED = gql` + mutation MARK_JOB_AS_EXPORTED( + $jobId: uuid! + $date_exported: timestamptz! + $default_exported: String! + ) { + update_jobs_by_pk( + pk_columns: { id: $jobId } + _set: { date_exported: $date_exported, status: $default_exported } + ) { + id + date_exported + date_invoiced + status + } + } +`; + +export const MARK_JOB_AS_UNINVOICED = gql` + mutation MARK_JOB_AS_UNINVOICED($jobId: uuid!, $default_delivered: String!) { + update_jobs_by_pk( + pk_columns: { id: $jobId } + _set: { + date_exported: null + date_invoiced: null + status: $default_delivered + } + ) { + id + date_exported + date_invoiced + status + } + } +`; From f8408908b22ea705a954cc7198b11c8db96be0b3 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 24 Jan 2024 21:40:05 -0500 Subject: [PATCH 54/67] - Major Progress Commit Signed-off-by: Dave Richer --- .../job-lifecycle/job-lifecycle.component.jsx | 131 ++++++++++++------ .../job-lifecycle/job-lifecycle.styles.scss | 0 .../jobs-detail.page.component.jsx | 17 +-- server/utils/calculateStatusDuration.js | 39 ++++-- 4 files changed, 126 insertions(+), 61 deletions(-) create mode 100644 client/src/components/job-lifecycle/job-lifecycle.styles.scss diff --git a/client/src/components/job-lifecycle/job-lifecycle.component.jsx b/client/src/components/job-lifecycle/job-lifecycle.component.jsx index 58475e300..6ddbcfc51 100644 --- a/client/src/components/job-lifecycle/job-lifecycle.component.jsx +++ b/client/src/components/job-lifecycle/job-lifecycle.component.jsx @@ -1,27 +1,12 @@ import React, {useCallback, useEffect, useState} from 'react'; import moment from "moment"; import axios from 'axios'; -import {Card, Space, Table} from 'antd'; +import {Badge, Card, Space, Table, Tag} from 'antd'; import {gql, useQuery} from "@apollo/client"; import {DateTimeFormatterFunction} from "../../utils/DateFormatter"; import {isEmpty} from "lodash"; -import {Bar, BarChart, CartesianGrid, Legend, Tooltip, YAxis} from "recharts"; - -const transformDataForChart = (durations) => { - const output = {}; - // output.amt = durations.total; - // output.name = 'Total'; - durations.summations.forEach((summation) => { - output[summation.status] = summation.value; - }); - return [output]; -} -const getColor = (key) => { - // Generate a random color - const randomColor = '#' + Math.floor(Math.random() * 16777215).toString(16); - return randomColor; -}; +require('./job-lifecycle.styles.scss'); export function JobLifecycleComponent({job, ...rest}) { const [loading, setLoading] = useState(true); @@ -124,34 +109,92 @@ export function JobLifecycleComponent({job, ...rest}) { {!loading ? ( lifecycleData && lifecycleData.lifecycle && lifecycleData.durations ? ( - - - - - - - - { - Object.keys(transformDataForChart(lifecycleData.durations)[0]).map((key) => { - return ( - - ) - }) - } - + + + Statuses + + + )} + style={{width: '100%'}} + > +
+ {lifecycleData.durations.summations.map((key, index, array) => { + const isFirst = index === 0; + const isLast = index === array.length - 1; + + return ( +
+ {Math.round(key.percentage)}% +
+ ); + })} +
+ +
+ {lifecycleData.durations.summations.map((key) => ( + +
+ {key.status} ({key.roundedPercentage}) +
+
+ ))} +
-
- + + Accumulated Time: {lifecycleData.durations.humanReadableTotal} + + + + + + Transitions + + + )}> +
diff --git a/client/src/components/job-lifecycle/job-lifecycle.styles.scss b/client/src/components/job-lifecycle/job-lifecycle.styles.scss new file mode 100644 index 000000000..e69de29bb diff --git a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx index 0f335d982..b2e8159c8 100644 --- a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx +++ b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx @@ -289,13 +289,6 @@ export function JobsDetailPage({ form={form} /> - Lifecycle} - key="lifecycle" - > - - - Lifecycle} + key="lifecycle" + > + + + + diff --git a/server/utils/calculateStatusDuration.js b/server/utils/calculateStatusDuration.js index ae0ef8238..b7d432e32 100644 --- a/server/utils/calculateStatusDuration.js +++ b/server/utils/calculateStatusDuration.js @@ -1,17 +1,23 @@ -const moment = require('moment'); const durationToHumanReadable = require("./durationToHumanReadable"); -/** - * Calculate the duration of each status of a job - * @param transitions - * @returns {{}} - */ +const moment = require("moment"); +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) => { let statusDuration = {}; let totalDuration = 0; let summations = []; transitions.forEach((transition, index) => { - let duration = transition.duration_minutes; + let duration = transition.duration; totalDuration += duration; if (!transition.prev_value) { @@ -42,16 +48,31 @@ const calculateStatusDuration = (transitions) => { } }); + // Calculate the percentage for each status +// Calculate the percentage for each status + let totalPercentage = 0; + const statusKeys = Object.keys(statusDuration); + statusKeys.forEach((status, index) => { + if (index !== statusKeys.length - 1) { + const percentage = (statusDuration[status].value / totalDuration) * 100; + totalPercentage += percentage; + statusDuration[status].percentage = percentage; + } else { + statusDuration[status].percentage = 100 - totalPercentage; + } + }); + for (let [status, {value, humanReadable}] of Object.entries(statusDuration)) { if (status !== 'total') { - summations.push({status, value, humanReadable}); + summations.push({status, value, humanReadable, percentage: statusDuration[status].percentage, color: getColor(status), roundedPercentage: `${Math.round(statusDuration[status].percentage)}%`}); } } const humanReadableTotal = durationToHumanReadable(moment.duration(totalDuration)); return { - summations, + summations: _.orderBy(summations, ['value'], ['asc']), + totalStatuses: summations.length, total: totalDuration, humanReadableTotal }; From a394d6b37e491b72e667d1077e4be52679274b60 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 25 Jan 2024 12:38:32 -0500 Subject: [PATCH 55/67] - Major Progress Commit Signed-off-by: Dave Richer --- .../job-lifecycle/job-lifecycle.component.jsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/client/src/components/job-lifecycle/job-lifecycle.component.jsx b/client/src/components/job-lifecycle/job-lifecycle.component.jsx index 6ddbcfc51..07cb778c9 100644 --- a/client/src/components/job-lifecycle/job-lifecycle.component.jsx +++ b/client/src/components/job-lifecycle/job-lifecycle.component.jsx @@ -99,10 +99,10 @@ export function JobLifecycleComponent({job, ...rest}) { }, ]; - useEffect(() => { - console.log('LifeCycle Data'); - console.dir(lifecycleData, {depth: null}) - }, [lifecycleData]); + // useEffect(() => { + // console.log('LifeCycle Data'); + // console.dir(lifecycleData, {depth: null}) + // }, [lifecycleData]); return ( @@ -144,14 +144,12 @@ export function JobLifecycleComponent({job, ...rest}) { alignItems: 'center', margin: 0, padding: 0, + borderTop: '1px solid #f0f2f5', borderBottom: '1px solid #f0f2f5', borderLeft: isFirst ? '1px solid #f0f2f5' : undefined, borderRight: isLast ? '1px solid #f0f2f5' : undefined, - borderBottomLeftRadius: isFirst ? '5px' : undefined, - borderTopLeftRadius: isFirst ? '5px' : undefined, - borderBottomRightRadius: isLast ? '5px' : undefined, - borderTopRightRadius: isLast ? '5px' : undefined, + backgroundColor: key.color, width: `${key.percentage}%` }} From 50f84d40e1dc13d0143fd68c293b5a695bda8dfb Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Thu, 25 Jan 2024 10:30:03 -0800 Subject: [PATCH 56/67] Allow negative balance for AR. --- .../1706207204357_run_sql_migration/down.sql | 35 +++++++++++++++++++ .../1706207204357_run_sql_migration/up.sql | 33 +++++++++++++++++ .../1706207267558_run_sql_migration/down.sql | 35 +++++++++++++++++++ .../1706207267558_run_sql_migration/up.sql | 33 +++++++++++++++++ 4 files changed, 136 insertions(+) create mode 100644 hasura/migrations/1706207204357_run_sql_migration/down.sql create mode 100644 hasura/migrations/1706207204357_run_sql_migration/up.sql create mode 100644 hasura/migrations/1706207267558_run_sql_migration/down.sql create mode 100644 hasura/migrations/1706207267558_run_sql_migration/up.sql diff --git a/hasura/migrations/1706207204357_run_sql_migration/down.sql b/hasura/migrations/1706207204357_run_sql_migration/down.sql new file mode 100644 index 000000000..1deabac64 --- /dev/null +++ b/hasura/migrations/1706207204357_run_sql_migration/down.sql @@ -0,0 +1,35 @@ +-- Could not auto-generate a down migration. +-- Please write an appropriate down migration for the SQL below: +-- CREATE OR REPLACE FUNCTION public.jobs_ar_summary () +-- RETURNS SETOF job_ar_schema +-- LANGUAGE plpgsql +-- STABLE +-- AS $function$ +-- BEGIN +-- +-- RETURN query +-- select +-- j.id, +-- j.ro_number, +-- j.clm_total, +-- coalesce (p.total_payments,0) as total_payments, +-- j.clm_total - coalesce (p.total_payments,0) as balance, +-- j.date_invoiced, +-- j.shopid +-- from +-- jobs j +-- left join ( +-- select +-- p.jobid, +-- coalesce (sum(p.amount),0) as total_payments +-- from +-- payments p +-- group by +-- p.jobid +-- ) p on +-- j.id = p.jobid +-- where j.remove_from_ar = false and j.date_invoiced is not null and j.clm_total - coalesce (p.total_payments,0) != 0; +-- +-- +-- END +-- $function$; diff --git a/hasura/migrations/1706207204357_run_sql_migration/up.sql b/hasura/migrations/1706207204357_run_sql_migration/up.sql new file mode 100644 index 000000000..6a42b8d7a --- /dev/null +++ b/hasura/migrations/1706207204357_run_sql_migration/up.sql @@ -0,0 +1,33 @@ +CREATE OR REPLACE FUNCTION public.jobs_ar_summary () + RETURNS SETOF job_ar_schema + LANGUAGE plpgsql + STABLE + AS $function$ +BEGIN + + RETURN query +select + j.id, + j.ro_number, + j.clm_total, + coalesce (p.total_payments,0) as total_payments, + j.clm_total - coalesce (p.total_payments,0) as balance, + j.date_invoiced, + j.shopid +from + jobs j +left join ( + select + p.jobid, + coalesce (sum(p.amount),0) as total_payments + from + payments p + group by + p.jobid + ) p on + j.id = p.jobid +where j.remove_from_ar = false and j.date_invoiced is not null and j.clm_total - coalesce (p.total_payments,0) != 0; + + +END +$function$; diff --git a/hasura/migrations/1706207267558_run_sql_migration/down.sql b/hasura/migrations/1706207267558_run_sql_migration/down.sql new file mode 100644 index 000000000..1deabac64 --- /dev/null +++ b/hasura/migrations/1706207267558_run_sql_migration/down.sql @@ -0,0 +1,35 @@ +-- Could not auto-generate a down migration. +-- Please write an appropriate down migration for the SQL below: +-- CREATE OR REPLACE FUNCTION public.jobs_ar_summary () +-- RETURNS SETOF job_ar_schema +-- LANGUAGE plpgsql +-- STABLE +-- AS $function$ +-- BEGIN +-- +-- RETURN query +-- select +-- j.id, +-- j.ro_number, +-- j.clm_total, +-- coalesce (p.total_payments,0) as total_payments, +-- j.clm_total - coalesce (p.total_payments,0) as balance, +-- j.date_invoiced, +-- j.shopid +-- from +-- jobs j +-- left join ( +-- select +-- p.jobid, +-- coalesce (sum(p.amount),0) as total_payments +-- from +-- payments p +-- group by +-- p.jobid +-- ) p on +-- j.id = p.jobid +-- where j.remove_from_ar = false and j.date_invoiced is not null and j.clm_total - coalesce (p.total_payments,0) != 0; +-- +-- +-- END +-- $function$; diff --git a/hasura/migrations/1706207267558_run_sql_migration/up.sql b/hasura/migrations/1706207267558_run_sql_migration/up.sql new file mode 100644 index 000000000..6a42b8d7a --- /dev/null +++ b/hasura/migrations/1706207267558_run_sql_migration/up.sql @@ -0,0 +1,33 @@ +CREATE OR REPLACE FUNCTION public.jobs_ar_summary () + RETURNS SETOF job_ar_schema + LANGUAGE plpgsql + STABLE + AS $function$ +BEGIN + + RETURN query +select + j.id, + j.ro_number, + j.clm_total, + coalesce (p.total_payments,0) as total_payments, + j.clm_total - coalesce (p.total_payments,0) as balance, + j.date_invoiced, + j.shopid +from + jobs j +left join ( + select + p.jobid, + coalesce (sum(p.amount),0) as total_payments + from + payments p + group by + p.jobid + ) p on + j.id = p.jobid +where j.remove_from_ar = false and j.date_invoiced is not null and j.clm_total - coalesce (p.total_payments,0) != 0; + + +END +$function$; From 0e4f5b8b2ac1c2be6a59132ef67775aa41a8c8d9 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 25 Jan 2024 13:35:20 -0500 Subject: [PATCH 57/67] - progress update. Signed-off-by: Dave Richer --- .../components/job-lifecycle/job-lifecycle.component.jsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/client/src/components/job-lifecycle/job-lifecycle.component.jsx b/client/src/components/job-lifecycle/job-lifecycle.component.jsx index 07cb778c9..1bfeef189 100644 --- a/client/src/components/job-lifecycle/job-lifecycle.component.jsx +++ b/client/src/components/job-lifecycle/job-lifecycle.component.jsx @@ -8,6 +8,13 @@ import {isEmpty} from "lodash"; require('./job-lifecycle.styles.scss'); +// Get Bodyshop record +// md_RepairStatus +// All status, array of strings, all statuses available system wide, the order is meaningful. + +// CHECK SORT OF LEGEND + +// show text on bar if text can fit export function JobLifecycleComponent({job, ...rest}) { const [loading, setLoading] = useState(true); const [lifecycleData, setLifecycleData] = useState(null); @@ -192,7 +199,6 @@ export function JobLifecycleComponent({job, ...rest}) { )}> -
From 908942ec09c5c492482af670b311b548b1d3c7b1 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Fri, 26 Jan 2024 08:11:29 -0800 Subject: [PATCH 58/67] IO-2543 Revert Lost Sales for Datedisable --- client/src/utils/TemplateConstants.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/utils/TemplateConstants.js b/client/src/utils/TemplateConstants.js index ad614af3b..bb6c23731 100644 --- a/client/src/utils/TemplateConstants.js +++ b/client/src/utils/TemplateConstants.js @@ -2020,7 +2020,6 @@ export const TemplateList = (type, context) => { key: "lost_sales", //idtype: "vendor", disabled: false, - datedisable: true, rangeFilter: { object: i18n.t("reportcenter.labels.objects.jobs"), field: i18n.t("jobs.fields.date_lost_sale"), From c7a0072f2dceef92e5386eb432602fb423cfbdf2 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 26 Jan 2024 11:19:03 -0500 Subject: [PATCH 59/67] - Revert Hasura Signed-off-by: Dave Richer --- hasura/metadata/tables.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml index b9efbb437..17ccc52a9 100644 --- a/hasura/metadata/tables.yaml +++ b/hasura/metadata/tables.yaml @@ -4198,7 +4198,7 @@ interval_sec: 10 num_retries: 0 timeout_sec: 60 - webhook: https://worktest.home.irony.online + webhook_from_env: HASURA_API_URL headers: - name: event-secret value_from_env: EVENT_SECRET From efd1c170339fd527e729ce5a78feb91e57e3e62d Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 26 Jan 2024 11:19:03 -0500 Subject: [PATCH 60/67] - Revert Hasura Signed-off-by: Dave Richer --- hasura/metadata/tables.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml index b9efbb437..17ccc52a9 100644 --- a/hasura/metadata/tables.yaml +++ b/hasura/metadata/tables.yaml @@ -4198,7 +4198,7 @@ interval_sec: 10 num_retries: 0 timeout_sec: 60 - webhook: https://worktest.home.irony.online + webhook_from_env: HASURA_API_URL headers: - name: event-secret value_from_env: EVENT_SECRET From 7503d86c693889c238b3660cdeb32be238e9c9e4 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Fri, 26 Jan 2024 08:42:57 -0800 Subject: [PATCH 61/67] IO-2543 Add wrap to space components to maintain limits of card --- .../jobs-admin-delete-intake.component.jsx | 2 +- .../jobs-admin-mark-reexport.component.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx b/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx index ec1bd976b..9d95f6e80 100644 --- a/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx +++ b/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx @@ -52,7 +52,7 @@ export default function JobAdminDeleteIntake({ job }) { return ( <> - +
+
) : ( diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index becddf9eb..7679ffeed 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -1231,7 +1231,7 @@ "not_available": "N/A", "previous_status_accumulated_time": "Previous Status Accumulated Time", "title": "Job Lifecycle Component", - "title_durations": "Historical Status Duration's", + "title_durations": "Historical Status Durations", "title_loading": "Loading", "title_transitions": "Transitions" }, diff --git a/server/utils/calculateStatusDuration.js b/server/utils/calculateStatusDuration.js index 75b30c54c..16165d001 100644 --- a/server/utils/calculateStatusDuration.js +++ b/server/utils/calculateStatusDuration.js @@ -87,7 +87,6 @@ const calculateStatusDuration = (transitions, statuses) => { const humanReadableTotal = durationToHumanReadable(moment.duration(totalDuration)); - return { summations: _.isArray(statuses) && !_.isEmpty(statuses) ? summations.sort((a, b) => { return statuses.indexOf(a.status) - statuses.indexOf(b.status); diff --git a/server/utils/durationToHumanReadable.js b/server/utils/durationToHumanReadable.js index f13e24c98..e6820c9bf 100644 --- a/server/utils/durationToHumanReadable.js +++ b/server/utils/durationToHumanReadable.js @@ -15,7 +15,7 @@ const durationToHumanReadable = (duration) => { if (days) parts.push(days + ' day' + (days > 1 ? 's' : '')); if (hours) parts.push(hours + ' hour' + (hours > 1 ? 's' : '')); if (minutes) parts.push(minutes + ' minute' + (minutes > 1 ? 's' : '')); - if (!minutes && !hours && !days && !months && !years && seconds) parts.push(seconds + ' second' + (seconds > 1 ? 's' : '')); + if (seconds) parts.push(seconds + ' second' + (seconds > 1 ? 's' : '')); return parts.join(', '); }