diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel
index ad5db59a6..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
@@ -17213,6 +17239,241 @@
+
+ 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
+
+
+ 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
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+ showall
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+ showavailable
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+
+
+ successes
+
+
+ inserted
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+
+
+
joblines
@@ -29934,6 +30195,27 @@
+
+ inventory
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
jobs
false
@@ -40529,6 +40811,27 @@
+
+ calendarperiod
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
dailyactual
false
@@ -40571,6 +40874,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
@@ -40592,6 +40958,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
@@ -40613,6 +41021,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
@@ -40634,6 +41105,27 @@
+
+ totaloverperiod
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
weeklyactual
false
@@ -40769,37 +41261,6 @@
-
- scoredboard
-
-
- successes
-
-
- updated
- false
-
-
-
-
-
- en-US
- false
-
-
- es-MX
- false
-
-
- fr-CA
- false
-
-
-
-
-
-
-
tech
@@ -42383,6 +42844,27 @@
+
+ inventory
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
jobs
false
@@ -43204,6 +43686,27 @@
+
+ inventory
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
jobs
false
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..a9eb07443 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 && !billEdit) {
+ 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..358bb7f38 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 && (
)}
+ {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..18d996d5a 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,8 @@ 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";
+import { useTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -34,10 +36,16 @@ export function BillEnterModalLinesComponent({
discount,
form,
responsibilityCenters,
+ billEdit,
+ billid,
}) {
const { t } = useTranslation();
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
-
+ const { Simple_Inventory } = useTreatments(
+ ["Simple_Inventory"],
+ {},
+ bodyshop && bodyshop.imexshopid
+ );
const columns = (remove) => {
return [
{
@@ -477,9 +485,22 @@ export function BillEnterModalLinesComponent({
dataIndex: "actions",
render: (text, record) => (
-
+
+
+
+ {() =>
+ Simple_Inventory.treatment === "on" && (
+
+ )
+ }
+
+
),
},
];
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..2a6d76355
--- /dev/null
+++ b/client/src/components/billline-add-inventory/billline-add-inventory.component.jsx
@@ -0,0 +1,147 @@
+import { FileAddFilled } from "@ant-design/icons";
+import { useMutation } from "@apollo/client";
+import { Button, notification, Tooltip } from "antd";
+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,
+ 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))
+});
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(BilllineAddInventory);
+
+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);
+
+ 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: false, //billline.applicable_taxes.local,
+ state: false, //billline.applicable_taxes.state,
+ federal: false, // billline.applicable_taxes.federal,
+ },
+ },
+ ],
+ };
+
+ cm.total = CalculateBillTotal(cm).enteredTotal.getAmount() / 100;
+
+ const insertResult = await insertInventoryLine({
+ variables: {
+ joblineId: billline.joblineid,
+ joblineStatus: bodyshop.md_order_statuses.default_returned,
+ 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.
+ 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"],
+ });
+
+ 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/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/components/header/header.component.jsx b/client/src/components/header/header.component.jsx
index 2a4e9698e..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,7 +206,20 @@ function Header({
>
{t("menus.header.enterbills")}
-
+ {Simple_Inventory.treatment === "on" && (
+ <>
+
+ }
+ >
+
+ {t("menus.header.inventory")}
+
+
+ >
+ )}
+
}>
{t("menus.header.allpayments")}
@@ -216,7 +236,6 @@ function Header({
{t("menus.header.enterpayment")}
-
}>
{t("menus.header.timetickets")}
@@ -235,7 +254,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 } = 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/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/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/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-targets-table/scoreboard-targets-table.util.js b/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js
index 97f1813b9..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
@@ -47,3 +47,15 @@ export const ListOfDaysInCurrentMonth = () => {
days.push(dateEnd.format("YYYY-MM-DD"));
return days;
};
+
+export const ListDaysBetween = ({ 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..063638a47
--- /dev/null
+++ b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.bar.component.jsx
@@ -0,0 +1,81 @@
+import { Card } from "antd";
+import React from "react";
+import { useTranslation } from "react-i18next";
+import { connect } from "react-redux";
+import {
+ Bar,
+ CartesianGrid,
+ ComposedChart,
+ Legend,
+ ResponsiveContainer,
+ Tooltip,
+ XAxis,
+ YAxis,
+} from "recharts";
+import { createStructuredSelector } from "reselect";
+import { selectBodyshop } from "../../redux/user/user.selectors";
+import TimeTicketsDatesSelector from "../ticket-tickets-dates-selector/time-tickets-dates-selector.component";
+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({ data, bodyshop }) {
+ const { t } = useTranslation();
+ return (
+ }
+ >
+
+
+
+
+
+
+
+ {/* */}
+ {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
new file mode 100644
index 000000000..f85982359
--- /dev/null
+++ b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.component.jsx
@@ -0,0 +1,301 @@
+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 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);
+ const { start, end } = searchParams;
+ const startDate = start
+ ? 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.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,
+ });
+
+ 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 (
+
+
+
+
+
+
+
+
+ );
+}
+
+//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.
+
+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..6d2183a54
--- /dev/null
+++ b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx
@@ -0,0 +1,115 @@
+import { Card, Col, Row, Statistic, Table, Typography } 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 (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {t("scoreboard.labels.calendarperiod")}
+
+
+
+
+
+
+
+ );
+}
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/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={
+