diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel
index afba43fec..81acd4f2e 100644
--- a/bodyshop_translations.babel
+++ b/bodyshop_translations.babel
@@ -8890,6 +8890,27 @@
+
+ tt_enforce_hours_for_tech_console
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
use_fippa
false
@@ -17435,6 +17456,27 @@
+
+ total
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
totals
false
@@ -45189,6 +45231,27 @@
+
+ hoursenteredmorethanavailable
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
diff --git a/client/src/components/production-list-columns/production-list-columns.data.js b/client/src/components/production-list-columns/production-list-columns.data.js
index bd2f15817..7d122200d 100644
--- a/client/src/components/production-list-columns/production-list-columns.data.js
+++ b/client/src/components/production-list-columns/production-list-columns.data.js
@@ -91,11 +91,13 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
b.v_make_desc + b.v_model_desc
),
sortOrder:
- state.sortedInfo.columnKey === "ownr" && state.sortedInfo.order,
+ state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
render: (text, record) => (
- {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
- record.v_model_desc || ""
- } ${record.v_color || ""} ${record.plate_no || ""}`}
+ {`${
+ record.v_model_yr || ""
+ } ${record.v_make_desc || ""} ${record.v_model_desc || ""} ${
+ record.v_color || ""
+ } ${record.plate_no || ""}`}
),
},
{
diff --git a/client/src/components/production-list-table/production-list-table.component.jsx b/client/src/components/production-list-table/production-list-table.component.jsx
index 6595476d5..746d956ab 100644
--- a/client/src/components/production-list-table/production-list-table.component.jsx
+++ b/client/src/components/production-list-table/production-list-table.component.jsx
@@ -81,7 +81,7 @@ export function ProductionListTable({
state,
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
}).find((e) => e.key === k.key),
- width: k.width,
+ width: k.width ?? 100,
};
})) ||
[]
@@ -267,6 +267,8 @@ export function ProductionListTable({
sortOrder:
state.sortedInfo.columnKey === c.key && state.sortedInfo.order,
title: headerItem(c),
+ ellipsis: true,
+ width: c.width ?? 100,
onHeaderCell: (column) => ({
width: column.width,
onResize: handleResize(index),
@@ -276,11 +278,12 @@ export function ProductionListTable({
rowKey="id"
loading={loading}
dataSource={dataSource}
- // scroll={{ x: true }}
+ scroll={{ x: 1000 }}
onChange={handleTableChange}
/>
);
}
+
export default connect(mapStateToProps, null)(ProductionListTable);
diff --git a/client/src/components/production-list-table/production-list-table.resizeable.component.jsx b/client/src/components/production-list-table/production-list-table.resizeable.component.jsx
index 2f1324999..618e9e8cd 100644
--- a/client/src/components/production-list-table/production-list-table.resizeable.component.jsx
+++ b/client/src/components/production-list-table/production-list-table.resizeable.component.jsx
@@ -3,8 +3,26 @@ import { Resizable } from "react-resizable";
export default function ResizableComponent(props) {
const { onResize, width, ...restProps } = props;
+
+ if (!width) {
+ return
| ;
+ }
+
return (
-
+ {
+ e.stopPropagation();
+ }}
+ />
+ }
+ >
|
);
diff --git a/client/src/components/scoreboard-day-stats/scoreboard-day-stats.component.jsx b/client/src/components/scoreboard-day-stats/scoreboard-day-stats.component.jsx
index 36b4a028b..cfd6d945d 100644
--- a/client/src/components/scoreboard-day-stats/scoreboard-day-stats.component.jsx
+++ b/client/src/components/scoreboard-day-stats/scoreboard-day-stats.component.jsx
@@ -1,4 +1,4 @@
-import { Card, Statistic } from "antd";
+import { Card, Divider, Statistic } from "antd";
import moment from "moment";
import React from "react";
import { connect } from "react-redux";
@@ -41,6 +41,9 @@ export function ScoreboardDayStats({ bodyshop, date, entries }) {
label="P"
value={paintHrs.toFixed(1)}
/>
+
+
+
);
}
diff --git a/client/src/components/scoreboard-targets-table/scoreboard-targets-table.component.jsx b/client/src/components/scoreboard-targets-table/scoreboard-targets-table.component.jsx
index 6f22b3b8a..112d441f6 100644
--- a/client/src/components/scoreboard-targets-table/scoreboard-targets-table.component.jsx
+++ b/client/src/components/scoreboard-targets-table/scoreboard-targets-table.component.jsx
@@ -1,5 +1,5 @@
import { CalendarOutlined } from "@ant-design/icons";
-import { Card, Col, Row, Statistic } from "antd";
+import { Card, Col, Divider, Row, Statistic } from "antd";
import _ from "lodash";
import moment from "moment";
import React, { useMemo } from "react";
@@ -177,6 +177,9 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
+
+
+
@@ -184,14 +187,53 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
value={(values.todayPaint + values.todayBody).toFixed(1)}
/>
-
+
+
+
-
-
+
+
+
+
+
+
{
@@ -94,6 +95,7 @@ export default function ScoreboardTimeTickets() {
totalLastMonth: 0,
totalOverPeriod: 0,
actualTotalOverPeriod: 0,
+ totalEffieciencyOverPeriod: 0,
};
}
@@ -221,6 +223,28 @@ export default function ScoreboardTimeTickets() {
ret2.push(r);
});
+
+ // Add total efficiency of employees
+ const totalActualAndProductive = Object.keys(ret.employees)
+ .map((key) => {
+ return { employee_number: key, ...ret.employees[key] };
+ })
+ .reduce(
+ (acc, e) => {
+ return {
+ totalOverPeriod: acc.totalOverPeriod + e.totalOverPeriod,
+ actualTotalOverPeriod:
+ acc.actualTotalOverPeriod + e.actualTotalOverPeriod,
+ };
+ },
+ { totalOverPeriod: 0, actualTotalOverPeriod: 0 }
+ );
+
+ ret.totalEffieciencyOverPeriod =
+ (totalActualAndProductive.totalOverPeriod /
+ totalActualAndProductive.actualTotalOverPeriod) *
+ 100;
+
roundObject(ret);
roundObject(totals);
roundObject(ret2);
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 0b1b9b6bf..e31cec3af 100644
--- a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx
+++ b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx
@@ -62,7 +62,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
key: "efficiencyoverperiod",
render: (text, record) =>
`${(
- (record.totalOverPeriod / (record.actualTotalOverPeriod || .1)) *
+ (record.totalOverPeriod / (record.actualTotalOverPeriod || 0.1)) *
100
).toFixed(1)} %`,
},
@@ -113,6 +113,12 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
value={data.totalOverPeriod}
/>
+
+
+
{t("scoreboard.labels.calendarperiod")}
@@ -121,7 +127,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
+
+
+
e.id === (technician && technician.id)
@@ -129,6 +141,54 @@ export function TechClockOffButton({
required: true,
//message: t("general.validation.required"),
},
+ ({ getFieldValue }) => ({
+ validator(rule, value) {
+ console.log(
+ bodyshop.tt_enforce_hours_for_tech_console
+ );
+ if (!bodyshop.tt_enforce_hours_for_tech_console) {
+ return Promise.resolve();
+ }
+ if (
+ !value ||
+ getFieldValue("cost_center") === null ||
+ !lineTicketData
+ )
+ return Promise.resolve();
+
+ //Check the cost center,
+ const totals = CalculateAllocationsTotals(
+ bodyshop,
+ lineTicketData.joblines,
+ lineTicketData.timetickets,
+ lineTicketData.jobs_by_pk.lbr_adjustments
+ );
+
+ const fieldTypeToCheck =
+ bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
+ ? "mod_lbr_ty"
+ : "cost_center";
+
+ const costCenterDiff =
+ Math.round(
+ totals.find(
+ (total) =>
+ total[fieldTypeToCheck] ===
+ getFieldValue("cost_center")
+ )?.difference * 10
+ ) / 10;
+
+ if (value > costCenterDiff)
+ return Promise.reject(
+ t(
+ "timetickets.validation.hoursenteredmorethanavailable"
+ )
+ );
+ else {
+ return Promise.resolve();
+ }
+ },
+ }),
]}
>
@@ -178,7 +238,11 @@ export function TechClockOffButton({
{!isShiftTicket && (
-
+
)}
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 d74069cd9..5a4a6b5ed 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
@@ -1,4 +1,4 @@
-import { useQuery } from "@apollo/client";
+import { useLazyQuery } from "@apollo/client";
import { Form, Input, InputNumber, Select, Switch } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -14,6 +14,7 @@ import FormDatePicker from "../form-date-picker/form-date-picker.component";
import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
import JobSearchSelect from "../job-search-select/job-search-select.component";
import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component";
+import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
@@ -39,7 +40,11 @@ export function TimeTicketModalComponent({
employeeSelectDisabled,
}) {
const { t } = useTranslation();
-
+ const [loadLineTicketData, { called, loading, data: lineTicketData }] =
+ useLazyQuery(GET_LINE_TICKET_BY_PK, {
+ fetchPolicy: "network-only",
+ nextFetchPolicy: "network-only",
+ });
const CostCenterSelect = ({ emps, value, ...props }) => {
return (
- {() => (
-
- )}
+ {() => {
+ const jobid = form.getFieldValue("jobid");
+ if (
+ (!called && jobid) ||
+ (jobid && lineTicketData?.jobs_by_pk?.id !== jobid && !loading)
+ ) {
+ loadLineTicketData({ variables: { id: jobid } });
+ }
+ return (
+
+ );
+ }}
);
}
-export function LaborAllocationContainer({ jobid }) {
- const { loading, data: lineTicketData } = useQuery(GET_LINE_TICKET_BY_PK, {
- variables: { id: jobid },
- skip: !jobid,
- fetchPolicy: "network-only",
- nextFetchPolicy: "network-only",
- });
+export function LaborAllocationContainer({ jobid, loading, lineTicketData }) {
if (loading) return ;
if (!lineTicketData) return null;
return (
diff --git a/client/src/graphql/bodyshop.queries.js b/client/src/graphql/bodyshop.queries.js
index 956bf9dd4..c934c9514 100644
--- a/client/src/graphql/bodyshop.queries.js
+++ b/client/src/graphql/bodyshop.queries.js
@@ -116,6 +116,7 @@ export const QUERY_BODYSHOP = gql`
md_lost_sale_reasons
md_parts_scan
enforce_conversion_category
+ tt_enforce_hours_for_tech_console
employees {
user_email
id
@@ -230,6 +231,7 @@ export const UPDATE_SHOP = gql`
md_lost_sale_reasons
md_parts_scan
enforce_conversion_category
+ tt_enforce_hours_for_tech_console
employees {
id
first_name
diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js
index dce7aff66..a6273edc4 100644
--- a/client/src/graphql/jobs.queries.js
+++ b/client/src/graphql/jobs.queries.js
@@ -284,6 +284,7 @@ export const QUERY_JOBS_IN_PRODUCTION = gql`
clm_no
v_make_desc
v_color
+ vehicleid
plate_no
actual_in
scheduled_completion
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index 41fdaa016..d4cd36bb5 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -544,6 +544,7 @@
"target_touchtime": "Target Touch Time",
"timezone": "Timezone",
"tt_allow_post_to_invoiced": "Allow Time Tickets to be posted to Invoiced & Exported Jobs",
+ "tt_enforce_hours_for_tech_console": "Restrict Claimable hours from Tech Console",
"use_fippa": "Use FIPPA for Names on Generated Documents?",
"uselocalmediaserver": "Use Local Media Server?",
"website": "Website",
@@ -1076,6 +1077,7 @@
"sunday": "Sunday",
"text": "Text",
"thursday": "Thursday",
+ "total": "Total",
"totals": "Totals",
"tuesday": "Tuesday",
"unknown": "Unknown",
@@ -2675,7 +2677,8 @@
},
"validation": {
"clockoffmustbeafterclockon": "Clock off time must be the same or after clock in time.",
- "clockoffwithoutclockon": "Clock off time cannot be set without a clock in time."
+ "clockoffwithoutclockon": "Clock off time cannot be set without a clock in time.",
+ "hoursenteredmorethanavailable": "The number of hours entered is more than what is available for this cost center."
}
},
"titles": {
diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json
index 11e2e5b35..2940fbffc 100644
--- a/client/src/translations/es/common.json
+++ b/client/src/translations/es/common.json
@@ -544,6 +544,7 @@
"target_touchtime": "",
"timezone": "",
"tt_allow_post_to_invoiced": "",
+ "tt_enforce_hours_for_tech_console": "",
"use_fippa": "",
"uselocalmediaserver": "",
"website": "",
@@ -1076,6 +1077,7 @@
"sunday": "",
"text": "",
"thursday": "",
+ "total": "",
"totals": "",
"tuesday": "",
"unknown": "Desconocido",
@@ -2675,7 +2677,8 @@
},
"validation": {
"clockoffmustbeafterclockon": "",
- "clockoffwithoutclockon": ""
+ "clockoffwithoutclockon": "",
+ "hoursenteredmorethanavailable": ""
}
},
"titles": {
diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json
index 74ca00248..d04b4ab86 100644
--- a/client/src/translations/fr/common.json
+++ b/client/src/translations/fr/common.json
@@ -544,6 +544,7 @@
"target_touchtime": "",
"timezone": "",
"tt_allow_post_to_invoiced": "",
+ "tt_enforce_hours_for_tech_console": "",
"use_fippa": "",
"uselocalmediaserver": "",
"website": "",
@@ -1076,6 +1077,7 @@
"sunday": "",
"text": "",
"thursday": "",
+ "total": "",
"totals": "",
"tuesday": "",
"unknown": "Inconnu",
@@ -2675,7 +2677,8 @@
},
"validation": {
"clockoffmustbeafterclockon": "",
- "clockoffwithoutclockon": ""
+ "clockoffwithoutclockon": "",
+ "hoursenteredmorethanavailable": ""
}
},
"titles": {
diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml
index 0055e4b7a..a456a9a0f 100644
--- a/hasura/metadata/tables.yaml
+++ b/hasura/metadata/tables.yaml
@@ -935,6 +935,7 @@
- textid
- timezone
- tt_allow_post_to_invoiced
+ - tt_enforce_hours_for_tech_console
- updated_at
- use_fippa
- uselocalmediaserver
@@ -1023,6 +1024,7 @@
- target_touchtime
- timezone
- tt_allow_post_to_invoiced
+ - tt_enforce_hours_for_tech_console
- updated_at
- use_fippa
- uselocalmediaserver
@@ -2391,30 +2393,32 @@
- active:
_eq: true
columns:
- - id
- - created_at
- - updated_at
- - jobid
- billid
+ - bodyshopid
+ - created_at
+ - id
+ - jobid
+ - message
+ - metadata
- paymentid
- successful
- - message
- - bodyshopid
+ - updated_at
- useremail
select_permissions:
- role: user
permission:
columns:
- - successful
- - message
- - useremail
- - created_at
- - updated_at
- billid
- bodyshopid
+ - created_at
- id
- jobid
+ - message
+ - metadata
- paymentid
+ - successful
+ - updated_at
+ - useremail
filter:
bodyshop:
associations:
diff --git a/hasura/migrations/1679343513178_alter_table_public_joblines_add_column_act_price_before_ppc/down.sql b/hasura/migrations/1679343513178_alter_table_public_joblines_add_column_act_price_before_ppc/down.sql
new file mode 100644
index 000000000..641b06fc4
--- /dev/null
+++ b/hasura/migrations/1679343513178_alter_table_public_joblines_add_column_act_price_before_ppc/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"."joblines" add column "act_price_before_ppc" numeric
+-- null;
diff --git a/hasura/migrations/1679343513178_alter_table_public_joblines_add_column_act_price_before_ppc/up.sql b/hasura/migrations/1679343513178_alter_table_public_joblines_add_column_act_price_before_ppc/up.sql
new file mode 100644
index 000000000..c817ce67a
--- /dev/null
+++ b/hasura/migrations/1679343513178_alter_table_public_joblines_add_column_act_price_before_ppc/up.sql
@@ -0,0 +1,2 @@
+alter table "public"."joblines" add column "act_price_before_ppc" numeric
+ null;
diff --git a/hasura/migrations/1681329377686_alter_table_public_exportlog_add_column_metadata/down.sql b/hasura/migrations/1681329377686_alter_table_public_exportlog_add_column_metadata/down.sql
new file mode 100644
index 000000000..3c175e747
--- /dev/null
+++ b/hasura/migrations/1681329377686_alter_table_public_exportlog_add_column_metadata/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"."exportlog" add column "metadata" jsonb
+-- null;
diff --git a/hasura/migrations/1681329377686_alter_table_public_exportlog_add_column_metadata/up.sql b/hasura/migrations/1681329377686_alter_table_public_exportlog_add_column_metadata/up.sql
new file mode 100644
index 000000000..621b8481c
--- /dev/null
+++ b/hasura/migrations/1681329377686_alter_table_public_exportlog_add_column_metadata/up.sql
@@ -0,0 +1,2 @@
+alter table "public"."exportlog" add column "metadata" jsonb
+ null;
diff --git a/hasura/migrations/1681331289298_alter_table_public_bodyshops_add_column_tt_enforce_hours_for_tech_console/down.sql b/hasura/migrations/1681331289298_alter_table_public_bodyshops_add_column_tt_enforce_hours_for_tech_console/down.sql
new file mode 100644
index 000000000..53e7608da
--- /dev/null
+++ b/hasura/migrations/1681331289298_alter_table_public_bodyshops_add_column_tt_enforce_hours_for_tech_console/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"."bodyshops" add column "tt_enforce_hours_for_tech_console" boolean
+-- not null default 'false';
diff --git a/hasura/migrations/1681331289298_alter_table_public_bodyshops_add_column_tt_enforce_hours_for_tech_console/up.sql b/hasura/migrations/1681331289298_alter_table_public_bodyshops_add_column_tt_enforce_hours_for_tech_console/up.sql
new file mode 100644
index 000000000..86e0f5973
--- /dev/null
+++ b/hasura/migrations/1681331289298_alter_table_public_bodyshops_add_column_tt_enforce_hours_for_tech_console/up.sql
@@ -0,0 +1,2 @@
+alter table "public"."bodyshops" add column "tt_enforce_hours_for_tech_console" boolean
+ not null default 'false';
diff --git a/server/accounting/pbs/pbs-job-export.js b/server/accounting/pbs/pbs-job-export.js
index ee0b3c8a8..cfbe71a19 100644
--- a/server/accounting/pbs/pbs-job-export.js
+++ b/server/accounting/pbs/pbs-job-export.js
@@ -249,10 +249,10 @@ async function QueryCustomersFromDms(socket) {
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
//ContactId: "00000000000000000000000000000000",
// ContactCode: socket.JobData.owner.accountingid,
- FirstName: socket.JobData.ownr_co_nm
+ FirstName: socket.JobData.ownr_fn,
+ LastName: socket.JobData.ownr_co_nm
? socket.JobData.ownr_co_nm
- : socket.JobData.ownr_fn,
- LastName: socket.JobData.ownr_ln,
+ : socket.JobData.ownr_ln,
PhoneNumber: socket.JobData.ownr_ph1,
EmailAddress: socket.JobData.ownr_ea,
// ModifiedSince: "0001-01-01T00:00:00.0000000Z",
diff --git a/server/cdk/cdk-job-export.js b/server/cdk/cdk-job-export.js
index d5fb6b295..3987d21bc 100644
--- a/server/cdk/cdk-job-export.js
+++ b/server/cdk/cdk-job-export.js
@@ -224,6 +224,7 @@ async function CdkSelectedCustomer(socket, selectedCustomerId) {
} finally {
//Ensure we always insert logEvents
//GQL to insert logevents.
+
CdkBase.createLogEvent(
socket,
"DEBUG",
@@ -1213,6 +1214,7 @@ async function GenerateTransWips(socket) {
wips.push(item);
});
+ socket.transWips = wips;
return wips;
}
@@ -1388,6 +1390,7 @@ async function MarkJobExported(socket, jobid) {
jobid: jobid,
successful: true,
useremail: socket.user.email,
+ metadata: socket.transWips,
},
bill: {
exported: true,