From 96995fdd1bca5037a44d457233bb4f772aab67fa Mon Sep 17 00:00:00 2001
From: Patrick Fic <>
Date: Mon, 30 May 2022 16:30:49 -0700
Subject: [PATCH 01/18] Add A/R Account to Receivables Export.
---
server/accounting/qbxml/qbxml-receivables.js | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/server/accounting/qbxml/qbxml-receivables.js b/server/accounting/qbxml/qbxml-receivables.js
index 0641c67c3..78c7389f7 100644
--- a/server/accounting/qbxml/qbxml-receivables.js
+++ b/server/accounting/qbxml/qbxml-receivables.js
@@ -249,7 +249,9 @@ const generateInvoiceQbxml = (
)}:${generateJobTier(jobs_by_pk)}`
).trim(),
},
-
+ ARAccountRef: {
+ FullName: bodyshop.md_responsibility_centers.ar.accountname,
+ },
...(jobs_by_pk.class
? { ClassRef: { FullName: jobs_by_pk.class } }
: {}),
From 908c17aa6870c4bbc747e66c10b508b5e3ebe486 Mon Sep 17 00:00:00 2001
From: Patrick Fic <>
Date: Mon, 30 May 2022 17:39:43 -0700
Subject: [PATCH 02/18] IO-1914 Schema for inventory and basic adding to
inventory.
---
bodyshop_translations.babel | 130 +++++++++++++++
.../bill-detail-edit.container.jsx | 4 +-
.../bill-enter-modal.container.jsx | 31 +++-
.../bill-form/bill-form.component.jsx | 12 +-
.../bill-form/bill-form.container.jsx | 20 +++
.../bill-form/bill-form.lines.component.jsx | 21 ++-
.../bill-form/bill-form.totals.utility.js | 1 +
.../bill-inventory-table.component.jsx | 153 ++++++++++++++++++
.../bill-inventory-table.styles.scss | 19 +++
.../billline-add-inventory.component.jsx | 103 ++++++++++++
client/src/graphql/inventory.queries.js | 49 ++++++
client/src/translations/en_us/common.json | 15 ++
client/src/translations/es/common.json | 15 ++
client/src/translations/fr/common.json | 15 ++
hasura/metadata/tables.yaml | 119 ++++++++++++++
.../down.sql | 1 +
.../up.sql | 18 +++
.../down.sql | 1 +
.../up.sql | 5 +
.../down.sql | 1 +
.../up.sql | 5 +
.../down.sql | 1 +
.../up.sql | 1 +
.../down.sql | 4 +
.../up.sql | 2 +
.../down.sql | 1 +
.../up.sql | 5 +
.../down.sql | 5 +
.../up.sql | 5 +
.../down.sql | 5 +
.../up.sql | 5 +
.../down.sql | 1 +
.../up.sql | 2 +
.../down.sql | 1 +
.../up.sql | 2 +
35 files changed, 769 insertions(+), 9 deletions(-)
create mode 100644 client/src/components/bill-inventory-table/bill-inventory-table.component.jsx
create mode 100644 client/src/components/bill-inventory-table/bill-inventory-table.styles.scss
create mode 100644 client/src/components/billline-add-inventory/billline-add-inventory.component.jsx
create mode 100644 client/src/graphql/inventory.queries.js
create mode 100644 hasura/migrations/1653933012524_create_table_public_inventory/down.sql
create mode 100644 hasura/migrations/1653933012524_create_table_public_inventory/up.sql
create mode 100644 hasura/migrations/1653933089451_set_fk_public_inventory_billid/down.sql
create mode 100644 hasura/migrations/1653933089451_set_fk_public_inventory_billid/up.sql
create mode 100644 hasura/migrations/1653933105635_set_fk_public_inventory_shopid/down.sql
create mode 100644 hasura/migrations/1653933105635_set_fk_public_inventory_shopid/up.sql
create mode 100644 hasura/migrations/1653935727610_alter_table_public_inventory_alter_column_billid/down.sql
create mode 100644 hasura/migrations/1653935727610_alter_table_public_inventory_alter_column_billid/up.sql
create mode 100644 hasura/migrations/1653953166273_alter_table_public_inventory_add_column_consumedbybillid/down.sql
create mode 100644 hasura/migrations/1653953166273_alter_table_public_inventory_add_column_consumedbybillid/up.sql
create mode 100644 hasura/migrations/1653953259127_set_fk_public_inventory_consumedbybillid/down.sql
create mode 100644 hasura/migrations/1653953259127_set_fk_public_inventory_consumedbybillid/up.sql
create mode 100644 hasura/migrations/1653953283374_set_fk_public_inventory_consumedbybillid/down.sql
create mode 100644 hasura/migrations/1653953283374_set_fk_public_inventory_consumedbybillid/up.sql
create mode 100644 hasura/migrations/1653953288756_set_fk_public_inventory_consumedbybillid/down.sql
create mode 100644 hasura/migrations/1653953288756_set_fk_public_inventory_consumedbybillid/up.sql
create mode 100644 hasura/migrations/1653953385835_create_index_inventory_consumedbybillid/down.sql
create mode 100644 hasura/migrations/1653953385835_create_index_inventory_consumedbybillid/up.sql
create mode 100644 hasura/migrations/1653953399350_create_index_inventory_shopididx/down.sql
create mode 100644 hasura/migrations/1653953399350_create_index_inventory_shopididx/up.sql
diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel
index 5f5bb62e5..ec94be6a6 100644
--- a/bodyshop_translations.babel
+++ b/bodyshop_translations.babel
@@ -17213,6 +17213,136 @@
+
+ inventory
+
+
+ actions
+
+
+ addtoinventory
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+ consumefrominventory
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+
+
+ errors
+
+
+ inserting
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+
+
+ labels
+
+
+ inventory
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+
+
+ successes
+
+
+ inserted
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+
+
+
joblines
diff --git a/client/src/components/bill-detail-edit/bill-detail-edit.container.jsx b/client/src/components/bill-detail-edit/bill-detail-edit.container.jsx
index 9bbca0a60..f2f03e90d 100644
--- a/client/src/components/bill-detail-edit/bill-detail-edit.container.jsx
+++ b/client/src/components/bill-detail-edit/bill-detail-edit.container.jsx
@@ -73,8 +73,8 @@ export function BillDetailEditcontainer({
sm: "100%",
md: "100%",
lg: "100%",
- xl: "80%",
- xxl: "80%",
+ xl: "90%",
+ xxl: "90%",
};
const drawerPercentage = selectedBreakpoint
? bpoints[selectedBreakpoint[0]]
diff --git a/client/src/components/bill-enter-modal/bill-enter-modal.container.jsx b/client/src/components/bill-enter-modal/bill-enter-modal.container.jsx
index bfadbcd37..82948fe88 100644
--- a/client/src/components/bill-enter-modal/bill-enter-modal.container.jsx
+++ b/client/src/components/bill-enter-modal/bill-enter-modal.container.jsx
@@ -12,6 +12,7 @@ import {
UPDATE_JOB,
} from "../../graphql/jobs.queries";
import { MUTATION_MARK_RETURN_RECEIVED } from "../../graphql/parts-orders.queries";
+import { UPDATE_INVENTORY_LINES } from "../../graphql/inventory.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectBillEnterModal } from "../../redux/modals/modals.selectors";
@@ -50,6 +51,7 @@ function BillEnterModalContainer({
const [insertBill] = useMutation(INSERT_NEW_BILL);
const [updateJobLines] = useMutation(UPDATE_JOB_LINE);
const [updatePartsOrderLines] = useMutation(MUTATION_MARK_RETURN_RECEIVED);
+ const [updateInventoryLines] = useMutation(UPDATE_INVENTORY_LINES);
const [loading, setLoading] = useState(false);
const client = useApolloClient();
@@ -79,8 +81,13 @@ function BillEnterModalContainer({
}
setLoading(true);
- const { upload, location, outstanding_returns, ...remainingValues } =
- values;
+ const {
+ upload,
+ location,
+ outstanding_returns,
+ inventory,
+ ...remainingValues
+ } = values;
let adjustmentsToInsert = {};
@@ -190,6 +197,26 @@ function BillEnterModalContainer({
}
const billId = r1.data.insert_bills.returning[0].id;
+ const markInventoryConsumed =
+ inventory && inventory.filter((i) => i.consumefrominventory);
+
+ if (markInventoryConsumed && markInventoryConsumed.length > 0) {
+ const r2 = await updateInventoryLines({
+ variables: {
+ InventoryIds: markInventoryConsumed.map((p) => p.id),
+ consumedbybillid: billId,
+ },
+ });
+ if (!!r2.errors) {
+ setLoading(false);
+ setEnterAgain(false);
+ notification["error"]({
+ message: t("inventory.errors.updating", {
+ message: JSON.stringify(r2.errors),
+ }),
+ });
+ }
+ }
await Promise.all(
remainingValues.billlines
diff --git a/client/src/components/bill-form/bill-form.component.jsx b/client/src/components/bill-form/bill-form.component.jsx
index f57c56819..7d7eb793a 100644
--- a/client/src/components/bill-form/bill-form.component.jsx
+++ b/client/src/components/bill-form/bill-form.component.jsx
@@ -48,6 +48,7 @@ export function BillFormComponent({
disableInvNumber,
job,
loadOutstandingReturns,
+ loadInventory,
}) {
const { t } = useTranslation();
const client = useApolloClient();
@@ -61,6 +62,7 @@ export function BillFormComponent({
setDiscount(opt.discount);
opt &&
+ !billEdit &&
loadOutstandingReturns({
variables: {
jobId: form.getFieldValue("jobid"),
@@ -86,7 +88,7 @@ export function BillFormComponent({
const jobId = form.getFieldValue("jobid");
if (jobId) {
loadLines({ variables: { id: jobId } });
- if (form.getFieldValue("is_credit_memo") && vendorId) {
+ if (form.getFieldValue("is_credit_memo") && vendorId && !billEdit) {
loadOutstandingReturns({
variables: {
jobId: jobId,
@@ -95,12 +97,19 @@ export function BillFormComponent({
});
}
}
+
+ if (vendorId === bodyshop.inhousevendorid) {
+ loadInventory();
+ }
}, [
form,
+ billEdit,
loadOutstandingReturns,
+ loadInventory,
setDiscount,
vendorAutoCompleteOptions,
loadLines,
+ bodyshop.inhousevendorid,
]);
return (
@@ -425,6 +434,7 @@ export function BillFormComponent({
form={form}
responsibilityCenters={responsibilityCenters}
disabled={disabled}
+ billEdit={billEdit}
/>
)}
diff --git a/client/src/components/bill-form/bill-form.container.jsx b/client/src/components/bill-form/bill-form.container.jsx
index 4fe056667..836a24be4 100644
--- a/client/src/components/bill-form/bill-form.container.jsx
+++ b/client/src/components/bill-form/bill-form.container.jsx
@@ -8,6 +8,9 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import BillFormComponent from "./bill-form.component";
import BillCmdReturnsTableComponent from "../bill-cm-returns-table/bill-cm-returns-table.component";
import { QUERY_UNRECEIVED_LINES } from "../../graphql/parts-orders.queries";
+import BillInventoryTable from "../bill-inventory-table/bill-inventory-table.component";
+import { QUERY_OUTSTANDING_INVENTORY } from "../../graphql/inventory.queries";
+import { useTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -20,6 +23,12 @@ export function BillFormContainer({
disabled,
disableInvNumber,
}) {
+ const { Simple_Inventory } = useTreatments(
+ ["Simple_Inventory"],
+ {},
+ bodyshop && bodyshop.imexshopid
+ );
+
const { data: VendorAutoCompleteData } = useQuery(
SEARCH_VENDOR_AUTOCOMPLETE,
{ fetchPolicy: "network-only", nextFetchPolicy: "network-only" }
@@ -31,6 +40,8 @@ export function BillFormContainer({
const [loadOutstandingReturns, { loading: returnLoading, data: returnData }] =
useLazyQuery(QUERY_UNRECEIVED_LINES);
+ const [loadInventory, { loading: inventoryLoading, data: inventoryData }] =
+ useLazyQuery(QUERY_OUTSTANDING_INVENTORY);
return (
<>
@@ -47,6 +58,7 @@ export function BillFormContainer({
responsibilityCenters={bodyshop.md_responsibility_centers || null}
disableInvNumber={disableInvNumber}
loadOutstandingReturns={loadOutstandingReturns}
+ loadInventory={loadInventory}
/>
{!billEdit && (
)}
+ {!billEdit && Simple_Inventory.treatment === "on" && (
+
+ )}
>
);
}
diff --git a/client/src/components/bill-form/bill-form.lines.component.jsx b/client/src/components/bill-form/bill-form.lines.component.jsx
index b8a1b5c9d..b3d6209c5 100644
--- a/client/src/components/bill-form/bill-form.lines.component.jsx
+++ b/client/src/components/bill-form/bill-form.lines.component.jsx
@@ -8,7 +8,7 @@ import {
Space,
Switch,
Table,
- Tooltip
+ Tooltip,
} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -18,6 +18,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import CiecaSelect from "../../utils/Ciecaselect";
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
+import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -34,6 +35,7 @@ export function BillEnterModalLinesComponent({
discount,
form,
responsibilityCenters,
+ billEdit,
}) {
const { t } = useTranslation();
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
@@ -477,9 +479,20 @@ export function BillEnterModalLinesComponent({
dataIndex: "actions",
render: (text, record) => (
-
+
+
+
+ {() => (
+
+ )}
+
+
),
},
];
diff --git a/client/src/components/bill-form/bill-form.totals.utility.js b/client/src/components/bill-form/bill-form.totals.utility.js
index df84ec86e..05396e66c 100644
--- a/client/src/components/bill-form/bill-form.totals.utility.js
+++ b/client/src/components/bill-form/bill-form.totals.utility.js
@@ -18,6 +18,7 @@ export const CalculateBillTotal = (invoice) => {
amount: Math.round((i.actual_cost || 0) * 100),
}).multiply(i.quantity || 1);
+ console.log(i, itemTotal.toFormat);
subtotal = subtotal.add(itemTotal);
if (i.applicable_taxes.federal) {
federalTax = federalTax.add(
diff --git a/client/src/components/bill-inventory-table/bill-inventory-table.component.jsx b/client/src/components/bill-inventory-table/bill-inventory-table.component.jsx
new file mode 100644
index 000000000..d8fb121d0
--- /dev/null
+++ b/client/src/components/bill-inventory-table/bill-inventory-table.component.jsx
@@ -0,0 +1,153 @@
+import { Checkbox, Form, Skeleton, Typography } from "antd";
+import React, { useEffect } from "react";
+import { useTranslation } from "react-i18next";
+import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
+import "./bill-inventory-table.styles.scss";
+
+import { connect } from "react-redux";
+import { createStructuredSelector } from "reselect";
+import { selectBodyshop } from "../../redux/user/user.selectors";
+
+const mapStateToProps = createStructuredSelector({
+ bodyshop: selectBodyshop,
+});
+const mapDispatchToProps = (dispatch) => ({
+ //setUserLanguage: language => dispatch(setUserLanguage(language))
+});
+export default connect(mapStateToProps, mapDispatchToProps)(BillInventoryTable);
+
+export function BillInventoryTable({
+ bodyshop,
+ form,
+ billEdit,
+ inventoryLoading,
+ inventoryData,
+}) {
+ const { t } = useTranslation();
+
+ useEffect(() => {
+ if (inventoryData) {
+ form.setFieldsValue({
+ inventory: inventoryData.inventory,
+ });
+ }
+ }, [inventoryData, form]);
+
+ return (
+ prev.vendorid !== cur.vendorid}
+ noStyle
+ >
+ {() => {
+ const is_inhouse =
+ form.getFieldValue("vendorid") === bodyshop.inhousevendorid;
+
+ if (!is_inhouse || billEdit) {
+ return null;
+ }
+
+ if (inventoryLoading) return ;
+
+ return (
+
+ {(fields, { add, remove, move }) => {
+ return (
+ <>
+
+ {t("inventory.labels.inventory")}
+
+
+
+
+ | {t("billlines.fields.line_desc")} |
+ {t("vendors.fields.name")} |
+ {t("billlines.fields.quantity")} |
+ {t("billlines.fields.actual_price")} |
+ {t("billlines.fields.actual_cost")} |
+ {t("inventory.actions.consumefrominventory")} |
+
+
+
+ {fields.map((field, index) => (
+
+ |
+
+
+
+ |
+
+
+
+
+
+ |
+
+
+
+
+ |
+
+
+
+
+ |
+
+
+
+
+ |
+
+
+
+
+
+ |
+
+ ))}
+
+
+ >
+ );
+ }}
+
+ );
+ }}
+
+ );
+}
diff --git a/client/src/components/bill-inventory-table/bill-inventory-table.styles.scss b/client/src/components/bill-inventory-table/bill-inventory-table.styles.scss
new file mode 100644
index 000000000..67bc0543b
--- /dev/null
+++ b/client/src/components/bill-inventory-table/bill-inventory-table.styles.scss
@@ -0,0 +1,19 @@
+.bill-inventory-table {
+ table-layout: fixed;
+ width: 100%;
+
+ th,
+ td {
+ padding: 8px;
+ text-align: left;
+ border-bottom: 1px solid #ddd;
+
+ .ant-form-item {
+ margin-bottom: 0px !important;
+ }
+ }
+
+ tr:hover {
+ background-color: #f5f5f5;
+ }
+}
\ No newline at end of file
diff --git a/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx b/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx
new file mode 100644
index 000000000..c6e9f63f4
--- /dev/null
+++ b/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx
@@ -0,0 +1,103 @@
+import { FileAddFilled } from "@ant-design/icons";
+import { useApolloClient, useMutation } from "@apollo/client";
+import { Button, notification, Tooltip } from "antd";
+import { json } from "body-parser";
+import { t } from "i18next";
+import moment from "moment";
+import React, { useState } from "react";
+import { connect } from "react-redux";
+import { createStructuredSelector } from "reselect";
+import { INSERT_INVENTORY_AND_CREDIT } from "../../graphql/inventory.queries";
+import { selectBodyshop } from "../../redux/user/user.selectors";
+import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
+
+const mapStateToProps = createStructuredSelector({
+ bodyshop: selectBodyshop,
+});
+const mapDispatchToProps = (dispatch) => ({
+ //setUserLanguage: language => dispatch(setUserLanguage(language))
+});
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(BilllineAddInventory);
+
+export function BilllineAddInventory({ bodyshop, billline, disabled, jobid }) {
+ const [loading, setLoading] = useState(false);
+
+ const [insertInventoryLine] = useMutation(INSERT_INVENTORY_AND_CREDIT);
+
+ const addToInventory = async () => {
+ setLoading(true);
+
+ //Check to make sure there are no existing items already in the inventory.
+
+ const cm = {
+ vendorid: bodyshop.inhousevendorid,
+ invoice_number: "ih",
+ jobid: jobid,
+ isinhouse: true,
+ is_credit_memo: true,
+ date: moment().format("YYYY-MM-DD"),
+ federal_tax_rate: bodyshop.bill_tax_rates.federal_tax_rate,
+ state_tax_rate: bodyshop.bill_tax_rates.state_tax_rate,
+ local_tax_rate: bodyshop.bill_tax_rates.local_tax_rate,
+ total: 0,
+ billlines: [
+ {
+ actual_price: billline.actual_price,
+ actual_cost: billline.actual_cost,
+ quantity: billline.quantity,
+ line_desc: billline.line_desc,
+ cost_center: billline.cost_center,
+ deductedfromlbr: billline.deductedfromlbr,
+ applicable_taxes: {
+ local: billline.applicable_taxes.local,
+ state: billline.applicable_taxes.state,
+ federal: billline.applicable_taxes.federal,
+ },
+ },
+ ],
+ };
+
+ cm.total = CalculateBillTotal(cm).enteredTotal.getAmount() / 100;
+
+ const insertResult = await insertInventoryLine({
+ variables: {
+ inv: {
+ shopid: bodyshop.id,
+ billlineid: billline.id,
+ actual_price: billline.actual_price,
+ actual_cost: billline.actual_cost,
+ quantity: billline.quantity,
+ line_desc: billline.line_desc,
+ },
+ cm: { ...cm, billlines: { data: cm.billlines } }, //Fix structure for apollo insert.
+ },
+ });
+
+ if (!insertResult.errors) {
+ notification.open({
+ type: "success",
+ message: t("inventory.successes.inserted"),
+ });
+ } else {
+ notification.open({
+ type: "error",
+ message: t("inventory.errors.inserting", {
+ error: JSON.stringify(insertResult.errors),
+ }),
+ });
+ }
+
+ setLoading(false);
+ };
+
+ return (
+
+
+
+ );
+}
diff --git a/client/src/graphql/inventory.queries.js b/client/src/graphql/inventory.queries.js
new file mode 100644
index 000000000..8c5bb7a4b
--- /dev/null
+++ b/client/src/graphql/inventory.queries.js
@@ -0,0 +1,49 @@
+import { gql } from "@apollo/client";
+
+export const INSERT_INVENTORY_AND_CREDIT = gql`
+ mutation INSERT_INVENTORY_AND_CREDIT(
+ $inv: inventory_insert_input!
+ $cm: bills_insert_input!
+ ) {
+ insert_inventory_one(object: $inv) {
+ id
+ }
+ insert_bills_one(object: $cm) {
+ id
+ }
+ }
+`;
+export const UPDATE_INVENTORY_LINES = gql`
+ mutation UPDATE_INVENTORY_LINES(
+ $InventoryIds: [uuid!]!
+ $consumedbybillid: uuid!
+ ) {
+ update_inventory(
+ where: { id: { _in: $InventoryIds } }
+ _set: { consumedbybillid: $consumedbybillid }
+ ) {
+ affected_rows
+ }
+ }
+`;
+
+export const QUERY_OUTSTANDING_INVENTORY = gql`
+ query QUERY_OUTSTANDING_INVENTORY {
+ inventory(where: { consumedbybillid: { _is_null: true } }) {
+ id
+ actual_cost
+ actual_price
+ quantity
+ billlineid
+ line_desc
+ billline {
+ bill {
+ invoice_number
+ vendor {
+ name
+ }
+ }
+ }
+ }
+ }
+`;
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index c684228ce..e34a885b6 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -1069,6 +1069,21 @@
"printpack": "Intake Print Pack"
}
},
+ "inventory": {
+ "actions": {
+ "addtoinventory": "Add to Inventory",
+ "consumefrominventory": "Consume from Inventory?"
+ },
+ "errors": {
+ "inserting": "Error inserting inventory item. {{error}}"
+ },
+ "labels": {
+ "inventory": "Inventory"
+ },
+ "successes": {
+ "inserted": "Added line to inventory."
+ }
+ },
"joblines": {
"actions": {
"new": "New Line"
diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json
index af04f5bb1..c7bc888ca 100644
--- a/client/src/translations/es/common.json
+++ b/client/src/translations/es/common.json
@@ -1069,6 +1069,21 @@
"printpack": ""
}
},
+ "inventory": {
+ "actions": {
+ "addtoinventory": "",
+ "consumefrominventory": ""
+ },
+ "errors": {
+ "inserting": ""
+ },
+ "labels": {
+ "inventory": ""
+ },
+ "successes": {
+ "inserted": ""
+ }
+ },
"joblines": {
"actions": {
"new": ""
diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json
index fbd28bd87..bc00188b9 100644
--- a/client/src/translations/fr/common.json
+++ b/client/src/translations/fr/common.json
@@ -1069,6 +1069,21 @@
"printpack": ""
}
},
+ "inventory": {
+ "actions": {
+ "addtoinventory": "",
+ "consumefrominventory": ""
+ },
+ "errors": {
+ "inserting": ""
+ },
+ "labels": {
+ "inventory": ""
+ },
+ "successes": {
+ "inserted": ""
+ }
+ },
"joblines": {
"actions": {
"new": ""
diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml
index 18224906e..b2d9e60d1 100644
--- a/hasura/metadata/tables.yaml
+++ b/hasura/metadata/tables.yaml
@@ -402,6 +402,14 @@
- name: jobline
using:
foreign_key_constraint_on: joblineid
+ array_relationships:
+ - name: inventories
+ using:
+ foreign_key_constraint_on:
+ column: billlineid
+ table:
+ schema: public
+ name: inventory
insert_permissions:
- role: user
permission:
@@ -541,6 +549,13 @@
table:
schema: public
name: exportlog
+ - name: inventories
+ using:
+ foreign_key_constraint_on:
+ column: consumedbybillid
+ table:
+ schema: public
+ name: inventory
- name: parts_orders
using:
foreign_key_constraint_on:
@@ -751,6 +766,13 @@
table:
schema: public
name: exportlog
+ - name: inventories
+ using:
+ foreign_key_constraint_on:
+ column: shopid
+ table:
+ schema: public
+ name: inventory
- name: jobs
using:
foreign_key_constraint_on:
@@ -2112,6 +2134,96 @@
- active:
_eq: true
allow_aggregations: true
+- table:
+ schema: public
+ name: inventory
+ object_relationships:
+ - name: bill
+ using:
+ foreign_key_constraint_on: consumedbybillid
+ - name: billline
+ using:
+ foreign_key_constraint_on: billlineid
+ - name: bodyshop
+ using:
+ foreign_key_constraint_on: shopid
+ - name: jobline
+ using:
+ foreign_key_constraint_on: joblineid
+ insert_permissions:
+ - role: user
+ permission:
+ check:
+ bodyshop:
+ associations:
+ _and:
+ - user:
+ authid:
+ _eq: X-Hasura-User-Id
+ - active:
+ _eq: true
+ columns:
+ - actual_cost
+ - actual_price
+ - billlineid
+ - consumedbybillid
+ - created_at
+ - id
+ - joblineid
+ - line_desc
+ - quantity
+ - shopid
+ - updated_at
+ backend_only: false
+ select_permissions:
+ - role: user
+ permission:
+ columns:
+ - actual_cost
+ - actual_price
+ - billlineid
+ - consumedbybillid
+ - created_at
+ - id
+ - joblineid
+ - line_desc
+ - quantity
+ - shopid
+ - updated_at
+ filter:
+ bodyshop:
+ associations:
+ _and:
+ - user:
+ authid:
+ _eq: X-Hasura-User-Id
+ - active:
+ _eq: true
+ update_permissions:
+ - role: user
+ permission:
+ columns:
+ - actual_cost
+ - actual_price
+ - billlineid
+ - consumedbybillid
+ - created_at
+ - id
+ - joblineid
+ - line_desc
+ - quantity
+ - shopid
+ - updated_at
+ filter:
+ bodyshop:
+ associations:
+ _and:
+ - user:
+ authid:
+ _eq: X-Hasura-User-Id
+ - active:
+ _eq: true
+ check: null
- table:
schema: public
name: ioevents
@@ -2211,6 +2323,13 @@
table:
schema: public
name: billlines
+ - name: inventories
+ using:
+ foreign_key_constraint_on:
+ column: joblineid
+ table:
+ schema: public
+ name: inventory
- name: parts_order_lines
using:
foreign_key_constraint_on:
diff --git a/hasura/migrations/1653933012524_create_table_public_inventory/down.sql b/hasura/migrations/1653933012524_create_table_public_inventory/down.sql
new file mode 100644
index 000000000..8fac4e21a
--- /dev/null
+++ b/hasura/migrations/1653933012524_create_table_public_inventory/down.sql
@@ -0,0 +1 @@
+DROP TABLE "public"."inventory";
diff --git a/hasura/migrations/1653933012524_create_table_public_inventory/up.sql b/hasura/migrations/1653933012524_create_table_public_inventory/up.sql
new file mode 100644
index 000000000..32e80acb1
--- /dev/null
+++ b/hasura/migrations/1653933012524_create_table_public_inventory/up.sql
@@ -0,0 +1,18 @@
+CREATE TABLE "public"."inventory" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "shopid" uuid NOT NULL, "billid" uuid, "joblineid" uuid, "line_desc" text NOT NULL, "actual_price" numeric NOT NULL, "actual_cost" numeric NOT NULL, "quantity" numeric NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("joblineid") REFERENCES "public"."joblines"("id") 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_inventory_updated_at"
+BEFORE UPDATE ON "public"."inventory"
+FOR EACH ROW
+EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"();
+COMMENT ON TRIGGER "set_public_inventory_updated_at" ON "public"."inventory"
+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/1653933089451_set_fk_public_inventory_billid/down.sql b/hasura/migrations/1653933089451_set_fk_public_inventory_billid/down.sql
new file mode 100644
index 000000000..10359e17b
--- /dev/null
+++ b/hasura/migrations/1653933089451_set_fk_public_inventory_billid/down.sql
@@ -0,0 +1 @@
+alter table "public"."inventory" drop constraint "inventory_billid_fkey";
diff --git a/hasura/migrations/1653933089451_set_fk_public_inventory_billid/up.sql b/hasura/migrations/1653933089451_set_fk_public_inventory_billid/up.sql
new file mode 100644
index 000000000..083b9da5f
--- /dev/null
+++ b/hasura/migrations/1653933089451_set_fk_public_inventory_billid/up.sql
@@ -0,0 +1,5 @@
+alter table "public"."inventory"
+ add constraint "inventory_billid_fkey"
+ foreign key ("billid")
+ references "public"."billlines"
+ ("id") on update restrict on delete restrict;
diff --git a/hasura/migrations/1653933105635_set_fk_public_inventory_shopid/down.sql b/hasura/migrations/1653933105635_set_fk_public_inventory_shopid/down.sql
new file mode 100644
index 000000000..2fb006d66
--- /dev/null
+++ b/hasura/migrations/1653933105635_set_fk_public_inventory_shopid/down.sql
@@ -0,0 +1 @@
+alter table "public"."inventory" drop constraint "inventory_shopid_fkey";
diff --git a/hasura/migrations/1653933105635_set_fk_public_inventory_shopid/up.sql b/hasura/migrations/1653933105635_set_fk_public_inventory_shopid/up.sql
new file mode 100644
index 000000000..b577f28bf
--- /dev/null
+++ b/hasura/migrations/1653933105635_set_fk_public_inventory_shopid/up.sql
@@ -0,0 +1,5 @@
+alter table "public"."inventory"
+ add constraint "inventory_shopid_fkey"
+ foreign key ("shopid")
+ references "public"."bodyshops"
+ ("id") on update restrict on delete restrict;
diff --git a/hasura/migrations/1653935727610_alter_table_public_inventory_alter_column_billid/down.sql b/hasura/migrations/1653935727610_alter_table_public_inventory_alter_column_billid/down.sql
new file mode 100644
index 000000000..9ec9e56ea
--- /dev/null
+++ b/hasura/migrations/1653935727610_alter_table_public_inventory_alter_column_billid/down.sql
@@ -0,0 +1 @@
+alter table "public"."inventory" rename column "billlineid" to "billid";
diff --git a/hasura/migrations/1653935727610_alter_table_public_inventory_alter_column_billid/up.sql b/hasura/migrations/1653935727610_alter_table_public_inventory_alter_column_billid/up.sql
new file mode 100644
index 000000000..aaf49eceb
--- /dev/null
+++ b/hasura/migrations/1653935727610_alter_table_public_inventory_alter_column_billid/up.sql
@@ -0,0 +1 @@
+alter table "public"."inventory" rename column "billid" to "billlineid";
diff --git a/hasura/migrations/1653953166273_alter_table_public_inventory_add_column_consumedbybillid/down.sql b/hasura/migrations/1653953166273_alter_table_public_inventory_add_column_consumedbybillid/down.sql
new file mode 100644
index 000000000..0e361bc38
--- /dev/null
+++ b/hasura/migrations/1653953166273_alter_table_public_inventory_add_column_consumedbybillid/down.sql
@@ -0,0 +1,4 @@
+-- Could not auto-generate a down migration.
+-- Please write an appropriate down migration for the SQL below:
+-- alter table "public"."inventory" add column "consumedbybillid" uuid
+-- null;
diff --git a/hasura/migrations/1653953166273_alter_table_public_inventory_add_column_consumedbybillid/up.sql b/hasura/migrations/1653953166273_alter_table_public_inventory_add_column_consumedbybillid/up.sql
new file mode 100644
index 000000000..8b21a892a
--- /dev/null
+++ b/hasura/migrations/1653953166273_alter_table_public_inventory_add_column_consumedbybillid/up.sql
@@ -0,0 +1,2 @@
+alter table "public"."inventory" add column "consumedbybillid" uuid
+ null;
diff --git a/hasura/migrations/1653953259127_set_fk_public_inventory_consumedbybillid/down.sql b/hasura/migrations/1653953259127_set_fk_public_inventory_consumedbybillid/down.sql
new file mode 100644
index 000000000..5d24c2671
--- /dev/null
+++ b/hasura/migrations/1653953259127_set_fk_public_inventory_consumedbybillid/down.sql
@@ -0,0 +1 @@
+alter table "public"."inventory" drop constraint "inventory_consumedbybillid_fkey";
diff --git a/hasura/migrations/1653953259127_set_fk_public_inventory_consumedbybillid/up.sql b/hasura/migrations/1653953259127_set_fk_public_inventory_consumedbybillid/up.sql
new file mode 100644
index 000000000..76c4bd767
--- /dev/null
+++ b/hasura/migrations/1653953259127_set_fk_public_inventory_consumedbybillid/up.sql
@@ -0,0 +1,5 @@
+alter table "public"."inventory"
+ add constraint "inventory_consumedbybillid_fkey"
+ foreign key ("consumedbybillid")
+ references "public"."bills"
+ ("id") on update restrict on delete restrict;
diff --git a/hasura/migrations/1653953283374_set_fk_public_inventory_consumedbybillid/down.sql b/hasura/migrations/1653953283374_set_fk_public_inventory_consumedbybillid/down.sql
new file mode 100644
index 000000000..2e6cdae18
--- /dev/null
+++ b/hasura/migrations/1653953283374_set_fk_public_inventory_consumedbybillid/down.sql
@@ -0,0 +1,5 @@
+alter table "public"."inventory" drop constraint "inventory_consumedbybillid_fkey",
+ add constraint "inventory_consumedbybillid_fkey"
+ foreign key ("shopid")
+ references "public"."bodyshops"
+ ("id") on update restrict on delete restrict;
diff --git a/hasura/migrations/1653953283374_set_fk_public_inventory_consumedbybillid/up.sql b/hasura/migrations/1653953283374_set_fk_public_inventory_consumedbybillid/up.sql
new file mode 100644
index 000000000..ee8578516
--- /dev/null
+++ b/hasura/migrations/1653953283374_set_fk_public_inventory_consumedbybillid/up.sql
@@ -0,0 +1,5 @@
+alter table "public"."inventory" drop constraint "inventory_consumedbybillid_fkey",
+ add constraint "inventory_consumedbybillid_fkey"
+ foreign key ("consumedbybillid")
+ references "public"."bills"
+ ("id") on update restrict on delete set null;
diff --git a/hasura/migrations/1653953288756_set_fk_public_inventory_consumedbybillid/down.sql b/hasura/migrations/1653953288756_set_fk_public_inventory_consumedbybillid/down.sql
new file mode 100644
index 000000000..2e6cdae18
--- /dev/null
+++ b/hasura/migrations/1653953288756_set_fk_public_inventory_consumedbybillid/down.sql
@@ -0,0 +1,5 @@
+alter table "public"."inventory" drop constraint "inventory_consumedbybillid_fkey",
+ add constraint "inventory_consumedbybillid_fkey"
+ foreign key ("shopid")
+ references "public"."bodyshops"
+ ("id") on update restrict on delete restrict;
diff --git a/hasura/migrations/1653953288756_set_fk_public_inventory_consumedbybillid/up.sql b/hasura/migrations/1653953288756_set_fk_public_inventory_consumedbybillid/up.sql
new file mode 100644
index 000000000..ac2008fc6
--- /dev/null
+++ b/hasura/migrations/1653953288756_set_fk_public_inventory_consumedbybillid/up.sql
@@ -0,0 +1,5 @@
+alter table "public"."inventory" drop constraint "inventory_consumedbybillid_fkey",
+ add constraint "inventory_consumedbybillid_fkey"
+ foreign key ("consumedbybillid")
+ references "public"."bills"
+ ("id") on update cascade on delete set null;
diff --git a/hasura/migrations/1653953385835_create_index_inventory_consumedbybillid/down.sql b/hasura/migrations/1653953385835_create_index_inventory_consumedbybillid/down.sql
new file mode 100644
index 000000000..d2d1f2b46
--- /dev/null
+++ b/hasura/migrations/1653953385835_create_index_inventory_consumedbybillid/down.sql
@@ -0,0 +1 @@
+DROP INDEX IF EXISTS "public"."inventory_consumedbybillid";
diff --git a/hasura/migrations/1653953385835_create_index_inventory_consumedbybillid/up.sql b/hasura/migrations/1653953385835_create_index_inventory_consumedbybillid/up.sql
new file mode 100644
index 000000000..f547132c9
--- /dev/null
+++ b/hasura/migrations/1653953385835_create_index_inventory_consumedbybillid/up.sql
@@ -0,0 +1,2 @@
+CREATE INDEX "inventory_consumedbybillid" on
+ "public"."inventory" using btree ("consumedbybillid");
diff --git a/hasura/migrations/1653953399350_create_index_inventory_shopididx/down.sql b/hasura/migrations/1653953399350_create_index_inventory_shopididx/down.sql
new file mode 100644
index 000000000..1b7d82158
--- /dev/null
+++ b/hasura/migrations/1653953399350_create_index_inventory_shopididx/down.sql
@@ -0,0 +1 @@
+DROP INDEX IF EXISTS "public"."inventory_shopididx";
diff --git a/hasura/migrations/1653953399350_create_index_inventory_shopididx/up.sql b/hasura/migrations/1653953399350_create_index_inventory_shopididx/up.sql
new file mode 100644
index 000000000..f987787cc
--- /dev/null
+++ b/hasura/migrations/1653953399350_create_index_inventory_shopididx/up.sql
@@ -0,0 +1,2 @@
+CREATE INDEX "inventory_shopididx" on
+ "public"."inventory" using btree ("shopid");
From 912756e0f9ed3175375d82e874135ce4328aa83d Mon Sep 17 00:00:00 2001
From: Patrick Fic <>
Date: Mon, 30 May 2022 17:48:29 -0700
Subject: [PATCH 03/18] Missed in previous commit.
---
.../billline-add-inventory.component.jsx | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx b/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx
index c6e9f63f4..f25ee2c61 100644
--- a/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx
+++ b/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx
@@ -1,7 +1,6 @@
import { FileAddFilled } from "@ant-design/icons";
-import { useApolloClient, useMutation } from "@apollo/client";
+import { useMutation } from "@apollo/client";
import { Button, notification, Tooltip } from "antd";
-import { json } from "body-parser";
import { t } from "i18next";
import moment from "moment";
import React, { useState } from "react";
From d28d4d62834fb2a1115c4289e2ffe217e268ae8f Mon Sep 17 00:00:00 2001
From: Patrick Fic <>
Date: Tue, 31 May 2022 12:38:07 -0700
Subject: [PATCH 04/18] IO-1914 Added inventory page.
---
bodyshop_translations.babel | 168 ++++++++++++++++++
.../bill-form/bill-form.totals.utility.js | 1 -
.../components/header/header.component.jsx | 9 +-
.../inventory-list.component.jsx | 156 ++++++++++++++++
.../inventory-list.container.jsx | 67 +++++++
.../components/rbac-wrapper/rbac-defaults.js | 4 +-
.../shop-info/shop-info.rbac.component.jsx | 12 ++
client/src/graphql/inventory.queries.js | 50 ++++++
client/src/pages/inventory/inventory.page.jsx | 32 ++++
.../pages/manage/manage.page.component.jsx | 6 +
client/src/translations/en_us/common.json | 10 +-
client/src/translations/es/common.json | 10 +-
client/src/translations/fr/common.json | 10 +-
hasura/metadata/functions.yaml | 3 +
hasura/metadata/tables.yaml | 1 +
.../1654022042838_run_sql_migration/down.sql | 40 +++++
.../1654022042838_run_sql_migration/up.sql | 38 ++++
.../1654023497348_run_sql_migration/down.sql | 37 ++++
.../1654023497348_run_sql_migration/up.sql | 35 ++++
.../1654023506265_run_sql_migration/down.sql | 37 ++++
.../1654023506265_run_sql_migration/up.sql | 35 ++++
.../1654023641223_run_sql_migration/down.sql | 37 ++++
.../1654023641223_run_sql_migration/up.sql | 35 ++++
.../1654023762220_run_sql_migration/down.sql | 3 +
.../1654023762220_run_sql_migration/up.sql | 1 +
25 files changed, 830 insertions(+), 7 deletions(-)
create mode 100644 client/src/components/inventory-list/inventory-list.component.jsx
create mode 100644 client/src/components/inventory-list/inventory-list.container.jsx
create mode 100644 client/src/pages/inventory/inventory.page.jsx
create mode 100644 hasura/migrations/1654022042838_run_sql_migration/down.sql
create mode 100644 hasura/migrations/1654022042838_run_sql_migration/up.sql
create mode 100644 hasura/migrations/1654023497348_run_sql_migration/down.sql
create mode 100644 hasura/migrations/1654023497348_run_sql_migration/up.sql
create mode 100644 hasura/migrations/1654023506265_run_sql_migration/down.sql
create mode 100644 hasura/migrations/1654023506265_run_sql_migration/up.sql
create mode 100644 hasura/migrations/1654023641223_run_sql_migration/down.sql
create mode 100644 hasura/migrations/1654023641223_run_sql_migration/up.sql
create mode 100644 hasura/migrations/1654023762220_run_sql_migration/down.sql
create mode 100644 hasura/migrations/1654023762220_run_sql_migration/up.sql
diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel
index ec94be6a6..eac8b6536 100644
--- a/bodyshop_translations.babel
+++ b/bodyshop_translations.babel
@@ -17292,6 +17292,69 @@
labels
+
+ consumedbyjob
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+ frombillinvoicenumber
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+ fromvendor
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
inventory
false
@@ -17313,6 +17376,48 @@
+
+ showall
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+ showavailable
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
@@ -30064,6 +30169,27 @@
+
+ inventory
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
jobs
false
@@ -42492,6 +42618,27 @@
+
+ inventory
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
jobs
false
@@ -43313,6 +43460,27 @@
+
+ inventory
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
jobs
false
diff --git a/client/src/components/bill-form/bill-form.totals.utility.js b/client/src/components/bill-form/bill-form.totals.utility.js
index 05396e66c..df84ec86e 100644
--- a/client/src/components/bill-form/bill-form.totals.utility.js
+++ b/client/src/components/bill-form/bill-form.totals.utility.js
@@ -18,7 +18,6 @@ export const CalculateBillTotal = (invoice) => {
amount: Math.round((i.actual_cost || 0) * 100),
}).multiply(i.quantity || 1);
- console.log(i, itemTotal.toFormat);
subtotal = subtotal.add(itemTotal);
if (i.applicable_taxes.federal) {
federalTax = federalTax.add(
diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx
index 2a4e9698e..63af81751 100644
--- a/client/src/components/header/header.component.jsx
+++ b/client/src/components/header/header.component.jsx
@@ -200,6 +200,13 @@ function Header({
{t("menus.header.enterbills")}
+ }
+ >
+ {t("menus.header.inventory")}
+
+
}>
{t("menus.header.allpayments")}
@@ -216,7 +223,6 @@ function Header({
{t("menus.header.enterpayment")}
-
}>
{t("menus.header.timetickets")}
@@ -235,7 +241,6 @@ function Header({
{t("menus.header.entertimeticket")}
-
({
+ //setUserLanguage: language => dispatch(setUserLanguage(language))
+});
+
+export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
+ const search = queryString.parse(useLocation().search);
+ const { page, sortcolumn, sortorder, invfilters } = search;
+ const history = useHistory();
+
+ const { t } = useTranslation();
+ const columns = [
+ {
+ title: t("billlines.fields.line_desc"),
+ dataIndex: "line_desc",
+ key: "line_desc",
+
+ sorter: true, //(a, b) => alphaSort(a.line_desc, b.line_desc),
+ sortOrder: sortcolumn === "line_desc" && sortorder,
+ },
+ {
+ title: t("inventory.labels.frombillinvoicenumber"),
+ dataIndex: "vendorname",
+ key: "vendorname",
+ ellipsis: true,
+ //sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
+
+ //sortOrder: sortcolumn === "ownr_ln" && sortorder,
+ render: (text, record) => record.billline?.bill?.invoice_number,
+ },
+ {
+ title: t("inventory.labels.fromvendor"),
+ dataIndex: "vendorname",
+ key: "vendorname",
+ ellipsis: true,
+ //sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
+
+ //sortOrder: sortcolumn === "ownr_ln" && sortorder,
+ render: (text, record) => record.billline?.bill?.vendor?.name,
+ },
+ {
+ title: t("billlines.fields.actual_price"),
+ dataIndex: "actual_price",
+ key: "actual_price",
+
+ render: (text, record) => (
+ {record.actual_price}
+ ),
+ },
+ {
+ title: t("billlines.fields.actual_cost"),
+ dataIndex: "actual_cost",
+ key: "actual_cost",
+
+ render: (text, record) => (
+ {record.actual_cost}
+ ),
+ },
+ {
+ title: t("inventory.labels.consumedbyjob"),
+ dataIndex: "consumedbyjob",
+ key: "consumedbyjob",
+
+ ellipsis: true,
+ render: (text, record) => record.bill?.job?.ro_number,
+ },
+ ];
+
+ const handleTableChange = (pagination, filters, sorter) => {
+ search.page = pagination.current;
+ search.sortcolumn = sorter.column && sorter.column.key;
+ search.sortorder = sorter.order;
+ history.push({ search: queryString.stringify(search) });
+ };
+
+ return (
+
+ {search.search && (
+ <>
+
+ {t("general.labels.searchresults", { search: search.search })}
+
+
+ >
+ )}
+
+
+
+
+ {
+ search.search = value;
+ history.push({ search: queryString.stringify(search) });
+ }}
+ enterButton
+ />
+
+ }
+ >
+
+
+ );
+}
+export default connect(mapStateToProps, mapDispatchToProps)(JobsList);
diff --git a/client/src/components/inventory-list/inventory-list.container.jsx b/client/src/components/inventory-list/inventory-list.container.jsx
new file mode 100644
index 000000000..568b3162a
--- /dev/null
+++ b/client/src/components/inventory-list/inventory-list.container.jsx
@@ -0,0 +1,67 @@
+import { useQuery } from "@apollo/client";
+import queryString from "query-string";
+import React from "react";
+import { connect } from "react-redux";
+import { useLocation } from "react-router-dom";
+import { createStructuredSelector } from "reselect";
+import { QUERY_INVENTORY_PAGINATED } from "../../graphql/inventory.queries";
+import {
+ setBreadcrumbs,
+ setSelectedHeader,
+} from "../../redux/application/application.actions";
+import AlertComponent from "../alert/alert.component";
+import InventoryListPaginated from "./inventory-list.component";
+import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
+
+const mapStateToProps = createStructuredSelector({
+ //bodyshop: selectBodyshop,
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
+ setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
+});
+
+export function InventoryList({ setBreadcrumbs, setSelectedHeader }) {
+ const searchParams = queryString.parse(useLocation().search);
+ const { page, sortcolumn, sortorder, search, showall } = searchParams;
+
+ const { loading, error, data, refetch } = useQuery(
+ QUERY_INVENTORY_PAGINATED,
+ {
+ fetchPolicy: "network-only",
+ nextFetchPolicy: "network-only",
+ variables: {
+ search: search || "",
+ offset: page ? (page - 1) * 25 : 0,
+ limit: 25,
+ consumedIsNull: showall === "true" ? null : true,
+ order: [
+ {
+ [sortcolumn || "created_at"]:
+ sortorder && sortorder !== "false"
+ ? sortorder === "descend"
+ ? "desc"
+ : "asc"
+ : "desc",
+ },
+ ],
+ },
+ }
+ );
+
+ if (error) return ;
+ return (
+
+
+
+ );
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(InventoryList);
diff --git a/client/src/components/rbac-wrapper/rbac-defaults.js b/client/src/components/rbac-wrapper/rbac-defaults.js
index 089e8954e..2f83443ac 100644
--- a/client/src/components/rbac-wrapper/rbac-defaults.js
+++ b/client/src/components/rbac-wrapper/rbac-defaults.js
@@ -25,7 +25,7 @@ const ret = {
"jobs:detail": 1,
"jobs:partsqueue": 4,
"jobs:checklist-view": 2,
-
+ "jobs:list-ready": 1,
"bills:enter": 2,
"bills:view": 2,
"bills:list": 2,
@@ -66,5 +66,7 @@ const ret = {
"timetickets:shiftedit": 5,
"users:editaccess": 4,
+
+ "inventory:list": 1,
};
export default ret;
diff --git a/client/src/components/shop-info/shop-info.rbac.component.jsx b/client/src/components/shop-info/shop-info.rbac.component.jsx
index 3c0f75c88..4c425ff69 100644
--- a/client/src/components/shop-info/shop-info.rbac.component.jsx
+++ b/client/src/components/shop-info/shop-info.rbac.component.jsx
@@ -633,6 +633,18 @@ export default function ShopInfoRbacComponent({ form }) {
>
+
+
+
);
diff --git a/client/src/graphql/inventory.queries.js b/client/src/graphql/inventory.queries.js
index 8c5bb7a4b..a2c36f6c1 100644
--- a/client/src/graphql/inventory.queries.js
+++ b/client/src/graphql/inventory.queries.js
@@ -47,3 +47,53 @@ export const QUERY_OUTSTANDING_INVENTORY = gql`
}
}
`;
+
+export const QUERY_INVENTORY_PAGINATED = gql`
+ query QUERY_INVENTORY_PAGINATED(
+ $search: String
+ $offset: Int
+ $limit: Int
+ $order: [inventory_order_by!]
+ $consumedIsNull: Boolean
+ ) {
+ search_inventory(
+ args: { search: $search }
+ offset: $offset
+ limit: $limit
+ order_by: $order
+ where: { consumedbybillid: { _is_null: $consumedIsNull } }
+ ) {
+ id
+ line_desc
+ actual_price
+ actual_cost
+ bill {
+ id
+ invoice_number
+ job {
+ ro_number
+ id
+ }
+ }
+ billline {
+ id
+ bill {
+ id
+ invoice_number
+ vendor {
+ id
+ name
+ }
+ }
+ }
+ }
+ search_inventory_aggregate(
+ args: { search: $search }
+ where: { consumedbybillid: { _is_null: $consumedIsNull } }
+ ) {
+ aggregate {
+ count(distinct: true)
+ }
+ }
+ }
+`;
diff --git a/client/src/pages/inventory/inventory.page.jsx b/client/src/pages/inventory/inventory.page.jsx
new file mode 100644
index 000000000..26cc2f2dc
--- /dev/null
+++ b/client/src/pages/inventory/inventory.page.jsx
@@ -0,0 +1,32 @@
+import React, { useEffect } from "react";
+import { useTranslation } from "react-i18next";
+import { connect } from "react-redux";
+import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
+import {
+ setBreadcrumbs,
+ setSelectedHeader,
+} from "../../redux/application/application.actions";
+import InventoryList from "../../components/inventory-list/inventory-list.container";
+
+const mapDispatchToProps = (dispatch) => ({
+ setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
+ setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
+});
+
+export function InventoryPage({ setBreadcrumbs, setSelectedHeader }) {
+ const { t } = useTranslation();
+
+ useEffect(() => {
+ document.title = t("titles.inventory");
+ setSelectedHeader("inventory");
+ setBreadcrumbs([{ link: "/manage/jobs", label: t("titles.bc.inventory") }]);
+ }, [t, setBreadcrumbs, setSelectedHeader]);
+
+ return (
+
+
+
+ );
+}
+
+export default connect(null, mapDispatchToProps)(InventoryPage);
diff --git a/client/src/pages/manage/manage.page.component.jsx b/client/src/pages/manage/manage.page.component.jsx
index 0fc635598..82017ffae 100644
--- a/client/src/pages/manage/manage.page.component.jsx
+++ b/client/src/pages/manage/manage.page.component.jsx
@@ -34,6 +34,7 @@ const JobsPage = lazy(() => import("../jobs/jobs.page"));
const JobsDetailPage = lazy(() =>
import("../jobs-detail/jobs-detail.page.container")
);
+const InventoryListPage = lazy(() => import("../inventory/inventory.page"));
const ProfilePage = lazy(() => import("../profile/profile.container.page"));
const JobsAvailablePage = lazy(() =>
import("../jobs-available/jobs-available.page.container")
@@ -250,6 +251,11 @@ export function Manage({ match, conflict, bodyshop }) {
+
Date: Wed, 1 Jun 2022 09:52:47 -0700
Subject: [PATCH 05/18] IO-1914 WIP Inventory.
---
.../bill-form/bill-form.component.jsx | 2 +-
.../bill-form/bill-form.container.jsx | 4 +--
.../billline-add-inventory.component.jsx | 6 ++++-
.../components/header/header.component.jsx | 27 ++++++++++++++-----
.../inventory-list.component.jsx | 7 ++---
client/src/graphql/bills.queries.js | 7 +++++
6 files changed, 37 insertions(+), 16 deletions(-)
diff --git a/client/src/components/bill-form/bill-form.component.jsx b/client/src/components/bill-form/bill-form.component.jsx
index 7d7eb793a..a9eb07443 100644
--- a/client/src/components/bill-form/bill-form.component.jsx
+++ b/client/src/components/bill-form/bill-form.component.jsx
@@ -98,7 +98,7 @@ export function BillFormComponent({
}
}
- if (vendorId === bodyshop.inhousevendorid) {
+ if (vendorId === bodyshop.inhousevendorid && !billEdit) {
loadInventory();
}
}, [
diff --git a/client/src/components/bill-form/bill-form.container.jsx b/client/src/components/bill-form/bill-form.container.jsx
index 836a24be4..358bb7f38 100644
--- a/client/src/components/bill-form/bill-form.container.jsx
+++ b/client/src/components/bill-form/bill-form.container.jsx
@@ -68,11 +68,11 @@ export function BillFormContainer({
returnData={returnData}
/>
)}
- {!billEdit && Simple_Inventory.treatment === "on" && (
+ {Simple_Inventory.treatment === "on" && (
)}
diff --git a/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx b/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx
index f25ee2c61..5b099eadf 100644
--- a/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx
+++ b/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx
@@ -94,7 +94,11 @@ export function BilllineAddInventory({ bodyshop, billline, disabled, jobid }) {
return (
-
diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx
index 63af81751..85aa0c200 100644
--- a/client/src/components/header/header.component.jsx
+++ b/client/src/components/header/header.component.jsx
@@ -1,3 +1,4 @@
+import { useTreatments } from "@splitsoftware/splitio-react";
import Icon, {
BankFilled,
BarChartOutlined,
@@ -83,6 +84,12 @@ function Header({
setReportCenterContext,
recentItems,
}) {
+ const { Simple_Inventory } = useTreatments(
+ ["Simple_Inventory"],
+ {},
+ bodyshop && bodyshop.imexshopid
+ );
+
const { t } = useTranslation();
return (
@@ -199,13 +206,19 @@ function Header({
>
{t("menus.header.enterbills")}
-
- }
- >
- {t("menus.header.inventory")}
-
+ {Simple_Inventory.treatment === "on" && (
+ <>
+
+ }
+ >
+
+ {t("menus.header.inventory")}
+
+
+ >
+ )}
}>
{t("menus.header.allpayments")}
diff --git a/client/src/components/inventory-list/inventory-list.component.jsx b/client/src/components/inventory-list/inventory-list.component.jsx
index 7ddb6595f..58210eb94 100644
--- a/client/src/components/inventory-list/inventory-list.component.jsx
+++ b/client/src/components/inventory-list/inventory-list.component.jsx
@@ -1,16 +1,13 @@
import { SyncOutlined } from "@ant-design/icons";
import { Button, Card, Input, Space, Table, Typography } from "antd";
-import _ from "lodash";
import queryString from "query-string";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
-import { Link, useHistory, useLocation } from "react-router-dom";
+import { useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
-import StartChatButton from "../chat-open-button/chat-open-button.component";
-import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
@@ -21,7 +18,7 @@ const mapDispatchToProps = (dispatch) => ({
export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
const search = queryString.parse(useLocation().search);
- const { page, sortcolumn, sortorder, invfilters } = search;
+ const { page, sortcolumn, sortorder } = search;
const history = useHistory();
const { t } = useTranslation();
diff --git a/client/src/graphql/bills.queries.js b/client/src/graphql/bills.queries.js
index dd6e95715..f042fd7ae 100644
--- a/client/src/graphql/bills.queries.js
+++ b/client/src/graphql/bills.queries.js
@@ -152,6 +152,10 @@ export const QUERY_BILL_BY_PK = gql`
state_tax_rate
federal_tax_rate
isinhouse
+ inventories {
+ id
+ line_desc
+ }
vendor {
id
name
@@ -165,6 +169,9 @@ export const QUERY_BILL_BY_PK = gql`
cost_center
quantity
joblineid
+ inventories {
+ id
+ }
jobline {
oem_partno
part_type
From 62b1da0b641f2ab329c6405d8b2cec92276472d0 Mon Sep 17 00:00:00 2001
From: Patrick Fic <>
Date: Wed, 1 Jun 2022 13:45:42 -0700
Subject: [PATCH 06/18] IO-1914 Add bill refetch after insert.
---
.../billline-add-inventory/billline-add-inventory.component.jsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx b/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx
index 5b099eadf..d84bf4b0d 100644
--- a/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx
+++ b/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx
@@ -73,6 +73,7 @@ export function BilllineAddInventory({ bodyshop, billline, disabled, jobid }) {
},
cm: { ...cm, billlines: { data: cm.billlines } }, //Fix structure for apollo insert.
},
+ refetchQueries: ["QUERY_BILL_BY_PK"],
});
if (!insertResult.errors) {
From 485b5d086663f118fc3e9d33a877cc2f7626e736 Mon Sep 17 00:00:00 2001
From: Patrick Fic <>
Date: Wed, 1 Jun 2022 16:32:24 -0700
Subject: [PATCH 07/18] IO-1914 Update inventory insert queries.
---
.../bill-form/bill-form.lines.component.jsx | 1 +
.../billline-add-inventory.component.jsx | 52 +++++-
.../scoreboard-targets-table.util.js | 17 ++
.../scoreboard-timetickets.bar.component.jsx | 157 ++++++++++++++++++
.../scoreboard-timetickets.component.jsx | 45 +++++
client/src/graphql/inventory.queries.js | 13 ++
.../scoreboard/scoreboard.page.container.jsx | 31 +++-
7 files changed, 309 insertions(+), 7 deletions(-)
create mode 100644 client/src/components/scoreboard-timetickets/scoreboard-timetickets.bar.component.jsx
create mode 100644 client/src/components/scoreboard-timetickets/scoreboard-timetickets.component.jsx
diff --git a/client/src/components/bill-form/bill-form.lines.component.jsx b/client/src/components/bill-form/bill-form.lines.component.jsx
index b3d6209c5..5891741d7 100644
--- a/client/src/components/bill-form/bill-form.lines.component.jsx
+++ b/client/src/components/bill-form/bill-form.lines.component.jsx
@@ -36,6 +36,7 @@ export function BillEnterModalLinesComponent({
form,
responsibilityCenters,
billEdit,
+ billid,
}) {
const { t } = useTranslation();
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
diff --git a/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx b/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx
index d84bf4b0d..2a6d76355 100644
--- a/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx
+++ b/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx
@@ -7,11 +7,17 @@ import React, { useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { INSERT_INVENTORY_AND_CREDIT } from "../../graphql/inventory.queries";
-import { selectBodyshop } from "../../redux/user/user.selectors";
+import {
+ selectBodyshop,
+ selectCurrentUser,
+} from "../../redux/user/user.selectors";
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
+import queryString from "query-string";
+import { useLocation } from "react-router-dom";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
+ currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
@@ -21,8 +27,15 @@ export default connect(
mapDispatchToProps
)(BilllineAddInventory);
-export function BilllineAddInventory({ bodyshop, billline, disabled, jobid }) {
+export function BilllineAddInventory({
+ currentUser,
+ bodyshop,
+ billline,
+ disabled,
+ jobid,
+}) {
const [loading, setLoading] = useState(false);
+ const { billid } = queryString.parse(useLocation().search);
const [insertInventoryLine] = useMutation(INSERT_INVENTORY_AND_CREDIT);
@@ -51,9 +64,9 @@ export function BilllineAddInventory({ bodyshop, billline, disabled, jobid }) {
cost_center: billline.cost_center,
deductedfromlbr: billline.deductedfromlbr,
applicable_taxes: {
- local: billline.applicable_taxes.local,
- state: billline.applicable_taxes.state,
- federal: billline.applicable_taxes.federal,
+ local: false, //billline.applicable_taxes.local,
+ state: false, //billline.applicable_taxes.state,
+ federal: false, // billline.applicable_taxes.federal,
},
},
],
@@ -63,6 +76,8 @@ export function BilllineAddInventory({ bodyshop, billline, disabled, jobid }) {
const insertResult = await insertInventoryLine({
variables: {
+ joblineId: billline.joblineid,
+ joblineStatus: bodyshop.md_order_statuses.default_returned,
inv: {
shopid: bodyshop.id,
billlineid: billline.id,
@@ -72,6 +87,31 @@ export function BilllineAddInventory({ bodyshop, billline, disabled, jobid }) {
line_desc: billline.line_desc,
},
cm: { ...cm, billlines: { data: cm.billlines } }, //Fix structure for apollo insert.
+ pol: {
+ returnfrombill: billid,
+ vendorid: bodyshop.inhousevendorid,
+ deliver_by: moment().format("YYYY-MM-DD"),
+ parts_order_lines: {
+ data: [
+ {
+ line_desc: billline.line_desc,
+
+ act_price: billline.actual_price,
+ cost: billline.actual_cost,
+ quantity: billline.quantity,
+ job_line_id: billline.joblineid,
+ part_type: billline.jobline.part_type,
+ cm_received: true,
+ },
+ ],
+ },
+ order_date: "2022-06-01",
+ orderedby: currentUser.email,
+ jobid: jobid,
+ user_email: currentUser.email,
+ return: true,
+ status: "Ordered",
+ },
},
refetchQueries: ["QUERY_BILL_BY_PK"],
});
@@ -97,7 +137,7 @@ export function BilllineAddInventory({ bodyshop, billline, disabled, jobid }) {
0}
+ disabled={disabled || billline?.inventories?.length > 0}
onClick={addToInventory}
>
diff --git a/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js b/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js
index 97f1813b9..2b9c0b3f7 100644
--- a/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js
+++ b/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js
@@ -47,3 +47,20 @@ export const ListOfDaysInCurrentMonth = () => {
days.push(dateEnd.format("YYYY-MM-DD"));
return days;
};
+
+export const ListDaysBetween = ({ start, end }) => {
+ console.log(
+ "🚀 ~ file: scoreboard-targets-table.util.js ~ line 52 ~ start, end",
+ start,
+ end
+ );
+ const days = [];
+ const dateStart = moment(start);
+ const dateEnd = moment(end);
+ while (dateEnd.diff(dateStart, "days") > 0) {
+ days.push(dateStart.format("YYYY-MM-DD"));
+ dateStart.add(1, "days");
+ }
+ days.push(dateEnd.format("YYYY-MM-DD"));
+ return days;
+};
diff --git a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.bar.component.jsx b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.bar.component.jsx
new file mode 100644
index 000000000..427766d3b
--- /dev/null
+++ b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.bar.component.jsx
@@ -0,0 +1,157 @@
+import { Card } from "antd";
+import moment from "moment";
+import React, { useMemo } from "react";
+import { connect } from "react-redux";
+import {
+ Area,
+ Bar,
+ CartesianGrid,
+ ComposedChart,
+ Legend,
+ Line,
+ ResponsiveContainer,
+ Tooltip,
+ XAxis,
+ YAxis,
+} from "recharts";
+import { createStructuredSelector } from "reselect";
+import { selectBodyshop } from "../../redux/user/user.selectors";
+import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
+import _ from "lodash";
+
+const graphProps = {
+ strokeWidth: 3,
+};
+
+const mapStateToProps = createStructuredSelector({
+ bodyshop: selectBodyshop,
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ //setUserLanguage: language => dispatch(setUserLanguage(language))
+});
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(ScoreboardTicketsBar);
+
+export function ScoreboardTicketsBar({ start, end, timetickets, bodyshop }) {
+ console.log(
+ "🚀 ~ file: scoreboard-timetickets.bar.component.jsx ~ line 39 ~ start, end,",
+ start,
+ end
+ );
+ const listOfBusDays = Utils.ListOfDaysInCurrentMonth();
+
+ const data = useMemo(() => {
+ const ticketsGroupedByDate = _.groupBy(timetickets, "date");
+
+ const listOfDays = Utils.ListDaysBetween({ start, end });
+ console.log(
+ "🚀 ~ file: scoreboard-timetickets.bar.component.jsx ~ line 45 ~ listOfDays",
+ listOfDays
+ );
+
+ console.log(
+ "🚀 ~ file: scoreboard-timetickets.bar.component.jsx ~ line 43 ~ groupedByDate",
+ ticketsGroupedByDate
+ );
+
+ const ret = [];
+ listOfDays.forEach((day) => {
+ const r = {
+ date: day,
+ total: 0,
+ };
+ });
+ }, [timetickets]);
+ // const data = listOfBusDays.reduce((acc, val) => {
+ // //Sum up the current day.
+
+ // const groupedbyDate = _.groupBy(data, "date");
+ // let dayhrs;
+ // if (!!sbEntriesByDate[val]) {
+ // dayhrs = sbEntriesByDate[val].reduce(
+ // (dayAcc, dayVal) => {
+ // return {
+ // bodyhrs: dayAcc.bodyhrs + dayVal.bodyhrs,
+ // painthrs: dayAcc.painthrs + dayVal.painthrs,
+ // };
+ // },
+ // { bodyhrs: 0, painthrs: 0 }
+ // );
+ // } else {
+ // dayhrs = {
+ // bodyhrs: 0,
+ // painthrs: 0,
+ // };
+ // }
+
+ // const theValue = {
+ // date: moment(val).format("D ddd"),
+ // paintHrs: _.round(dayhrs.painthrs, 1),
+ // bodyHrs: _.round(dayhrs.bodyhrs, 1),
+ // accTargetHrs: _.round(
+ // Utils.AsOfDateTargetHours(
+ // bodyshop.scoreboard_target.dailyBodyTarget +
+ // bodyshop.scoreboard_target.dailyPaintTarget,
+ // val
+ // ),
+ // 1
+ // ),
+ // accHrs: _.round(
+ // acc.length > 0
+ // ? acc[acc.length - 1].accHrs + dayhrs.painthrs + dayhrs.bodyhrs
+ // : dayhrs.painthrs + dayhrs.bodyhrs,
+ // 1
+ // ),
+ // };
+
+ // return [...acc, theValue];
+ // }, []);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.component.jsx b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.component.jsx
new file mode 100644
index 000000000..fae8f4379
--- /dev/null
+++ b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.component.jsx
@@ -0,0 +1,45 @@
+import React from "react";
+import { useLocation } from "react-router-dom";
+import queryString from "query-string";
+import moment from "moment";
+import { useQuery } from "@apollo/client";
+import { QUERY_TIME_TICKETS_IN_RANGE } from "../../graphql/timetickets.queries";
+import AlertComponent from "../alert/alert.component";
+import TimeTicketsDatesSelector from "../../components/ticket-tickets-dates-selector/time-tickets-dates-selector.component";
+import ScoreboardTicketsBar from "./scoreboard-timetickets.bar.component";
+
+export default function ScoreboardTimeTickets() {
+ const searchParams = queryString.parse(useLocation().search);
+ const { start, end } = searchParams;
+ const startDate = start
+ ? moment(start)
+ : moment().startOf("week").subtract(7, "days");
+ const endDate = end ? moment(end) : moment().endOf("week");
+ const { loading, error, data } = useQuery(QUERY_TIME_TICKETS_IN_RANGE, {
+ variables: {
+ start: startDate,
+ end: endDate,
+ },
+ fetchPolicy: "network-only",
+ nextFetchPolicy: "network-only",
+ });
+ if (error) return ;
+
+ return (
+
+
+
+
+ );
+}
+
+//Include a filter by employee.
+
+//Hours produced today.
+//Hours produced in last 7 days
+//Hours produced for time period by day
+//Hours produced by employee by day for time period.
diff --git a/client/src/graphql/inventory.queries.js b/client/src/graphql/inventory.queries.js
index a2c36f6c1..51b713e92 100644
--- a/client/src/graphql/inventory.queries.js
+++ b/client/src/graphql/inventory.queries.js
@@ -4,6 +4,9 @@ export const INSERT_INVENTORY_AND_CREDIT = gql`
mutation INSERT_INVENTORY_AND_CREDIT(
$inv: inventory_insert_input!
$cm: bills_insert_input!
+ $pol: parts_orders_insert_input!
+ $joblineId: uuid!
+ $joblineStatus: String
) {
insert_inventory_one(object: $inv) {
id
@@ -11,6 +14,16 @@ export const INSERT_INVENTORY_AND_CREDIT = gql`
insert_bills_one(object: $cm) {
id
}
+ insert_parts_orders_one(object: $pol) {
+ id
+ }
+ update_joblines_by_pk(
+ pk_columns: { id: $joblineId }
+ _set: { status: $joblineStatus }
+ ) {
+ id
+ status
+ }
}
`;
export const UPDATE_INVENTORY_LINES = gql`
diff --git a/client/src/pages/scoreboard/scoreboard.page.container.jsx b/client/src/pages/scoreboard/scoreboard.page.container.jsx
index d1e8115d9..1abffe30e 100644
--- a/client/src/pages/scoreboard/scoreboard.page.container.jsx
+++ b/client/src/pages/scoreboard/scoreboard.page.container.jsx
@@ -1,3 +1,5 @@
+import Icon from "@ant-design/icons";
+import { Tabs } from "antd";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -10,6 +12,8 @@ import {
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
+import { FaHardHat, FaRegStickyNote, FaShieldAlt } from "react-icons/fa";
+import ScoreboardTimeTickets from "../../components/scoreboard-timetickets/scoreboard-timetickets.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -37,7 +41,32 @@ export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
return (
-
+
+
+
+ {t("menus.jobsdetail.general")}
+
+ }
+ destroyInactiveTabPane
+ key="sb"
+ >
+
+
+
+
+ {t("menus.jobsdetail.general")}
+
+ }
+ destroyInactiveTabPane
+ key="tickets"
+ >
+
+
+
);
From 1258843f3d29a5a853928f65f855f764e4f6d7bf Mon Sep 17 00:00:00 2001
From: Patrick Fic <>
Date: Thu, 2 Jun 2022 10:09:01 -0700
Subject: [PATCH 08/18] IO-1913 Add Rental Reservation key
---
bodyshop_translations.babel | 21 +++++++++++++++++++++
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 | 8 ++++++++
server/job/job-totals.js | 2 +-
6 files changed, 33 insertions(+), 1 deletion(-)
diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel
index eac8b6536..a6b7d0456 100644
--- a/bodyshop_translations.babel
+++ b/bodyshop_translations.babel
@@ -36514,6 +36514,27 @@
+
+ rental_reservation
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
ro_totals
false
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index 22faa81ea..9f42c9251 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -2166,6 +2166,7 @@
"purchases_by_ro_detail": "Purchases - Detail",
"purchases_by_ro_summary": "Purchases - Summary",
"qc_sheet": "Quality Control Sheet",
+ "rental_reservation": "Rental Reservation",
"ro_totals": "RO Totals",
"ro_with_description": "RO Summary with Descriptions",
"sgi_certificate_of_repairs": "SGI - Certificate of Repairs",
diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json
index e1fab299e..2c26b4a3b 100644
--- a/client/src/translations/es/common.json
+++ b/client/src/translations/es/common.json
@@ -2166,6 +2166,7 @@
"purchases_by_ro_detail": "",
"purchases_by_ro_summary": "",
"qc_sheet": "",
+ "rental_reservation": "",
"ro_totals": "",
"ro_with_description": "",
"sgi_certificate_of_repairs": "",
diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json
index 9edfc5aac..2f2547393 100644
--- a/client/src/translations/fr/common.json
+++ b/client/src/translations/fr/common.json
@@ -2166,6 +2166,7 @@
"purchases_by_ro_detail": "",
"purchases_by_ro_summary": "",
"qc_sheet": "",
+ "rental_reservation": "",
"ro_totals": "",
"ro_with_description": "",
"sgi_certificate_of_repairs": "",
diff --git a/client/src/utils/TemplateConstants.js b/client/src/utils/TemplateConstants.js
index 861a752ea..ee91dd02c 100644
--- a/client/src/utils/TemplateConstants.js
+++ b/client/src/utils/TemplateConstants.js
@@ -480,6 +480,14 @@ export const TemplateList = (type, context) => {
disabled: false,
group: "ro",
},
+ rental_reservation: {
+ title: i18n.t("printcenter.jobs.rental_reservation"),
+ description: "CASL Authorization",
+ subject: i18n.t("printcenter.jobs.rental_reservation"),
+ key: "rental_reservation",
+ disabled: false,
+ group: "pre",
+ },
}
: {}),
...(!type || type === "job_special"
diff --git a/server/job/job-totals.js b/server/job/job-totals.js
index b61259fe2..54b30a9d6 100644
--- a/server/job/job-totals.js
+++ b/server/job/job-totals.js
@@ -37,7 +37,7 @@ exports.totalsSsu = async function (req, res) {
clm_total: newTotals.totals.total_repairs.toFormat("0.00"),
owner_owing: newTotals.totals.custPayable.total.toFormat("0.00"),
job_totals: newTotals,
- queued_for_parts: true,
+ //queued_for_parts: true,
},
});
From 3feb1a388762469e8e39e9e3f495752ef1a8afc2 Mon Sep 17 00:00:00 2001
From: Patrick Fic <>
Date: Mon, 6 Jun 2022 09:53:42 -0700
Subject: [PATCH 09/18] IO-1914 Add split tracking for inventory.
---
.../bill-form/bill-form.lines.component.jsx | 23 ++++++++++++-------
1 file changed, 15 insertions(+), 8 deletions(-)
diff --git a/client/src/components/bill-form/bill-form.lines.component.jsx b/client/src/components/bill-form/bill-form.lines.component.jsx
index 5891741d7..18d996d5a 100644
--- a/client/src/components/bill-form/bill-form.lines.component.jsx
+++ b/client/src/components/bill-form/bill-form.lines.component.jsx
@@ -19,6 +19,7 @@ import CiecaSelect from "../../utils/Ciecaselect";
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.component";
+import { useTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -40,7 +41,11 @@ export function BillEnterModalLinesComponent({
}) {
const { t } = useTranslation();
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
-
+ const { Simple_Inventory } = useTreatments(
+ ["Simple_Inventory"],
+ {},
+ bodyshop && bodyshop.imexshopid
+ );
const columns = (remove) => {
return [
{
@@ -485,13 +490,15 @@ export function BillEnterModalLinesComponent({
- {() => (
-
- )}
+ {() =>
+ Simple_Inventory.treatment === "on" && (
+
+ )
+ }
),
From 674c06665cfc250ec2b9aea3b331e44ec1045b8a Mon Sep 17 00:00:00 2001
From: Patrick Fic <>
Date: Mon, 6 Jun 2022 09:54:02 -0700
Subject: [PATCH 10/18] IO-1911 Timetickets Scoreboard.
---
bodyshop_translations.babel | 189 ++++++++++++
.../scoreboard-targets-table.util.js | 5 -
.../scoreboard-timetickets.bar.component.jsx | 128 ++------
.../scoreboard-timetickets.component.jsx | 288 +++++++++++++++++-
...scoreboard-timetickets.stats.component.jsx | 101 ++++++
.../time-tickets-dates-selector.component.jsx | 2 +
client/src/graphql/jobs-lines.queries.js | 1 +
client/src/graphql/timetickets.queries.js | 36 ++-
.../scoreboard/scoreboard.page.container.jsx | 29 +-
client/src/translations/en_us/common.json | 9 +
client/src/translations/es/common.json | 9 +
client/src/translations/fr/common.json | 9 +
12 files changed, 674 insertions(+), 132 deletions(-)
create mode 100644 client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx
diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel
index a6b7d0456..22823dc7c 100644
--- a/bodyshop_translations.babel
+++ b/bodyshop_translations.babel
@@ -40827,6 +40827,69 @@
+
+ jobs
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+ lastmonth
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+ lastweek
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
monthlytarget
false
@@ -40848,6 +40911,48 @@
+
+ productivestatistics
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+ productivetimeticketsoverdate
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
targets
false
@@ -40869,6 +40974,69 @@
+
+ thismonth
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+ thisweek
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+ timetickets
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
todateactual
false
@@ -40890,6 +41058,27 @@
+
+ totaloverperiod
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
weeklyactual
false
diff --git a/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js b/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js
index 2b9c0b3f7..9f925a1a3 100644
--- a/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js
+++ b/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js
@@ -49,11 +49,6 @@ export const ListOfDaysInCurrentMonth = () => {
};
export const ListDaysBetween = ({ start, end }) => {
- console.log(
- "🚀 ~ file: scoreboard-targets-table.util.js ~ line 52 ~ start, end",
- start,
- end
- );
const days = [];
const dateStart = moment(start);
const dateEnd = moment(end);
diff --git a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.bar.component.jsx b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.bar.component.jsx
index 427766d3b..063638a47 100644
--- a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.bar.component.jsx
+++ b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.bar.component.jsx
@@ -1,14 +1,12 @@
import { Card } from "antd";
-import moment from "moment";
-import React, { useMemo } from "react";
+import React from "react";
+import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import {
- Area,
Bar,
CartesianGrid,
ComposedChart,
Legend,
- Line,
ResponsiveContainer,
Tooltip,
XAxis,
@@ -16,9 +14,7 @@ import {
} from "recharts";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
-import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
-import _ from "lodash";
-
+import TimeTicketsDatesSelector from "../ticket-tickets-dates-selector/time-tickets-dates-selector.component";
const graphProps = {
strokeWidth: 3,
};
@@ -35,86 +31,16 @@ export default connect(
mapDispatchToProps
)(ScoreboardTicketsBar);
-export function ScoreboardTicketsBar({ start, end, timetickets, bodyshop }) {
- console.log(
- "🚀 ~ file: scoreboard-timetickets.bar.component.jsx ~ line 39 ~ start, end,",
- start,
- end
- );
- const listOfBusDays = Utils.ListOfDaysInCurrentMonth();
-
- const data = useMemo(() => {
- const ticketsGroupedByDate = _.groupBy(timetickets, "date");
-
- const listOfDays = Utils.ListDaysBetween({ start, end });
- console.log(
- "🚀 ~ file: scoreboard-timetickets.bar.component.jsx ~ line 45 ~ listOfDays",
- listOfDays
- );
-
- console.log(
- "🚀 ~ file: scoreboard-timetickets.bar.component.jsx ~ line 43 ~ groupedByDate",
- ticketsGroupedByDate
- );
-
- const ret = [];
- listOfDays.forEach((day) => {
- const r = {
- date: day,
- total: 0,
- };
- });
- }, [timetickets]);
- // const data = listOfBusDays.reduce((acc, val) => {
- // //Sum up the current day.
-
- // const groupedbyDate = _.groupBy(data, "date");
- // let dayhrs;
- // if (!!sbEntriesByDate[val]) {
- // dayhrs = sbEntriesByDate[val].reduce(
- // (dayAcc, dayVal) => {
- // return {
- // bodyhrs: dayAcc.bodyhrs + dayVal.bodyhrs,
- // painthrs: dayAcc.painthrs + dayVal.painthrs,
- // };
- // },
- // { bodyhrs: 0, painthrs: 0 }
- // );
- // } else {
- // dayhrs = {
- // bodyhrs: 0,
- // painthrs: 0,
- // };
- // }
-
- // const theValue = {
- // date: moment(val).format("D ddd"),
- // paintHrs: _.round(dayhrs.painthrs, 1),
- // bodyHrs: _.round(dayhrs.bodyhrs, 1),
- // accTargetHrs: _.round(
- // Utils.AsOfDateTargetHours(
- // bodyshop.scoreboard_target.dailyBodyTarget +
- // bodyshop.scoreboard_target.dailyPaintTarget,
- // val
- // ),
- // 1
- // ),
- // accHrs: _.round(
- // acc.length > 0
- // ? acc[acc.length - 1].accHrs + dayhrs.painthrs + dayhrs.bodyhrs
- // : dayhrs.painthrs + dayhrs.bodyhrs,
- // 1
- // ),
- // };
-
- // return [...acc, theValue];
- // }, []);
-
+export function ScoreboardTicketsBar({ data, bodyshop }) {
+ const { t } = useTranslation();
return (
-
+ }
+ >
@@ -122,34 +48,32 @@ export function ScoreboardTicketsBar({ start, end, timetickets, bodyshop }) {
-
-
-
- */}
+ {data &&
+ data.employees.map((e, idx) => (
+
+ ))}
+
+ {/*
+ /> */}
diff --git a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.component.jsx b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.component.jsx
index fae8f4379..f85982359 100644
--- a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.component.jsx
+++ b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.component.jsx
@@ -1,12 +1,16 @@
-import React from "react";
-import { useLocation } from "react-router-dom";
-import queryString from "query-string";
-import moment from "moment";
import { useQuery } from "@apollo/client";
+import { Col, Row } from "antd";
+import _ from "lodash";
+import moment from "moment";
+import queryString from "query-string";
+import React, { useMemo } from "react";
+import { useLocation } from "react-router-dom";
import { QUERY_TIME_TICKETS_IN_RANGE } from "../../graphql/timetickets.queries";
import AlertComponent from "../alert/alert.component";
-import TimeTicketsDatesSelector from "../../components/ticket-tickets-dates-selector/time-tickets-dates-selector.component";
+import LoadingSpinner from "../loading-spinner/loading-spinner.component";
+import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
import ScoreboardTicketsBar from "./scoreboard-timetickets.bar.component";
+import ScoreboardTicketsStats from "./scoreboard-timetickets.stats.component";
export default function ScoreboardTimeTickets() {
const searchParams = queryString.parse(useLocation().search);
@@ -15,25 +19,230 @@ export default function ScoreboardTimeTickets() {
? moment(start)
: moment().startOf("week").subtract(7, "days");
const endDate = end ? moment(end) : moment().endOf("week");
+
+ const fixedPeriods = useMemo(() => {
+ const endOfThisMonth = moment().endOf("month");
+ const startofthisMonth = moment().startOf("month");
+
+ const endOfLastmonth = moment().subtract(1, "month").endOf("month");
+ const startOfLastmonth = moment().subtract(1, "month").startOf("month");
+
+ const endOfThisWeek = moment().endOf("week");
+ const startOfThisWeek = moment().startOf("week");
+
+ const endOfLastWeek = moment().subtract(1, "week").endOf("week");
+ const startOfLastWeek = moment().subtract(1, "week").startOf("week");
+
+ const allDates = [
+ endOfThisMonth,
+ startofthisMonth,
+ endOfLastmonth,
+ startOfLastmonth,
+ endOfThisWeek,
+ startOfThisWeek,
+ endOfLastWeek,
+ startOfLastWeek,
+ ];
+ const start = moment.min(allDates);
+ const end = moment.max(allDates);
+ return {
+ start,
+ end,
+ endOfThisMonth,
+ startofthisMonth,
+ endOfLastmonth,
+ startOfLastmonth,
+ endOfThisWeek,
+ startOfThisWeek,
+ endOfLastWeek,
+ startOfLastWeek,
+ };
+ }, []);
+
const { loading, error, data } = useQuery(QUERY_TIME_TICKETS_IN_RANGE, {
variables: {
- start: startDate,
- end: endDate,
+ start: startDate.format("YYYY-MM-DD"),
+ end: endDate.format("YYYY-MM-DD"),
+ fixedStart: fixedPeriods.start.format("YYYY-MM-DD"),
+ fixedEnd: fixedPeriods.end.format("YYYY-MM-DD"),
},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
+ pollInterval: 60000,
+ skip: !fixedPeriods,
});
- if (error) return ;
+ const calculatedData = useMemo(() => {
+ if (!data) return [];
+ const ret = {
+ totalThisWeek: 0,
+ totalLastWeek: 0,
+ totalThisMonth: 0,
+ totalLastMonth: 0,
+ totalOverPeriod: 0,
+ employees: {},
+ };
+ data.fixedperiod.forEach((ticket) => {
+ const ticketDate = moment(ticket.date);
+
+ if (!ret.employees[ticket.employee.employee_number]) {
+ ret.employees[ticket.employee.employee_number] = {
+ totalThisWeek: 0,
+ totalLastWeek: 0,
+ totalThisMonth: 0,
+ totalLastMonth: 0,
+ totalOverPeriod: 0,
+ };
+ }
+
+ if (
+ ticketDate.isBetween(
+ fixedPeriods.startOfThisWeek,
+ fixedPeriods.endOfThisWeek,
+ undefined,
+ "[]"
+ )
+ ) {
+ ret.totalThisWeek = ret.totalThisWeek + ticket.productivehrs;
+ ret.employees[ticket.employee.employee_number].totalThisWeek =
+ ret.employees[ticket.employee.employee_number].totalThisWeek +
+ ticket.productivehrs;
+ } else if (
+ ticketDate.isBetween(
+ fixedPeriods.startOfLastWeek,
+ fixedPeriods.endOfLastWeek,
+ undefined,
+ "[]"
+ )
+ ) {
+ ret.totalLastWeek = ret.totalLastWeek + ticket.productivehrs;
+ ret.employees[ticket.employee.employee_number].totalLastWeek =
+ ret.employees[ticket.employee.employee_number].totalLastWeek +
+ ticket.productivehrs;
+ }
+ if (
+ ticketDate.isBetween(
+ fixedPeriods.startofthisMonth,
+ fixedPeriods.endOfThisMonth,
+ undefined,
+ "[]"
+ )
+ ) {
+ ret.totalThisMonth = ret.totalThisMonth + ticket.productivehrs;
+ ret.employees[ticket.employee.employee_number].totalThisMonth =
+ ret.employees[ticket.employee.employee_number].totalThisMonth +
+ ticket.productivehrs;
+ } else if (
+ ticketDate.isBetween(
+ fixedPeriods.startOfLastmonth,
+ fixedPeriods.endOfLastmonth,
+ undefined,
+ "[]"
+ )
+ ) {
+ ret.totalLastMonth = ret.totalLastMonth + ticket.productivehrs;
+ ret.employees[ticket.employee.employee_number].totalLastMonth =
+ ret.employees[ticket.employee.employee_number].totalLastMonth +
+ ticket.productivehrs;
+ }
+ });
+
+ const ticketsGroupedByDate = _.groupBy(data.timetickets, "date");
+ const listOfDays = Utils.ListDaysBetween({
+ start: startDate,
+ end: endDate,
+ });
+
+ const employees = [];
+ const ret2 = [];
+ let totals = {
+ totalproductive: 0,
+ totalactual: 0,
+ employees: {},
+ };
+
+ listOfDays.forEach((day) => {
+ const r = {
+ date: moment(day).format("MM/DD"),
+ actualtotal: 0,
+ productivetotal: 0,
+ employees: {},
+ };
+
+ if (ticketsGroupedByDate[day]) {
+ ticketsGroupedByDate[day].forEach((ticket) => {
+ r.actualtotal = r.actualtotal + ticket.actualhrs;
+ r.productivetotal = r.productivetotal + ticket.productivehrs;
+ totals.totalactual = totals.totalactual + ticket.actualhrs;
+ totals.totalproductive =
+ totals.totalproductive + ticket.productivehrs;
+
+ employees.push(ticket.employee.employee_number);
+ //Add to table data.
+ ret.employees[ticket.employee.employee_number].totalOverPeriod =
+ ret.employees[ticket.employee.employee_number].totalOverPeriod +
+ ticket.productivehrs;
+
+ if (!totals.employees[ticket.employee.employee_number])
+ totals.employees[ticket.employee.employee_number] = {
+ totalactual: 0,
+ totalproductive: 0,
+ };
+
+ if (!r.employees[ticket.employee.employee_number])
+ r.employees[ticket.employee.employee_number] = {
+ actual: 0,
+ productive: 0,
+ };
+
+ //Add to totals.
+ totals.employees[ticket.employee.employee_number].totalproductive =
+ totals.employees[ticket.employee.employee_number].totalproductive +
+ ticket.productivehrs;
+
+ totals.employees[ticket.employee.employee_number].totalactual =
+ totals.employees[ticket.employee.employee_number].totalactual +
+ ticket.actualhrs;
+ //Add to dailys.
+ r.employees[ticket.employee.employee_number].productive =
+ r.employees[ticket.employee.employee_number].productive +
+ ticket.productivehrs;
+
+ r.employees[ticket.employee.employee_number].actual =
+ r.employees[ticket.employee.employee_number].actual +
+ ticket.actualhrs;
+ });
+ }
+
+ ret2.push(r);
+ });
+
+ return {
+ fixed: ret,
+ timeperiod: {
+ totals,
+ chartData: ret2,
+ employees: _.uniq(employees),
+ colors: getColorArray(employees.length),
+ },
+ };
+ }, [fixedPeriods, data, startDate, endDate]);
+
+ if (error) return ;
+ if (loading) return ;
return (
-
-
-
-
+
+
+
+
+
+
+
+
);
}
@@ -43,3 +252,50 @@ export default function ScoreboardTimeTickets() {
//Hours produced in last 7 days
//Hours produced for time period by day
//Hours produced by employee by day for time period.
+
+function getColorArray(num) {
+ return [
+ "#3366cc",
+ "#dc3912",
+ "#ff9900",
+ "#109618",
+ "#990099",
+ "#0099c6",
+ "#dd4477",
+ "#66aa00",
+ "#b82e2e",
+ "#316395",
+ "#3366cc",
+ "#994499",
+ "#22aa99",
+ "#aaaa11",
+ "#6633cc",
+ "#e67300",
+ "#8b0707",
+ "#651067",
+ "#329262",
+ "#5574a6",
+ "#3b3eac",
+ "#b77322",
+ "#16d620",
+ "#b91383",
+ "#f4359e",
+ "#9c5935",
+ "#a9c413",
+ "#2a778d",
+ "#668d1c",
+ "#bea413",
+ "#0c5922",
+ "#743411",
+ ];
+ // var result = [];
+ // for (var i = 0; i < num; i += 1) {
+ // var letters = "0123456789ABCDEF".split("");
+ // var color = "#";
+ // for (var j = 0; j < 6; j += 1) {
+ // color += letters[Math.floor(Math.random() * 16)];
+ // }
+ // result.push(color);
+ // }
+ // return result;
+}
diff --git a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx
new file mode 100644
index 000000000..863f5f14f
--- /dev/null
+++ b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx
@@ -0,0 +1,101 @@
+import { Card, Col, Row, Space, Statistic, Table } from "antd";
+import React from "react";
+import { useTranslation } from "react-i18next";
+import { connect } from "react-redux";
+import { createStructuredSelector } from "reselect";
+import { selectBodyshop } from "../../redux/user/user.selectors";
+
+const mapStateToProps = createStructuredSelector({
+ bodyshop: selectBodyshop,
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ //setUserLanguage: language => dispatch(setUserLanguage(language))
+});
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(ScoreboardTicketsStats);
+
+export function ScoreboardTicketsStats({ data, bodyshop }) {
+ const { t } = useTranslation();
+
+ const columns = [
+ {
+ title: t("employees.fields.employee_number"),
+ dataIndex: "employee_number",
+ key: "employee_number",
+ sorter: (a, b) => a.employee_number - b.employee_number,
+ },
+ {
+ title: t("scoreboard.labels.thisweek"),
+ dataIndex: "totalThisWeek",
+ key: "totalThisWeek",
+ sorter: (a, b) => a.totalThisWeek - b.totalThisWeek,
+ },
+ {
+ title: t("scoreboard.labels.lastweek"),
+ dataIndex: "totalLastWeek",
+ key: "totalLastWeek",
+ sorter: (a, b) => a.totalLastWeek - b.totalLastWeek,
+ },
+ {
+ title: t("scoreboard.labels.thismonth"),
+ dataIndex: "totalThisMonth",
+ key: "totalThisMonth",
+ sorter: (a, b) => a.totalThisMonth - b.totalThisMonth,
+ },
+ {
+ title: t("scoreboard.labels.lastmonth"),
+ dataIndex: "totalLastMonth",
+ key: "totalLastMonth",
+ sorter: (a, b) => a.totalLastMonth - b.totalLastMonth,
+ },
+ {
+ title: t("scoreboard.labels.totaloverperiod"),
+ dataIndex: "totalOverPeriod",
+ key: "totalOverPeriod",
+ sorter: (a, b) => a.totalOverPeriod - b.totalOverPeriod,
+ },
+ ];
+
+ const tableData = data
+ ? Object.keys(data.employees).map((key) => {
+ return { employee_number: key, ...data.employees[key] };
+ })
+ : [];
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/client/src/components/ticket-tickets-dates-selector/time-tickets-dates-selector.component.jsx b/client/src/components/ticket-tickets-dates-selector/time-tickets-dates-selector.component.jsx
index b7a5ac44b..a827e3fd7 100644
--- a/client/src/components/ticket-tickets-dates-selector/time-tickets-dates-selector.component.jsx
+++ b/client/src/components/ticket-tickets-dates-selector/time-tickets-dates-selector.component.jsx
@@ -17,6 +17,7 @@ export default function TimeTicketsDatesSelector() {
if (!!start && !!end) {
history.push({
search: queryString.stringify({
+ ...searchParams,
start: start.format("YYYY-MM-DD"),
end: end.format("YYYY-MM-DD"),
}),
@@ -25,6 +26,7 @@ export default function TimeTicketsDatesSelector() {
} else {
history.push({
search: queryString.stringify({
+ ...searchParams,
start: null,
end: null,
}),
diff --git a/client/src/graphql/jobs-lines.queries.js b/client/src/graphql/jobs-lines.queries.js
index 3617df239..b2184f072 100644
--- a/client/src/graphql/jobs-lines.queries.js
+++ b/client/src/graphql/jobs-lines.queries.js
@@ -61,6 +61,7 @@ export const GET_LINE_TICKET_BY_PK = gql`
flat_rate
clockon
clockoff
+ rate
employee {
id
first_name
diff --git a/client/src/graphql/timetickets.queries.js b/client/src/graphql/timetickets.queries.js
index 057edc1cb..12d370560 100644
--- a/client/src/graphql/timetickets.queries.js
+++ b/client/src/graphql/timetickets.queries.js
@@ -26,7 +26,12 @@ export const QUERY_TICKETS_BY_JOBID = gql`
`;
export const QUERY_TIME_TICKETS_IN_RANGE = gql`
- query QUERY_TIME_TICKETS_IN_RANGE($start: date!, $end: date!) {
+ query QUERY_TIME_TICKETS_IN_RANGE(
+ $start: date!
+ $end: date!
+ $fixedStart: date!
+ $fixedEnd: date!
+ ) {
timetickets(
where: { date: { _gte: $start, _lte: $end } }
order_by: { date: desc_nulls_first }
@@ -56,6 +61,35 @@ export const QUERY_TIME_TICKETS_IN_RANGE = gql`
last_name
}
}
+ fixedperiod: timetickets(
+ where: { date: { _gte: $fixedStart, _lte: $fixedEnd } }
+ order_by: { date: desc_nulls_first }
+ ) {
+ actualhrs
+ ciecacode
+ clockoff
+ clockon
+ cost_center
+ created_at
+ date
+ id
+ rate
+ productivehrs
+ memo
+ jobid
+ flat_rate
+ job {
+ id
+ ro_number
+ }
+ employeeid
+ employee {
+ id
+ employee_number
+ first_name
+ last_name
+ }
+ }
}
`;
diff --git a/client/src/pages/scoreboard/scoreboard.page.container.jsx b/client/src/pages/scoreboard/scoreboard.page.container.jsx
index 1abffe30e..a15ad12ac 100644
--- a/client/src/pages/scoreboard/scoreboard.page.container.jsx
+++ b/client/src/pages/scoreboard/scoreboard.page.container.jsx
@@ -1,19 +1,21 @@
-import Icon from "@ant-design/icons";
+import Icon, { BarsOutlined } from "@ant-design/icons";
import { Tabs } from "antd";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
+import { FaShieldAlt } from "react-icons/fa";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import ScoreboardDisplay from "../../components/scoreboard-display/scoreboard-display.component";
+import ScoreboardTimeTickets from "../../components/scoreboard-timetickets/scoreboard-timetickets.component";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
-import { FaHardHat, FaRegStickyNote, FaShieldAlt } from "react-icons/fa";
-import ScoreboardTimeTickets from "../../components/scoreboard-timetickets/scoreboard-timetickets.component";
+import queryString from "query-string";
+import { useHistory, useLocation } from "react-router-dom";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -26,7 +28,9 @@ const mapDispatchToProps = (dispatch) => ({
export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
-
+ const searchParams = queryString.parse(useLocation().search);
+ const { tab } = searchParams;
+ const history = useHistory();
useEffect(() => {
document.title = t("titles.scoreboard");
setSelectedHeader("scoreboard");
@@ -41,12 +45,21 @@ export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
return (
-
+ {
+ searchParams.tab = key;
+ history.push({
+ search: queryString.stringify(searchParams),
+ });
+ }}
+ >
- {t("menus.jobsdetail.general")}
+ {t("scoreboard.labels.jobs")}
}
destroyInactiveTabPane
@@ -57,8 +70,8 @@ export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
-
- {t("menus.jobsdetail.general")}
+
+ {t("scoreboard.labels.timetickets")}
}
destroyInactiveTabPane
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index 9f42c9251..ba02ecc71 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -2426,9 +2426,18 @@
"asoftodaytarget": "As of Today",
"dailyactual": "Actual (D)",
"dailytarget": "Daily",
+ "jobs": "Jobs",
+ "lastmonth": "Last Month",
+ "lastweek": "Last Week",
"monthlytarget": "Monthly",
+ "productivestatistics": "Productive Hours Statistics",
+ "productivetimeticketsoverdate": "Productive Hours over Selected Dates",
"targets": "Targets",
+ "thismonth": "This Month",
+ "thisweek": "This Week",
+ "timetickets": "Timetickets",
"todateactual": "Actual (MTD)",
+ "totaloverperiod": "Total Period",
"weeklyactual": "Actual (W)",
"weeklytarget": "Weekly",
"workingdays": "Working Days / Month"
diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json
index 2c26b4a3b..ae5d81de2 100644
--- a/client/src/translations/es/common.json
+++ b/client/src/translations/es/common.json
@@ -2426,9 +2426,18 @@
"asoftodaytarget": "",
"dailyactual": "",
"dailytarget": "",
+ "jobs": "",
+ "lastmonth": "",
+ "lastweek": "",
"monthlytarget": "",
+ "productivestatistics": "",
+ "productivetimeticketsoverdate": "",
"targets": "",
+ "thismonth": "",
+ "thisweek": "",
+ "timetickets": "",
"todateactual": "",
+ "totaloverperiod": "",
"weeklyactual": "",
"weeklytarget": "",
"workingdays": ""
diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json
index 2f2547393..b71d833e8 100644
--- a/client/src/translations/fr/common.json
+++ b/client/src/translations/fr/common.json
@@ -2426,9 +2426,18 @@
"asoftodaytarget": "",
"dailyactual": "",
"dailytarget": "",
+ "jobs": "",
+ "lastmonth": "",
+ "lastweek": "",
"monthlytarget": "",
+ "productivestatistics": "",
+ "productivetimeticketsoverdate": "",
"targets": "",
+ "thismonth": "",
+ "thisweek": "",
+ "timetickets": "",
"todateactual": "",
+ "totaloverperiod": "",
"weeklyactual": "",
"weeklytarget": "",
"workingdays": ""
From bd6553f8e45795033a2c3fe5e3992b9ecf8697ce Mon Sep 17 00:00:00 2001
From: Patrick Fic
Date: Mon, 6 Jun 2022 10:59:17 -0700
Subject: [PATCH 11/18] Resolve AR Account Reference.
---
server/accounting/qbxml/qbxml-receivables.js | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/server/accounting/qbxml/qbxml-receivables.js b/server/accounting/qbxml/qbxml-receivables.js
index 78c7389f7..29af650da 100644
--- a/server/accounting/qbxml/qbxml-receivables.js
+++ b/server/accounting/qbxml/qbxml-receivables.js
@@ -249,13 +249,13 @@ const generateInvoiceQbxml = (
)}:${generateJobTier(jobs_by_pk)}`
).trim(),
},
- ARAccountRef: {
- FullName: bodyshop.md_responsibility_centers.ar.accountname,
- },
...(jobs_by_pk.class
? { ClassRef: { FullName: jobs_by_pk.class } }
: {}),
-
+
+ ARAccountRef: {
+ FullName: bodyshop.md_responsibility_centers.ar.accountname,
+ },
TxnDate: moment(jobs_by_pk.date_invoiced)
.tz(bodyshop.timezone)
.format("YYYY-MM-DD"),
From 3666b7cd22fcebf4dbcc125b917a250eb64dc7e6 Mon Sep 17 00:00:00 2001
From: Patrick Fic <>
Date: Mon, 6 Jun 2022 12:04:47 -0700
Subject: [PATCH 12/18] IO-1911 Tickets scoreboard updates.
---
bodyshop_translations.babel | 52 +++++++-----------
.../scoreboard-entry-edit.component.jsx | 2 +-
...scoreboard-timetickets.stats.component.jsx | 55 ++++++++++++-------
client/src/translations/en_us/common.json | 8 +--
client/src/translations/es/common.json | 6 +-
client/src/translations/fr/common.json | 6 +-
6 files changed, 60 insertions(+), 69 deletions(-)
diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel
index 22823dc7c..b0565a686 100644
--- a/bodyshop_translations.babel
+++ b/bodyshop_translations.babel
@@ -40785,6 +40785,27 @@
+
+ calendarperiod
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
dailyactual
false
@@ -41214,37 +41235,6 @@
-
- scoredboard
-
-
- successes
-
-
- updated
- false
-
-
-
-
-
- en-US
- false
-
-
- es-MX
- false
-
-
- fr-CA
- false
-
-
-
-
-
-
-
tech
diff --git a/client/src/components/scoreboard-entry-edit/scoreboard-entry-edit.component.jsx b/client/src/components/scoreboard-entry-edit/scoreboard-entry-edit.component.jsx
index 3a74409f2..a8488e9dd 100644
--- a/client/src/components/scoreboard-entry-edit/scoreboard-entry-edit.component.jsx
+++ b/client/src/components/scoreboard-entry-edit/scoreboard-entry-edit.component.jsx
@@ -26,7 +26,7 @@ export default function ScoreboardEntryEdit({ entry }) {
return;
} else {
notification["success"]({
- message: t("scoredboard.successes.updated"),
+ message: t("scoreboard.successes.updated"),
});
setVisible(false);
}
diff --git a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx
index 863f5f14f..71492893c 100644
--- a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx
+++ b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx
@@ -1,4 +1,4 @@
-import { Card, Col, Row, Space, Statistic, Table } from "antd";
+import { Card, Col, Row, Space, Statistic, Table, Typography } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -68,27 +68,40 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
return (
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {t("scoreboard.labels.calendarperiod")}
+
-
+
Date: Mon, 6 Jun 2022 12:10:01 -0700
Subject: [PATCH 13/18] IO-1911 Add scrolling to time tickets scoreboard.
---
.../scoreboard-timetickets.stats.component.jsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx
index 71492893c..8b521e990 100644
--- a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx
+++ b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx
@@ -106,6 +106,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
columns={columns}
dataSource={tableData}
id="employee_number"
+ scroll={{ y: "300px" }}
/>
From a0b238c4bb867a5576217d5773253b0f6fc811c4 Mon Sep 17 00:00:00 2001
From: Patrick Fic <>
Date: Mon, 6 Jun 2022 12:20:32 -0700
Subject: [PATCH 14/18] IO-1841 Remove special characters on QB Export.
---
.../scoreboard-timetickets.stats.component.jsx | 2 +-
server/accounting/qbo/qbo-receivables.js | 5 +++--
server/accounting/qbxml/qbxml-utils.js | 12 ++++++++----
3 files changed, 12 insertions(+), 7 deletions(-)
diff --git a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx
index 8b521e990..6d2183a54 100644
--- a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx
+++ b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx
@@ -1,4 +1,4 @@
-import { Card, Col, Row, Space, Statistic, Table, Typography } from "antd";
+import { Card, Col, Row, Statistic, Table, Typography } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
diff --git a/server/accounting/qbo/qbo-receivables.js b/server/accounting/qbo/qbo-receivables.js
index bab135c25..fec6a15c9 100644
--- a/server/accounting/qbo/qbo-receivables.js
+++ b/server/accounting/qbo/qbo-receivables.js
@@ -211,7 +211,7 @@ async function QueryInsuranceCo(oauthClient, qbo_realmId, req, job) {
qbo_realmId,
"query",
`select * From Customer where DisplayName = '${StandardizeName(
- job.ins_co_nm
+ job.ins_co_nm.trim()
)}'`
),
method: "POST",
@@ -239,7 +239,7 @@ async function InsertInsuranceCo(oauthClient, qbo_realmId, req, job, bodyshop) {
const insCo = bodyshop.md_ins_cos.find((i) => i.name === job.ins_co_nm);
const Customer = {
- DisplayName: job.ins_co_nm,
+ DisplayName: job.ins_co_nm.trim(),
BillWithParent: true,
BillAddr: {
City: job.ownr_city,
@@ -269,6 +269,7 @@ async function InsertInsuranceCo(oauthClient, qbo_realmId, req, job, bodyshop) {
}
}
exports.InsertInsuranceCo = InsertInsuranceCo;
+
async function QueryOwner(oauthClient, qbo_realmId, req, job) {
const ownerName = generateOwnerTier(job, true, null);
const result = await oauthClient.makeApiCall({
diff --git a/server/accounting/qbxml/qbxml-utils.js b/server/accounting/qbxml/qbxml-utils.js
index 3cee63ca8..af59b3cf7 100644
--- a/server/accounting/qbxml/qbxml-utils.js
+++ b/server/accounting/qbxml/qbxml-utils.js
@@ -6,11 +6,11 @@ exports.addQbxmlHeader = addQbxmlHeader = (xml) => {
};
exports.generateSourceTier = (jobs_by_pk) => {
- return jobs_by_pk.ins_co_nm && jobs_by_pk.ins_co_nm.trim();
+ return jobs_by_pk.ins_co_nm && jobs_by_pk.ins_co_nm.trim().replace(":", " ");
};
exports.generateJobTier = (jobs_by_pk) => {
- return jobs_by_pk.ro_number && jobs_by_pk.ro_number.trim();
+ return jobs_by_pk.ro_number && jobs_by_pk.ro_number.trim().replace(":", " ");
};
exports.generateOwnerTier = (jobs_by_pk, isThreeTier, twotierpref) => {
@@ -24,7 +24,9 @@ exports.generateOwnerTier = (jobs_by_pk, isThreeTier, twotierpref) => {
: `${`${jobs_by_pk.ownr_ln || ""} ${
jobs_by_pk.ownr_fn || ""
}`.substring(0, 30)} #${jobs_by_pk.owner.accountingid || ""}`
- ).trim();
+ )
+ .trim()
+ .replace(":", " ");
} else {
//What's the 2 tier pref?
if (twotierpref === "source") {
@@ -40,7 +42,9 @@ exports.generateOwnerTier = (jobs_by_pk, isThreeTier, twotierpref) => {
: `${`${jobs_by_pk.ownr_ln || ""} ${
jobs_by_pk.ownr_fn || ""
}`.substring(0, 30)} #${jobs_by_pk.owner.accountingid || ""}`
- ).trim();
+ )
+ .trim()
+ .replace(":", " ");
}
}
};
From 320aa9c1778fa91dde829b0099f7875ea3936d15 Mon Sep 17 00:00:00 2001
From: Patrick Fic <>
Date: Mon, 6 Jun 2022 12:32:32 -0700
Subject: [PATCH 15/18] IO-1923 Add CC warning to CC list page.
---
.../courtesy-cars-list.component.jsx | 26 ++++++++++++++++---
client/src/graphql/courtesy-car.queries.js | 1 +
2 files changed, 23 insertions(+), 4 deletions(-)
diff --git a/client/src/components/courtesy-cars-list/courtesy-cars-list.component.jsx b/client/src/components/courtesy-cars-list/courtesy-cars-list.component.jsx
index a488368b9..62f6d1119 100644
--- a/client/src/components/courtesy-cars-list/courtesy-cars-list.component.jsx
+++ b/client/src/components/courtesy-cars-list/courtesy-cars-list.component.jsx
@@ -1,12 +1,12 @@
-import { SyncOutlined } from "@ant-design/icons";
-import { Button, Card, Input, Space, Table } from "antd";
+import { SyncOutlined, WarningFilled } from "@ant-design/icons";
+import { Button, Card, Input, Space, Table, Tooltip } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
-
+import moment from "moment";
export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
const [state, setState] = useState({
sortedInfo: {},
@@ -56,7 +56,25 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
onFilter: (value, record) => value.includes(record.status),
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
- render: (text, record) => t(record.status),
+ render: (text, record) => {
+ const { nextservicedate, nextservicekm, mileage } = record;
+
+ const mileageOver = nextservicekm <= mileage;
+
+ const dueForService =
+ nextservicedate && moment(nextservicedate).isBefore(moment());
+
+ return (
+
+ {t(record.status)}
+ {(mileageOver || dueForService) && (
+
+
+
+ )}
+
+ );
+ },
},
{
title: t("courtesycars.fields.year"),
diff --git a/client/src/graphql/courtesy-car.queries.js b/client/src/graphql/courtesy-car.queries.js
index d4e19bac8..a5bcbea55 100644
--- a/client/src/graphql/courtesy-car.queries.js
+++ b/client/src/graphql/courtesy-car.queries.js
@@ -80,6 +80,7 @@ export const QUERY_ALL_CC = gql`
status
vin
year
+ mileage
cccontracts(
where: { status: { _eq: "contracts.status.out" } }
order_by: { contract_date: desc }
From 644f2696293f7986529e7179fa8ed39361b15e62 Mon Sep 17 00:00:00 2001
From: Patrick Fic <>
Date: Mon, 6 Jun 2022 12:59:24 -0700
Subject: [PATCH 16/18] IO-1919 Add filtering to parts queue.
---
.../parts-queue.page.component.jsx | 20 +++++++++++--------
1 file changed, 12 insertions(+), 8 deletions(-)
diff --git a/client/src/pages/parts-queue/parts-queue.page.component.jsx b/client/src/pages/parts-queue/parts-queue.page.component.jsx
index b0b66cbf1..bc6bf2c03 100644
--- a/client/src/pages/parts-queue/parts-queue.page.component.jsx
+++ b/client/src/pages/parts-queue/parts-queue.page.component.jsx
@@ -1,7 +1,6 @@
import { SyncOutlined } from "@ant-design/icons";
import { useQuery } from "@apollo/client";
import { Button, Card, Input, Space, Table } from "antd";
-import _ from "lodash";
import queryString from "query-string";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
@@ -92,13 +91,7 @@ export function PartsQueuePageComponent({ bodyshop }) {
// searchParams.page = pagination.current;
searchParams.sortcolumn = sorter.columnKey;
searchParams.sortorder = sorter.order;
- if (filters.status) {
- searchParams.statusFilters = JSON.stringify(
- _.flattenDeep(filters.status)
- );
- } else {
- delete searchParams.statusFilters;
- }
+
history.push({ search: queryString.stringify(searchParams) });
};
@@ -244,6 +237,17 @@ export function PartsQueuePageComponent({ bodyshop }) {
key: "queued_for_parts",
sorter: (a, b) => a.queued_for_parts - b.queued_for_parts,
sortOrder: sortcolumn === "queued_for_parts" && sortorder,
+ filters: [
+ {
+ text: "Queued",
+ value: true,
+ },
+ {
+ text: "Unqueued",
+ value: false,
+ },
+ ],
+ //onFilter: (value, record) => record.queued_for_parts === value,
render: (text, record) => (
Date: Mon, 6 Jun 2022 13:53:09 -0700
Subject: [PATCH 17/18] IO-1920 Add ability to directly post tickets from tech
console.
---
bodyshop_translations.babel | 26 ++++++++++++++++++
.../tech-job-clock-in-form.container.jsx | 27 ++++++++++++++++---
.../time-ticket-modal.component.jsx | 2 ++
.../time-ticket-modal.container.jsx | 6 +++++
client/src/translations/en_us/common.json | 3 +++
client/src/translations/es/common.json | 3 +++
client/src/translations/fr/common.json | 3 +++
7 files changed, 67 insertions(+), 3 deletions(-)
diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel
index b0565a686..516cab7d9 100644
--- a/bodyshop_translations.babel
+++ b/bodyshop_translations.babel
@@ -5691,6 +5691,32 @@
+
+ inventory
+
+
+ list
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+
jobs
diff --git a/client/src/components/tech-job-clock-in-form/tech-job-clock-in-form.container.jsx b/client/src/components/tech-job-clock-in-form/tech-job-clock-in-form.container.jsx
index cae229746..4144d06d3 100644
--- a/client/src/components/tech-job-clock-in-form/tech-job-clock-in-form.container.jsx
+++ b/client/src/components/tech-job-clock-in-form/tech-job-clock-in-form.container.jsx
@@ -11,13 +11,21 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import TechClockInComponent from "./tech-job-clock-in-form.component";
import TechJobPrintTickets from "../tech-job-print-tickets/tech-job-print-tickets.component";
import moment from "moment";
+import { setModalContext } from "../../redux/modals/modals.actions";
const mapStateToProps = createStructuredSelector({
technician: selectTechnician,
bodyshop: selectBodyshop,
});
-
-export function TechClockInContainer({ technician, bodyshop }) {
+const mapDispatchToProps = (dispatch) => ({
+ setTimeTicketContext: (context) =>
+ dispatch(setModalContext({ context: context, modal: "timeTicket" })),
+});
+export function TechClockInContainer({
+ setTimeTicketContext,
+ technician,
+ bodyshop,
+}) {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [insertTimeTicket] = useMutation(INSERT_NEW_TIME_TICKET, {
@@ -75,6 +83,16 @@ export function TechClockInContainer({ technician, bodyshop }) {
title={t("timetickets.labels.clockintojob")}
extra={
+ {
+ setTimeTicketContext({
+ actions: {},
+ context: { timeticket: { employeeid: technician.id } },
+ });
+ }}
+ >
+ {t("timetickets.actions.enter")}
+
);
}
-export default connect(mapStateToProps, null)(TechClockInContainer);
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(TechClockInContainer);
diff --git a/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx b/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx
index c8e37a688..e5cbd67ee 100644
--- a/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx
+++ b/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx
@@ -35,6 +35,7 @@ export function TimeTicketModalComponent({
authLevel,
employeeAutoCompleteOptions,
isEdit,
+ employeeSelectDisabled,
}) {
const { t } = useTranslation();
@@ -118,6 +119,7 @@ export function TimeTicketModalComponent({
]}
>
{
const emps =
diff --git a/client/src/components/time-ticket-modal/time-ticket-modal.container.jsx b/client/src/components/time-ticket-modal/time-ticket-modal.container.jsx
index 0ec1d3035..463b2a80c 100644
--- a/client/src/components/time-ticket-modal/time-ticket-modal.container.jsx
+++ b/client/src/components/time-ticket-modal/time-ticket-modal.container.jsx
@@ -244,6 +244,12 @@ export function TimeTicketModalContainer({
employeeAutoCompleteOptions={
EmployeeAutoCompleteData && EmployeeAutoCompleteData.employees
}
+ employeeSelectDisabled={
+ timeTicketModal.context?.timeticket?.employeeid &&
+ !timeTicketModal.context.id
+ ? true
+ : false
+ }
/>
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index 6e9d6f82c..f283e1777 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -352,6 +352,9 @@
"employees": {
"page": "Employees -> List"
},
+ "inventory": {
+ "list": "Inventory -> List"
+ },
"jobs": {
"admin": "Jobs -> Admin",
"available-list": "Jobs -> Available List",
diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json
index c0570bb5f..b40b5175d 100644
--- a/client/src/translations/es/common.json
+++ b/client/src/translations/es/common.json
@@ -352,6 +352,9 @@
"employees": {
"page": ""
},
+ "inventory": {
+ "list": ""
+ },
"jobs": {
"admin": "",
"available-list": "",
diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json
index 56c6f95bf..6843f664f 100644
--- a/client/src/translations/fr/common.json
+++ b/client/src/translations/fr/common.json
@@ -352,6 +352,9 @@
"employees": {
"page": ""
},
+ "inventory": {
+ "list": ""
+ },
"jobs": {
"admin": "",
"available-list": "",
From 78dd14af856d48b0a5795de94c82a62bbafc20eb Mon Sep 17 00:00:00 2001
From: Patrick Fic <>
Date: Mon, 6 Jun 2022 14:32:59 -0700
Subject: [PATCH 18/18] IO-1921 Add shipping to reconciliation.
---
.../job-bills-total/job-bills-total.component.jsx | 1 +
.../job-reconciliation-modal.component.jsx | 3 ++-
.../job-reconciliation-totals.utility.js | 8 +++++++-
server/job/job-totals.js | 7 +++++++
4 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/client/src/components/job-bills-total/job-bills-total.component.jsx b/client/src/components/job-bills-total/job-bills-total.component.jsx
index b1d4f33f5..701ebe1c6 100644
--- a/client/src/components/job-bills-total/job-bills-total.component.jsx
+++ b/client/src/components/job-bills-total/job-bills-total.component.jsx
@@ -86,6 +86,7 @@ export default function JobBillsTotalComponent({
const totalPartsSublet = Dinero(totals.parts.parts.total)
.add(Dinero(totals.parts.sublets.total))
+ .add(Dinero(totals.additional.shipping))
.add(Dinero(totals.additional.towing));
const discrepancy = totalPartsSublet.subtract(billTotals);
diff --git a/client/src/components/job-reconciliation-modal/job-reconciliation-modal.component.jsx b/client/src/components/job-reconciliation-modal/job-reconciliation-modal.component.jsx
index cde3abc6c..8c83a504a 100644
--- a/client/src/components/job-reconciliation-modal/job-reconciliation-modal.component.jsx
+++ b/client/src/components/job-reconciliation-modal/job-reconciliation-modal.component.jsx
@@ -22,7 +22,8 @@ export default function JobReconciliationModalComponent({ job, bills }) {
(j.part_type !== null && j.part_type !== "PAE") ||
(j.line_desc &&
j.line_desc.toLowerCase().includes("towing") &&
- j.lbr_op === "OP13")
+ j.lbr_op === "OP13") ||
+ j.db_ref === "936004" //ADD SHIPPING LINE.
);
return (
diff --git a/client/src/components/job-reconciliation-totals/job-reconciliation-totals.utility.js b/client/src/components/job-reconciliation-totals/job-reconciliation-totals.utility.js
index 119878890..6a6e1a0f6 100644
--- a/client/src/components/job-reconciliation-totals/job-reconciliation-totals.utility.js
+++ b/client/src/components/job-reconciliation-totals/job-reconciliation-totals.utility.js
@@ -1,5 +1,6 @@
import i18next from "i18next";
import _ from "lodash";
+
export const reconcileByAssocLine = (
jobLines,
jobLineState,
@@ -73,7 +74,12 @@ export const reconcileByPrice = (
jobLines.forEach((jl) => {
const matchingBillLineIds = billLines
- .filter((bl) => bl.actual_price === jl.act_price && bl.quantity === jl.part_qty && !jl.removed)
+ .filter(
+ (bl) =>
+ bl.actual_price === jl.act_price &&
+ bl.quantity === jl.part_qty &&
+ !jl.removed
+ )
.map((bl) => bl.id);
if (matchingBillLineIds.length > 1) {
diff --git a/server/job/job-totals.js b/server/job/job-totals.js
index 54b30a9d6..79a2337e1 100644
--- a/server/job/job-totals.js
+++ b/server/job/job-totals.js
@@ -523,6 +523,7 @@ function CalculateAdditional(job) {
additionalCostItems: [],
adjustments: null,
towing: null,
+ shipping: Dinero(),
storage: null,
pvrt: null,
total: null,
@@ -530,6 +531,7 @@ function CalculateAdditional(job) {
ret.towing = Dinero({
amount: Math.round((job.towing_payable || 0) * 100),
});
+
ret.additionalCosts = job.joblines
.filter((jl) => !jl.removed && IsAdditionalCost(jl))
.reduce((acc, val) => {
@@ -537,6 +539,11 @@ function CalculateAdditional(job) {
amount: Math.round((val.act_price || 0) * 100),
}).multiply(val.part_qty || 1);
+ if (val.db_ref === "936004") {
+ //Shipping line IO-1921.
+ ret.shipping = ret.shipping.add(lineValue);
+ }
+
if (val.line_desc.toLowerCase().includes("towing")) {
ret.towing = lineValue;
return acc;