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 (