diff --git a/_reference/reportFiltersAndSorters.md b/_reference/reportFiltersAndSorters.md
new file mode 100644
index 000000000..bcaa08ade
--- /dev/null
+++ b/_reference/reportFiltersAndSorters.md
@@ -0,0 +1,120 @@
+# Filters and Sorters
+
+This documentation details the schema required for `.filters` files on the report server. It is used to dynamically
+modify the graphQL query and provide the user more power over their reports.
+
+## High level Schema Overview
+
+```javascript
+const schema = {
+ "filters": [
+ {
+ "name": "jobs.joblines.mod_lb_hrs", // Name and path of the field in the graphQL query
+ "translation": "jobs.joblines.mod_lb_hrs_1", // Translation key for the label used in the GUI
+ "label": "mod_lb_hrs_1", // Label used in the case the GUI does not contain a translation
+ "type": "number" // Type of field, can be number or string currently
+ },
+ // ... more filters
+ ],
+ "sorters": [
+ {
+ "name": "jobs.joblines.mod_lb_hrs", // Name and path of the field in the graphQL query
+ "translation": "jobs.joblines.mod_lb_hrs_1", // Translation key for the label used in the GUI
+ "label": "mod_lb_hrs_1", // Label used in the case the GUI does not contain a translation
+ "type": "number" // Type of field, can be number or string currently
+ },
+ // ... more sorters
+ ],
+ "dates": {
+ // This is not yet implemented and will be added in a future release
+ }
+}
+```
+
+## Filters
+
+Filters effect the where clause of the graphQL query. They are used to filter the data returned from the server.
+A note on special notation used in the `name` field.
+
+### Path without brackets, multi level
+
+`"name": "jobs.joblines.mod_lb_hrs",`
+This will produce a where clause at the `joblines` level of the graphQL query,
+
+```graphql
+query gendoc_hours_sold_detail_open($starttz: timestamptz!, $endtz: timestamptz!) {
+ jobs(
+ where: {date_invoiced: {_is_null: true}, date_open: {_gte: $starttz, _lte: $endtz}, ro_number: {_is_null: false}, voided: {_eq: false}}
+ ) {
+ joblines(
+ order_by: {line_no: asc}
+ where: {removed: {_eq: false}, mod_lb_hrs: {_lt: 3}}
+ ) {
+ line_no
+ mod_lbr_ty
+ mod_lb_hrs
+ convertedtolbr
+ convertedtolbr_data
+ }
+ ownr_co_nm
+ ownr_fn
+ ownr_ln
+ plate_no
+ ro_number
+ status
+ v_make_desc
+ v_model_desc
+ v_model_yr
+ v_vin
+ v_color
+ }
+}
+```
+
+
+### Path with brackets,top level
+`"name": "[jobs].joblines.mod_lb_hrs",`
+This will produce a where clause at the `jobs` level of the graphQL query.
+
+```graphql
+query gendoc_hours_sold_detail_open($starttz: timestamptz!, $endtz: timestamptz!) {
+ jobs(
+ where: {date_invoiced: {_is_null: true}, date_open: {_gte: $starttz, _lte: $endtz}, ro_number: {_is_null: false}, voided: {_eq: false}, joblines: {mod_lb_hrs: {_gt: 4}}}
+ ) {
+ joblines(
+ order_by: {line_no: asc}
+ where: {removed: {_eq: false}}
+ ) {
+ line_no
+ mod_lbr_ty
+ mod_lb_hrs
+ convertedtolbr
+ convertedtolbr_data
+ }
+ ownr_co_nm
+ ownr_fn
+ ownr_ln
+ plate_no
+ ro_number
+ status
+ v_make_desc
+ v_model_desc
+ v_model_yr
+ v_vin
+ v_color
+ }
+}
+```
+
+## Known Caveats
+- Will only support two level of nesting in the graphQL query `jobs.joblines.mod_lb_hrs` vs `[jobs].joblines.mod_lb_hrs` is fine, but `jobs.[joblines.].some_table.mod_lb_hrs` is not.
+- The `dates` object is not yet implemented and will be added in a future release.
+- The type object must be 'string' or 'number' and is case-sensitive.
+- The `translation` key is used to look up the label in the GUI, if it is not found, the `label` key is used.
+- Do not add the ability to filter things that are already filtered as part of the original query, this would be redundant and could cause issues.
+- Do not add the ability to filter on things like FK constraints, must like the above example.
+
+
+## Sorters
+- Sorters follow the same schema as filters, however, they do not do square bracket wrapping to indicate level hoisting, a filter added on `job.md_status` would be added at the top level, and a filter added on `jobs.joblines.mod_lb_hrs` would be added at the `joblines` level.
+- Most of the reports currently do sorting on a template level, this will need to change to actually see the results using the sorters.
diff --git a/client/src/components/job-scoreboard-add-button/job-scoreboard-add-button.component.jsx b/client/src/components/job-scoreboard-add-button/job-scoreboard-add-button.component.jsx
index 0e1343438..68b8980ea 100644
--- a/client/src/components/job-scoreboard-add-button/job-scoreboard-add-button.component.jsx
+++ b/client/src/components/job-scoreboard-add-button/job-scoreboard-add-button.component.jsx
@@ -1,16 +1,16 @@
-import { useMutation, useLazyQuery } from "@apollo/client";
import { CheckCircleOutlined } from "@ant-design/icons";
+import { useLazyQuery, useMutation } from "@apollo/client";
import {
Button,
Card,
Form,
InputNumber,
- notification,
Popover,
Space,
+ notification,
} from "antd";
import moment from "moment";
-import React, { useState, useEffect } from "react";
+import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
import {
@@ -50,6 +50,7 @@ export default function ScoreboardAddButton({
const handleFinish = async (values) => {
logImEXEvent("job_close_add_to_scoreboard");
+ values.date = moment(values.date).format("YYYY-MM-DD");
setLoading(true);
let result;
@@ -177,7 +178,7 @@ export default function ScoreboardAddButton({
return acc + job.lbr_adjustments[val];
}, 0);
form.setFieldsValue({
- date: new moment(),
+ date: moment(),
bodyhrs: Math.round(v.bodyhrs * 10) / 10,
painthrs: Math.round(v.painthrs * 10) / 10,
});
diff --git a/client/src/components/jobs-available-table/jobs-available-table.container.jsx b/client/src/components/jobs-available-table/jobs-available-table.container.jsx
index 12d10baf9..942132852 100644
--- a/client/src/components/jobs-available-table/jobs-available-table.container.jsx
+++ b/client/src/components/jobs-available-table/jobs-available-table.container.jsx
@@ -6,7 +6,7 @@ import {
useQuery,
} from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react";
-import { Col, notification, Row } from "antd";
+import { Col, Row, notification } from "antd";
import Axios from "axios";
import Dinero from "dinero.js";
import moment from "moment";
@@ -30,8 +30,8 @@ import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
-import confirmDialog from "../../utils/asyncConfirm";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
+import confirmDialog from "../../utils/asyncConfirm";
import CriticalPartsScan from "../../utils/criticalPartsScan";
import AlertComponent from "../alert/alert.component";
import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component";
@@ -73,7 +73,15 @@ export function JobsAvailableContainer({
const [selectedJob, setSelectedJob] = useState(null);
const [selectedOwner, setSelectedOwner] = useState(null);
- const [partsQueueToggle, setPartsQueueToggle] = useState(bodyshop.md_functionality_toggles.parts_queue_toggle);
+ const [partsQueueToggle, setPartsQueueToggle] = useState(
+ bodyshop.md_functionality_toggles.parts_queue_toggle
+ );
+ const [updateSchComp, setSchComp] = useState({
+ actual_in: moment(),
+ checked: false,
+ scheduled_completion: moment(),
+ automatic: false,
+ });
const [insertLoading, setInsertLoading] = useState(false);
@@ -197,11 +205,16 @@ export function JobsAvailableContainer({
notification["error"]({
message: t("jobs.errors.creating", { error: err.message }),
});
- refetch().catch(e => {console.error(`Something went wrong in jobs available table container - ${err.message || ''}`)});
+ refetch().catch((e) => {
+ console.error(
+ `Something went wrong in jobs available table container - ${
+ err.message || ""
+ }`
+ );
+ });
setInsertLoading(false);
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
}
-
};
//Supplement scenario
@@ -225,6 +238,22 @@ export function JobsAvailableContainer({
//IO-539 Check for Parts Rate on PAL for SGI use case.
await CheckTaxRates(supp, bodyshop);
+ if (updateSchComp.checked === true) {
+ if (updateSchComp.automatic === true) {
+ const job_hrs = supp.joblines.data.reduce(
+ (acc, val) => acc + val.mod_lb_hrs,
+ 0
+ );
+ const num_days = job_hrs / bodyshop.target_touchtime;
+ supp.actual_in = updateSchComp.actual_in;
+ supp.scheduled_completion = moment(
+ updateSchComp.actual_in
+ ).businessAdd(num_days, "days");
+ } else {
+ supp.scheduled_completion = updateSchComp.scheduled_completion;
+ }
+ }
+
delete supp.owner;
delete supp.vehicle;
delete supp.ins_co_nm;
@@ -261,9 +290,9 @@ export function JobsAvailableContainer({
},
});
- setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
+ setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
- if (CriticalPartsScanning.treatment === "on") {
+ if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(updateResult.data.update_jobs.returning[0].id);
}
if (updateResult.errors) {
@@ -367,7 +396,6 @@ export function JobsAvailableContainer({
if (error) return ;
-
return (