Compare commits

...

59 Commits

Author SHA1 Message Date
Allan Carr
fc16190ec4 Merged in feature/IO-2470-CC-Year-b-Make-UI (pull request #1058)
IO-2470-CC-Year-b-Make-UI
2023-11-17 19:08:43 +00:00
Allan Carr
cf7f4f1b46 IO-2470-CC-Year-b-Make-UI 2023-11-17 11:08:17 -08:00
Dave Richer
ba95a636cf Merged in feature/IO-2331-Remove-Required-CC-Fields (pull request #1055)
IO-2331 Remove required CC fields Next Service KMS and Next Service Date
2023-11-15 22:13:56 +00:00
Dave Richer
2478fedf1a IO-2331 Remove required CC fields Next Service KMS and Next Service Date 2023-11-15 17:10:52 -05:00
Dave Richer
bc6c889afc Merged in feature/io-2283-Add-Plate-Number-To-Global-Search (pull request #1053)
IO-2283 - Add Plate Number to global search

Approved-by: Patrick Fic
2023-11-15 21:42:09 +00:00
Dave Richer
33bcf250d0 Merged release/2023-11-17 into feature/io-2283-Add-Plate-Number-To-Global-Search 2023-11-15 21:32:04 +00:00
Dave Richer
bad32e069b IO-2283 - Add Plate Number to global search 2023-11-15 16:29:04 -05:00
Allan Carr
9acf20d4f3 Merged in feature/IO-1016-Registration-Expiry-for-CCs (pull request #1052)
IO-1016 Remove required rule for Registration Expiry date
2023-11-15 18:36:53 +00:00
Allan Carr
26f1ee0d89 Merged in feature/IO-2453-Clear-Bin-Location (pull request #1051)
IO-2453 Clear Bin Location
2023-11-15 18:36:36 +00:00
Allan Carr
9c86be8250 Merged in feature/IO-2448-CC-Search-by-Plate (pull request #1050)
IO-2448 Search by C/C Plate number
2023-11-15 18:36:20 +00:00
Allan Carr
4a06f9a686 Merged in feature/IO-2451-Non-Converted-Jobs-in-List (pull request #1049)
IO-2451 Non Converted Jobs in Lists
2023-11-15 18:36:04 +00:00
Allan Carr
98700f54b4 IO-1016 Remove required rule for Registration Expiry date 2023-11-14 17:55:57 -08:00
Patrick Fic
9a37cb5cb8 IO-1532 Re-enable hasura event. 2023-11-14 13:53:28 -08:00
Allan Carr
673d0bb7c5 IO-2453 Clear Bin Location 2023-11-14 12:15:09 -08:00
Allan Carr
04c7bc445b IO-2448 Search by C/C Plate number 2023-11-10 16:32:38 -08:00
Allan Carr
41782fe120 IO-2451 Non Converted Jobs in Lists
These lists should be converted jobs only
2023-11-10 16:01:17 -08:00
Patrick Fic
4e8e25a336 Merged in release/2023-11-10 (pull request #1048)
Release/2023 11 10
2023-11-10 19:21:15 +00:00
Patrick Fic
3c2d3156cb Merge branch 'release/2023-11-10' into test 2023-11-09 13:39:46 -08:00
Patrick Fic
0dac15391f Merge branch 'feature/IO-2445-block-types' into release/2023-11-10 2023-11-09 13:39:21 -08:00
Allan Carr
19c0553746 Merged in release/2023-11-10 (pull request #1047)
Release/2023 11 10
2023-11-09 21:23:41 +00:00
Patrick Fic
5f1475d2ec Merge branch 'feature/IO-2444-single-device-login' into release/2023-11-10 2023-11-08 12:08:32 -08:00
Patrick Fic
84f4d5956a IO-2444 update fingerprint JS and re-enable using shop flag. 2023-11-08 12:08:21 -08:00
Allan Carr
4330ddd926 Merged in feature/IO-2438-All-Employee-Scoreboard-Summary (pull request #1046)
IO-2438 All Employee Scoreboard
2023-11-08 17:15:37 +00:00
Allan Carr
0711210512 IO-2438 All Employee Scoreboard
A-G Autobody request
2023-11-08 09:13:16 -08:00
Patrick Fic
458ec76835 Merge branch 'feature/IO-2437-CDK-make-override' into release/2023-11-10 2023-11-07 12:36:40 -08:00
Patrick Fic
682ea860fb IO-2437 override CDK make model on update. 2023-11-07 12:36:30 -08:00
Patrick Fic
99977934e7 IO-2429 Remove CIECA information on duplication. 2023-11-07 09:24:21 -08:00
Patrick Fic
3e05b21c90 IO-2426 Add FCM Cache update for conversation aggregate count. 2023-11-07 08:55:59 -08:00
Patrick Fic
4e1dd52bea IO-2332 filter insurance company name on convert. 2023-11-06 15:07:26 -08:00
Patrick Fic
f6bcc743d8 IO-2330 Remove phone validation for vendor sav.e 2023-11-06 15:04:01 -08:00
Allan Carr
d747594e39 Merged in release/2023-11-03 (pull request #1039)
Release/2023 11 03
2023-11-02 22:47:29 +00:00
Allan Carr
a33bfedbb8 Merged in feature/2023-10-27 (pull request #1034)
Feature/2023 10 27
2023-10-27 15:24:30 +00:00
Allan Carr
5da34cbeac Merged in release/2023-10-20 (pull request #1027)
Release/2023 10 20

Approved-by: Patrick Fic
2023-10-20 15:37:26 +00:00
Allan Carr
34b4baac3d Merged in 2023-09-29 (pull request #1006)
IO-1559 Adjust count object to new field tag
2023-10-12 21:53:29 +00:00
Allan Carr
6b4709b76b Merged in 2023-09-29 (pull request #1003)
2023 09 29

Approved-by: Patrick Fic
2023-10-12 19:41:37 +00:00
Allan Carr
4513acc640 Merged in release/2023-09-23 (pull request #991)
IO-2408 Remove unneeded library
2023-09-22 18:50:44 +00:00
Allan Carr
f1aa7944a3 Merged in release/2023-09-23 (pull request #989)
IO-2408 Use bodyshop.timezone instead of UTCOffset in Speedprint
2023-09-22 18:33:50 +00:00
Allan Carr
4313cf471f Merged in release/2023-09-23 (pull request #987)
Release/2023 09 23
2023-09-22 15:27:51 +00:00
Patrick Fic
171c0b2b5a Merged in release/2023-09-15 (pull request #979)
Refactor payments for intellipay.
2023-09-15 17:39:16 +00:00
Allan Carr
fd4820336f Merged in release/2023-09-15 (pull request #977)
IO-2395 Adjust Payment Number Label
2023-09-15 16:14:40 +00:00
Allan Carr
46840266ee Merged in release/2023-09-15 (pull request #975)
Release/2023 09 15
2023-09-15 16:05:02 +00:00
Allan Carr
82a0d287d6 Merged in release/2023-09-01 (pull request #970)
IO-2391 Remote IP comment out till proxy for X-Forwarded-For can be figured out from AWS Load balancer
2023-09-08 20:17:48 +00:00
Allan Carr
4de604ef7c Merged in release/2023-09-01 (pull request #968)
IO-2368 Move Cache update to function and just pass in keys array
2023-09-08 17:18:06 +00:00
Allan Carr
ec2c26ca69 Merged in release/2023-09-01 (pull request #962)
Release/2023 09 01

Approved-by: Patrick Fic
2023-09-08 01:35:06 +00:00
Allan Carr
026ce853e2 Merged in release/2023-08-25 (pull request #952)
Release/2023 08 25
2023-08-25 18:09:40 +00:00
Patrick Fic
720b7f891b Merged in release/2023-08-25 (pull request #951)
Release/2023 08 25
2023-08-25 16:46:10 +00:00
Allan Carr
b5332458ec Merged in release/2023-08-18 (pull request #936)
Release/2023 08 18
2023-08-18 21:00:11 +00:00
Allan Carr
3c7b16412b Merged in release/2023-08-11 (pull request #929)
IO-2325 Shift Clock Created By
2023-08-11 16:05:39 +00:00
Allan Carr
a454c57bc9 Merged in release/2023-08-11 (pull request #927)
Release/2023 08 11
2023-08-10 19:07:49 +00:00
Allan Carr
86ff7cbc69 Merged in release/2023-08-04 (pull request #919)
Release/2023 08 04
2023-08-04 17:36:26 +00:00
Allan Carr
fbfdbc903c Merged in release/2023-08-04 (pull request #917)
IO-2370 Work In Progress Jobs
2023-08-02 23:38:22 +00:00
Allan Carr
9e64fdc985 Merged in release/2023-07-28 (pull request #912)
Release/2023 07 28
2023-07-28 20:01:26 +00:00
Allan Carr
d7c23297ab Merged in release/2023-07-28 (pull request #907)
Release/2023 07 28
2023-07-26 23:45:22 +00:00
Allan Carr
f6e095e0a6 Merged in release/2023-07-28 (pull request #905)
IO-2356 Auto CC for Parts Return
2023-07-24 22:44:46 +00:00
Allan Carr
d7ec2e717c Merged in release/2023-07-28 (pull request #903)
Release/2023 07 28
2023-07-24 20:02:19 +00:00
Allan Carr
35fd74d3fe Merged in release/2023-07-21 (pull request #900)
Release/2023 07 21
2023-07-21 17:09:31 +00:00
Patrick Fic
8325e2d9cf Merged in release/2023-07-14 (pull request #892)
IO-2349 Change control number for AP to allow RO.
2023-07-14 23:14:34 +00:00
Allan Carr
cc3c1242f5 Merged in release/2023-07-14 (pull request #889)
IO-2170 Job Status change on Job Clock Out
2023-07-12 17:40:32 +00:00
Patrick Fic
b2f4a5539c Merged in feature/IO-2349-pbs-ap-cogs-wip (pull request #886)
IO-2349 Allow PBS AP Posting to WIP
2023-07-12 16:39:01 +00:00
36 changed files with 28522 additions and 2379 deletions

4
.gitignore vendored
View File

@@ -117,4 +117,6 @@ logs/oAuthClient-log.log
.node-persist/**
/*.env.*
/*.env.*
.idea/*
.idea

View File

@@ -24138,6 +24138,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>dms_model_override</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>dms_unsold</name>
<definition_loaded>false</definition_loaded>

24522
client/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@
"@apollo/client": "^3.7.9",
"@asseinfo/react-kanban": "^2.2.0",
"@craco/craco": "^7.0.0",
"@fingerprintjs/fingerprintjs": "^3.3.3",
"@fingerprintjs/fingerprintjs": "^3.4.2",
"@jsreport/browser-client": "^3.1.0",
"@sentry/react": "^7.40.0",
"@sentry/tracing": "^7.40.0",

View File

@@ -2,10 +2,10 @@ import { useQuery } from "@apollo/client";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import ContractJobsComponent from "./contract-jobs.component";
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -15,6 +15,7 @@ export function ContractJobsContainer({ selectedJobState, bodyshop }) {
const { loading, error, data } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
variables: {
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open"],
isConverted: true,
},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",

View File

@@ -4,14 +4,14 @@ import queryString from "query-string";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link, useHistory, useLocation } from "react-router-dom";
import { setModalContext } from "../../redux/modals/modals.actions";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import ContractsFindModalContainer from "../contracts-find-modal/contracts-find-modal.container";
import { setModalContext } from "../../redux/modals/modals.actions";
import moment from "moment";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import moment from "moment";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({

View File

@@ -34,6 +34,18 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
{/* <FormFieldsChanged form={form} /> */}
<LayoutFormRow header={t("courtesycars.labels.vehicle")}>
<Form.Item
label={t("courtesycars.fields.year")}
name="year"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("courtesycars.fields.make")}
name="make"
@@ -58,18 +70,6 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
>
<Input />
</Form.Item>
<Form.Item
label={t("courtesycars.fields.year")}
name="year"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("courtesycars.fields.plate")}
name="plate"
@@ -216,12 +216,6 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
<Form.Item
label={t("courtesycars.fields.nextservicekm")}
name="nextservicekm"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber />
</Form.Item>
@@ -229,12 +223,6 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
<Form.Item
label={t("courtesycars.fields.nextservicedate")}
name="nextservicedate"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<FormDatePicker />
</Form.Item>
@@ -283,12 +271,6 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
<Form.Item
label={t("courtesycars.fields.registrationexpires")}
name="registrationexpires"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<FormDatePicker />
</Form.Item>
@@ -330,9 +312,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
<FormDatePicker />
</Form.Item>
<Form.Item
shouldUpdate={(p, c) =>
p.insuranceexpires !== c.insuranceexpires
}
shouldUpdate={(p, c) => p.insuranceexpires !== c.insuranceexpires}
>
{() => {
const expires = form.getFieldValue("insuranceexpires");

View File

@@ -174,6 +174,7 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
(c.year || "").toLowerCase().includes(searchText.toLowerCase()) ||
(c.make || "").toLowerCase().includes(searchText.toLowerCase()) ||
(c.model || "").toLowerCase().includes(searchText.toLowerCase()) ||
(c.plate || "").toLowerCase().includes(searchText.toLowerCase()) ||
(t(c.status) || "").toLowerCase().includes(searchText.toLowerCase())
)
: courtesycars;

View File

@@ -191,6 +191,13 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
>
<Switch />
</Form.Item>
<Form.Item
name="dms_model_override"
label={t("jobs.fields.dms.dms_model_override")}
initialValue={false}
>
<Switch />
</Form.Item>
</Space>
</div>
)}

View File

@@ -54,6 +54,7 @@ export default function GlobalSearchOs() {
job.v_make_desc || ""
} ${job.v_model_desc || ""}`}</span>
<span>{`${job.clm_no || ""}`}</span>
<span>{`${job.plate_no || ""}`}</span>
</Space>
</Link>
),

View File

@@ -1,6 +1,6 @@
import { useMutation } from "@apollo/client";
import { notification, Select } from "antd";
import React, { useEffect, useState } from "react";
import { useMutation } from "@apollo/client";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -56,8 +56,10 @@ export function JobLineLocationPopup({ bodyshop, jobline, disabled }) {
<LoadingSpinner loading={loading}>
<Select
autoFocus
allowClear
dropdownMatchSelectWidth={100}
value={location}
onClear={() => setLocation(null)}
onSelect={handleChange}
onBlur={handleSave}
>

View File

@@ -108,7 +108,7 @@ export function JobsConvertButton({
},
]}
>
<Select>
<Select showSearch>
{bodyshop.md_ins_cos.map((s, i) => (
<Select.Option key={i} value={s.name}>
{s.name}

View File

@@ -27,6 +27,8 @@ export default async function DuplicateJob(
delete existingJob.id;
delete existingJob.createdat;
delete existingJob.updatedat;
delete existingJob.cieca_stl;
delete existingJob.cieca_ttl;
const newJob = {
...existingJob,
@@ -81,6 +83,8 @@ export async function CreateIouForJob(
delete existingJob.id;
delete existingJob.createdat;
delete existingJob.updatedat;
delete existingJob.cieca_stl;
delete existingJob.cieca_ttl;
const newJob = {
...existingJob,

View File

@@ -1,8 +1,8 @@
import {
BranchesOutlined,
ExclamationCircleFilled,
PauseCircleOutlined,
SyncOutlined,
BranchesOutlined,
} from "@ant-design/icons";
import { useQuery } from "@apollo/client";
import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd";
@@ -14,8 +14,8 @@ import { Link, useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { onlyUnique } from "../../utils/arrayHelper";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { onlyUnique } from "../../utils/arrayHelper";
import { alphaSort } from "../../utils/sorters";
import AlertComponent from "../alert/alert.component";
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
@@ -46,6 +46,7 @@ export function JobsReadyList({ bodyshop }) {
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
variables: {
statuses: readyStatuses,
isConverted: true,
},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",

View File

@@ -8,18 +8,45 @@ export const CalculateWorkingDaysThisMonth = () => {
return moment().endOf("month").businessDaysIntoMonth();
};
export const CalculateWorkingDaysInPeriod = (start, end) => {
return moment(start).businessDiff(moment(end));
};
export const CalculateWorkingDaysAsOfToday = () => {
return moment().businessDaysIntoMonth();
};
export const CalculateWorkingDaysLastMonth = () => {
return moment().subtract(1, "month").endOf("month").businessDaysIntoMonth();
};
export const WeeklyTargetHrs = (dailyTargetHrs, bodyshop) => {
return dailyTargetHrs * 5;
return (
dailyTargetHrs *
CalculateWorkingDaysInPeriod(
moment().startOf("week"),
moment().endOf("week")
)
);
};
export const WeeklyTargetHrsInPeriod = (
dailyTargetHrs,
start,
end,
bodyshop
) => {
return dailyTargetHrs * CalculateWorkingDaysInPeriod(start, end);
};
export const MonthlyTargetHrs = (dailyTargetHrs, bodyshop) => {
return dailyTargetHrs * CalculateWorkingDaysThisMonth();
};
export const LastMonthTargetHrs = (dailyTargetHrs, bodyshop) => {
return dailyTargetHrs * CalculateWorkingDaysLastMonth();
};
export const AsOfTodayTargetHrs = (dailyTargetHrs, bodyshop) => {
return dailyTargetHrs * CalculateWorkingDaysAsOfToday();
};

View File

@@ -0,0 +1,26 @@
const CustomTooltip = ({ active, payload, label }) => {
if (active && payload && payload.length) {
return (
<div
style={{
backgroundColor: "white",
border: "1px solid gray",
padding: "0.5rem",
}}
>
<p style={{ margin: "0" }}>{label}</p>
{payload.map((data, index) => {
return (
<p style={{ margin: "10px 0", color: data.color }} key={index}>{`${
data.name
} : ${data.value.toFixed(1)}`}</p>
);
})}
</div>
);
}
return null;
};
export default CustomTooltip;

View File

@@ -0,0 +1,54 @@
import { Card } from "antd";
import React from "react";
import {
Area,
CartesianGrid,
ComposedChart,
Legend,
Line,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from "recharts";
import CustomTooltip from "./chart-custom-tooltip";
const graphProps = {
strokeWidth: 3,
};
export default function ScoreboardTimeTicketsChart({ data, chartTitle }) {
return (
<Card title={chartTitle}>
<ResponsiveContainer width="100%" height={275}>
<ComposedChart
data={data}
margin={{ top: 20, right: 20, bottom: 20, left: 20 }}
>
<CartesianGrid stroke="#f5f5f5" />
<XAxis dataKey="date" strokeWidth={graphProps.strokeWidth} />
<YAxis yAxisId="left" strokeWidth={graphProps.strokeWidth} />
<Tooltip content={<CustomTooltip />} />
<Legend />
<Line
name="Target Hours"
type="monotone"
dataKey="accTargetHrs"
stroke="#ff7300"
yAxisId="left"
strokeWidth={graphProps.strokeWidth}
/>
<Area
type="monotone"
name="MTD Hours"
dataKey="accHrs"
fill="lightblue"
stroke="blue"
yAxisId="left"
/>
</ComposedChart>
</ResponsiveContainer>
</Card>
);
}

View File

@@ -0,0 +1,399 @@
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 { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { QUERY_TIME_TICKETS_IN_RANGE_SB } from "../../graphql/timetickets.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
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 ScoreboardTimeTicketsChart from "./scoreboard-timetickets.chart.component";
import ScoreboardTicketsStats from "./scoreboard-timetickets.stats.component";
import ScoreboardTimeticketsTargetsTable from "./scoreboard-timetickets.targets-table.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ScoreboardTimeTicketsStats);
export function ScoreboardTimeTicketsStats({ bodyshop }) {
const { t } = useTranslation();
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 endOfPriorWeek = moment().subtract(2, "week").endOf("week");
const startOfPriorWeek = moment().subtract(2, "week").startOf("week");
const allDates = [
endOfThisMonth,
startofthisMonth,
endOfLastmonth,
startOfLastmonth,
endOfThisWeek,
startOfThisWeek,
endOfLastWeek,
startOfLastWeek,
endOfPriorWeek,
startOfPriorWeek,
];
const start = moment.min(allDates);
const end = moment.max(allDates);
return {
start,
end,
endOfThisMonth,
startofthisMonth,
endOfLastmonth,
startOfLastmonth,
endOfThisWeek,
startOfThisWeek,
endOfLastWeek,
startOfLastWeek,
endOfPriorWeek,
startOfPriorWeek,
};
}, []);
const { loading, error, data } = useQuery(QUERY_TIME_TICKETS_IN_RANGE_SB, {
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,
totalThisWeekLAB: 0,
totalThisWeekLAR: 0,
totalLastWeek: 0,
totalLastWeekLAB: 0,
totalLastWeekLAR: 0,
totalPriorWeek: 0,
totalPriorWeekLAB: 0,
totalPriorWeekLAR: 0,
totalThisMonth: 0,
totalThisMonthLAB: 0,
totalThisMonthLAR: 0,
totalLastMonth: 0,
totalLastMonthLAB: 0,
totalLastMonthLAR: 0,
actualTotalOverPeriod: 0,
actualTotalOverPeriodLAB: 0,
actualTotalOverPeriodLAR: 0,
totalEffieciencyOverPeriod: 0,
totalEffieciencyOverPeriodLAB: 0,
totalEffieciencyOverPeriodLAR: 0,
seperatedThisWeek: {
sunday: {
total: 0,
lab: 0,
lar: 0,
},
monday: {
total: 0,
lab: 0,
lar: 0,
},
tuesday: {
total: 0,
lab: 0,
lar: 0,
},
wednesday: {
total: 0,
lab: 0,
lar: 0,
},
thursday: {
total: 0,
lab: 0,
lar: 0,
},
friday: {
total: 0,
lab: 0,
lar: 0,
},
saturday: {
total: 0,
lab: 0,
lar: 0,
},
},
};
data.fixedperiod.forEach((ticket) => {
const ticketDate = moment(ticket.date);
if (
ticketDate.isBetween(
fixedPeriods.startOfThisWeek,
fixedPeriods.endOfThisWeek,
undefined,
"[]"
)
) {
ret.totalThisWeek = ret.totalThisWeek + ticket.productivehrs;
if (ticket.ciecacode !== "LAR")
ret.totalThisWeekLAB = ret.totalThisWeekLAB + ticket.productivehrs;
if (ticket.ciecacode === "LAR")
ret.totalThisWeekLAR = ret.totalThisWeekLAR + ticket.productivehrs;
//Seperate out to Day of Week
ret.seperatedThisWeek[
moment(ticket.date).format("dddd").toLowerCase()
].total =
ret.seperatedThisWeek[
moment(ticket.date).format("dddd").toLowerCase()
].total + ticket.productivehrs;
if (ticket.ciecacode !== "LAR")
ret.seperatedThisWeek[
moment(ticket.date).format("dddd").toLowerCase()
].lab =
ret.seperatedThisWeek[
moment(ticket.date).format("dddd").toLowerCase()
].lab + ticket.productivehrs;
if (ticket.ciecacode === "LAR")
ret.seperatedThisWeek[
moment(ticket.date).format("dddd").toLowerCase()
].lar =
ret.seperatedThisWeek[
moment(ticket.date).format("dddd").toLowerCase()
].lar + ticket.productivehrs;
} else if (
ticketDate.isBetween(
fixedPeriods.startOfLastWeek,
fixedPeriods.endOfLastWeek,
undefined,
"[]"
)
) {
ret.totalLastWeek = ret.totalLastWeek + ticket.productivehrs;
if (ticket.ciecacode !== "LAR")
ret.totalLastWeekLAB = ret.totalLastWeekLAB + ticket.productivehrs;
if (ticket.ciecacode === "LAR")
ret.totalLastWeekLAR = ret.totalLastWeekLAR + ticket.productivehrs;
} else if (
ticketDate.isBetween(
fixedPeriods.startOfPriorWeek,
fixedPeriods.endOfPriorWeek,
undefined,
"[]"
)
) {
ret.totalPriorWeek = ret.totalPriorWeek + ticket.productivehrs;
if (ticket.ciecacode !== "LAR")
ret.totalPriorWeekLAB = ret.totalPriorWeekLAB + ticket.productivehrs;
if (ticket.ciecacode === "LAR")
ret.totalPriorWeekLAR = ret.totalPriorWeekLAR + ticket.productivehrs;
}
if (
ticketDate.isBetween(
fixedPeriods.startofthisMonth,
fixedPeriods.endOfThisMonth,
undefined,
"[]"
)
) {
ret.totalThisMonth = ret.totalThisMonth + ticket.productivehrs;
ret.actualTotalOverPeriod =
ret.actualTotalOverPeriod + (ticket.actualhrs || 0);
if (ticket.ciecacode !== "LAR") {
ret.totalThisMonthLAB = ret.totalThisMonthLAB + ticket.productivehrs;
ret.actualTotalOverPeriodLAB =
ret.actualTotalOverPeriodLAB + (ticket.actualhrs || 0);
}
if (ticket.ciecacode === "LAR") {
ret.totalThisMonthLAR = ret.totalThisMonthLAR + ticket.productivehrs;
ret.actualTotalOverPeriodLAR =
ret.actualTotalOverPeriodLAR + (ticket.actualhrs || 0);
}
} else if (
ticketDate.isBetween(
fixedPeriods.startOfLastmonth,
fixedPeriods.endOfLastmonth,
undefined,
"[]"
)
) {
ret.totalLastMonth = ret.totalLastMonth + ticket.productivehrs;
if (ticket.ciecacode !== "LAR")
ret.totalLastMonthLAB = ret.totalLastMonthLAB + ticket.productivehrs;
if (ticket.ciecacode === "LAR")
ret.totalLastMonthLAR = ret.totalLastMonthLAR + ticket.productivehrs;
}
});
ret.totalEffieciencyOverPeriod = ret.actualTotalOverPeriod
? (ret.totalThisMonth / ret.actualTotalOverPeriod) * 100
: 0;
ret.totalEffieciencyOverPeriodLAB = ret.actualTotalOverPeriodLAB
? (ret.totalThisMonthLAB / ret.actualTotalOverPeriodLAB) * 100
: 0;
ret.totalEffieciencyOverPeriodLAR = ret.actualTotalOverPeriodLAR
? (ret.totalThisMonthLAR / ret.actualTotalOverPeriodLAR) * 100
: 0;
roundObject(ret);
const ticketsGroupedByDate = _.groupBy(data.timetickets, "date");
const listOfDays = Utils.ListOfDaysInCurrentMonth();
const combinedData = [],
labData = [],
larData = [];
var acc_comb = 0;
var acc_lab = 0;
var acc_lar = 0;
listOfDays.forEach((day) => {
const r = {
date: moment(day).format("MM/DD"),
actualhrs: 0,
productivehrs: 0,
};
const combined = {
accTargetHrs: _.round(
Utils.AsOfDateTargetHours(
bodyshop.scoreboard_target.dailyBodyTarget +
bodyshop.scoreboard_target.dailyPaintTarget,
day
) +
(bodyshop.scoreboard_target.dailyBodyTarget +
bodyshop.scoreboard_target.dailyPaintTarget),
1
),
accHrs: 0,
};
const lab = {
accTargetHrs: _.round(
Utils.AsOfDateTargetHours(
bodyshop.scoreboard_target.dailyBodyTarget,
day
) + bodyshop.scoreboard_target.dailyBodyTarget,
1
),
accHrs: 0,
};
const lar = {
accTargetHrs: _.round(
Utils.AsOfDateTargetHours(
bodyshop.scoreboard_target.dailyPaintTarget,
day
) + bodyshop.scoreboard_target.dailyPaintTarget,
1
),
accHrs: 0,
};
if (ticketsGroupedByDate[day]) {
ticketsGroupedByDate[day].forEach((ticket) => {
r.actualhrs = r.actualhrs + ticket.actualhrs;
r.productivehrs = r.productivehrs + ticket.productivehrs;
acc_comb = acc_comb + ticket.productivehrs;
if (ticket.ciecacode !== "LAR")
acc_lab = acc_lab + ticket.productivehrs;
if (ticket.ciecacode === "LAR")
acc_lar = acc_lar + ticket.productivehrs;
});
}
combined.accHrs = acc_comb;
lab.accHrs = acc_lab;
lar.accHrs = acc_lar;
combinedData.push({ ...r, ...combined });
labData.push({ ...r, ...lab });
larData.push({ ...r, ...lar });
});
return {
fixed: ret,
combinedData: combinedData,
labData: labData,
larData: larData,
};
}, [fixedPeriods, data, bodyshop]);
if (error) return <AlertComponent message={error.message} type="error" />;
if (loading) return <LoadingSpinner />;
return (
<Row gutter={[16, 16]}>
<Col span={24}>
<ScoreboardTimeticketsTargetsTable />
</Col>
<Col span={24}>
<ScoreboardTicketsStats data={calculatedData.fixed} />
</Col>
<Col span={24}>
<ScoreboardTimeTicketsChart
data={calculatedData.combinedData}
chartTitle={t("scoreboard.labels.combinedcharttitle")}
/>
</Col>
<Col span={12}>
<ScoreboardTimeTicketsChart
data={calculatedData.labData}
chartTitle={t("scoreboard.labels.bodycharttitle")}
/>
</Col>
<Col span={12}>
<ScoreboardTimeTicketsChart
data={calculatedData.larData}
chartTitle={t("scoreboard.labels.refinishcharttitle")}
/>
</Col>
</Row>
);
}
function roundObject(inputObj) {
for (var key of Object.keys(inputObj)) {
if (typeof inputObj[key] === "number") {
inputObj[key] = inputObj[key].toFixed(1);
} else if (Array.isArray(inputObj[key])) {
inputObj[key].forEach((item) => roundObject(item));
} else if (typeof inputObj[key] === "object") {
roundObject(inputObj[key]);
}
}
}

View File

@@ -0,0 +1,617 @@
import {
Card,
Col,
Form,
Row,
Space,
Statistic,
Switch,
Typography,
} from "antd";
import moment from "moment";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import * as Util from "../scoreboard-targets-table/scoreboard-targets-table.util";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ScoreboardTicketsStats);
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(storedValue));
}, [key, storedValue]);
return [storedValue, setStoredValue];
}
export function ScoreboardTicketsStats({ data, bodyshop }) {
const { t } = useTranslation();
const [isLarge, setIsLarge] = useLocalStorage("isLargeStatistic", false);
const statisticSize = isLarge ? 36 : 24;
const statisticWeight = isLarge ? 550 : "normal";
const daySpan =
Util.CalculateWorkingDaysInPeriod(
moment().startOf("week"),
moment().endOf("week")
) > 5
? 3
: 4;
return (
<Card
title={t("scoreboard.labels.productivestatistics")}
extra={
<Form.Item
label={t("general.labels.tvmode")}
valuePropName="checked"
name={["tvmode"]}
>
<Switch
onClick={() => setIsLarge(!isLarge)}
defaultChecked={isLarge}
/>
</Form.Item>
}
>
<Row gutter={[16, 16]}>
<Col md={24}>
{/* Daily Stats */}
<Space direction="vertical" size="middle" style={{ display: "flex" }}>
<Row gutter={[16, 16]} align="center">
{[
"sunday",
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
].map((day) => {
if (bodyshop.workingdays[day] === true) {
return (
<Col key={day} span={daySpan} align="center">
<Card size="small" title={t("general.labels." + day)}>
<Row gutter={[8, 8]}>
<Col span={24}>
<Statistic
value={data.seperatedThisWeek[day].total}
valueStyle={{
color:
parseFloat(
data.seperatedThisWeek[day].total
) >=
bodyshop.scoreboard_target.dailyBodyTarget +
bodyshop.scoreboard_target.dailyPaintTarget
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.body")}
</Typography.Text>
}
value={data.seperatedThisWeek[day].lab}
valueStyle={{
color:
parseFloat(data.seperatedThisWeek[day].lab) >=
bodyshop.scoreboard_target.dailyBodyTarget
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.refinish")}
</Typography.Text>
}
value={data.seperatedThisWeek[day].lar}
valueStyle={{
color:
parseFloat(data.seperatedThisWeek[day].lar) >=
bodyshop.scoreboard_target.dailyPaintTarget
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
</Card>
</Col>
);
} else {
return null;
}
})}
</Row>
{/* Weekly Stats */}
<Row gutter={[16, 16]}>
{/* This Week */}
<Col span={8} align="center">
<Card size="small" title={t("scoreboard.labels.thisweek")}>
<Row gutter={[16, 16]}>
<Col span={24}>
<Statistic
value={data.totalThisWeek}
valueStyle={{
color:
parseFloat(data.totalThisWeek) >=
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().startOf("week"),
moment().endOf("week"),
bodyshop
) +
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().startOf("week"),
moment().endOf("week"),
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.body")}
</Typography.Text>
}
value={data.totalThisWeekLAB}
valueStyle={{
color:
parseFloat(data.totalThisWeekLAB) >=
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().startOf("week"),
moment().endOf("week"),
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.refinish")}
</Typography.Text>
}
value={data.totalThisWeekLAR}
valueStyle={{
color:
parseFloat(data.totalThisWeekLAR) >=
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().startOf("week"),
moment().endOf("week"),
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
</Card>
</Col>
{/* Last Week */}
<Col span={8} align="center">
<Card size="small" title={t("scoreboard.labels.lastweek")}>
<Row gutter={[16, 16]}>
<Col span={24}>
<Statistic
value={data.totalLastWeek}
valueStyle={{
color:
parseFloat(data.totalLastWeek) >=
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().subtract(1, "week").startOf("week"),
moment().subtract(1, "week").endOf("week"),
bodyshop
) +
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().subtract(1, "week").startOf("week"),
moment().subtract(1, "week").endOf("week"),
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.body")}
</Typography.Text>
}
value={data.totalLastWeekLAB}
valueStyle={{
color:
parseFloat(data.totalLastWeekLAB) >=
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().subtract(1, "week").startOf("week"),
moment().subtract(1, "week").endOf("week"),
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.refinish")}
</Typography.Text>
}
value={data.totalLastWeekLAR}
valueStyle={{
color:
parseFloat(data.totalLastWeekLAR) >=
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().subtract(1, "week").startOf("week"),
moment().subtract(1, "week").endOf("week"),
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
</Card>
</Col>
{/* Prior Week */}
<Col span={8} align="center">
<Card size="small" title={t("scoreboard.labels.priorweek")}>
<Row gutter={[16, 16]}>
<Col span={24}>
<Statistic
value={data.totalPriorWeek}
valueStyle={{
color:
parseFloat(data.totalPriorWeek) >=
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().subtract(2, "week").startOf("week"),
moment().subtract(2, "week").endOf("week"),
bodyshop
) +
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().subtract(2, "week").startOf("week"),
moment().subtract(2, "week").endOf("week"),
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.body")}
</Typography.Text>
}
value={data.totalPriorWeekLAB}
valueStyle={{
color:
parseFloat(data.totalPriorWeekLAB) >=
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().subtract(2, "week").startOf("week"),
moment().subtract(2, "week").endOf("week"),
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.refinish")}
</Typography.Text>
}
value={data.totalPriorWeekLAR}
valueStyle={{
color:
parseFloat(data.totalPriorWeekLAR) >=
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().subtract(2, "week").startOf("week"),
moment().subtract(2, "week").endOf("week"),
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
</Card>
</Col>
</Row>
{/* Monthly Stats */}
<Row gutter={[16, 16]}>
{/* This Month */}
<Col span={8} align="center">
<Card size="small" title={t("scoreboard.labels.thismonth")}>
<Row gutter={[16, 16]}>
<Col span={24}>
<Statistic
value={data.totalThisMonth}
valueStyle={{
color:
parseFloat(data.totalThisMonth) >=
Util.MonthlyTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
bodyshop
) +
Util.MonthlyTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.body")}
</Typography.Text>
}
value={data.totalThisMonthLAB}
valueStyle={{
color:
parseFloat(data.totalThisMonthLAB) >=
Util.MonthlyTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.refinish")}
</Typography.Text>
}
value={data.totalThisMonthLAR}
valueStyle={{
color:
parseFloat(data.totalThisMonthLAR) >=
Util.MonthlyTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
</Card>
</Col>
{/* Last Month */}
<Col span={8} align="center">
<Card size="small" title={t("scoreboard.labels.lastmonth")}>
<Row gutter={[16, 16]}>
<Col span={24}>
<Statistic
value={data.totalLastMonth}
valueStyle={{
color:
parseFloat(data.totalLastMonth) >=
Util.LastMonthTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
bodyshop
) +
Util.LastMonthTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.body")}
</Typography.Text>
}
value={data.totalLastMonthLAB}
valueStyle={{
color:
parseFloat(data.totalLastMonthLAB) >=
Util.LastMonthTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.refinish")}
</Typography.Text>
}
value={data.totalLastMonthLAR}
valueStyle={{
color:
parseFloat(data.totalLastMonthLAR) >=
Util.LastMonthTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
</Card>
</Col>
{/* Efficiency Over Period */}
<Col span={8} align="center">
<Card
size="small"
title={t("scoreboard.labels.efficiencyoverperiod")}
>
<Row gutter={[16, 16]}>
<Col span={24}>
<Statistic
value={`${data.totalEffieciencyOverPeriod || 0}%`}
valueStyle={{
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.body")}
</Typography.Text>
}
value={`${data.totalEffieciencyOverPeriodLAB || 0}%`}
valueStyle={{
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.refinish")}
</Typography.Text>
}
value={`${data.totalEffieciencyOverPeriodLAR || 0}%`}
valueStyle={{
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
</Card>
</Col>
</Row>
</Space>
{/* Disclaimer */}
<Typography.Text type="secondary">
*{t("scoreboard.labels.calendarperiod")}
</Typography.Text>
</Col>
</Row>
</Card>
);
}

View File

@@ -0,0 +1,285 @@
import { CalendarOutlined } from "@ant-design/icons";
import { Card, Col, Divider, Row, Statistic } from "antd";
import moment from "moment";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import * as Util from "../scoreboard-targets-table/scoreboard-targets-table.util";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
const rowGutter = [16, 16];
const statSpans = { xs: 24, sm: 3 };
export function ScoreboardTimeticketsTargetsTable({ bodyshop }) {
const { t } = useTranslation();
return (
<Card title={t("scoreboard.labels.targets")}>
<Row gutter={rowGutter}>
<Col xs={24} sm={{ offset: 0, span: 3 }} lg={{ span: 3 }}>
<Statistic
title={t("scoreboard.labels.workingdays")}
value={Util.CalculateWorkingDaysThisMonth()}
prefix={<CalendarOutlined />}
/>
</Col>
<Col xs={24} sm={{ offset: 0, span: 20 }} lg={{ offset: 0, span: 20 }}>
<Row>
<Col {...statSpans}>
<Statistic title="Type" value={t("scoreboard.labels.body")} />
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.dailytarget")}
value={bodyshop.scoreboard_target.dailyBodyTarget}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.thisweek")}
value={Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().startOf("week"),
moment().endOf("week"),
bodyshop
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.lastweek")}
value={Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().subtract(1, "week").startOf("week"),
moment().subtract(1, "week").endOf("week"),
bodyshop
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.priorweek")}
value={Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().subtract(2, "week").startOf("week"),
moment().subtract(2, "week").endOf("week"),
bodyshop
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.thismonth")}
value={Util.MonthlyTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
bodyshop
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.lastmonth")}
value={Util.LastMonthTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
bodyshop
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.asoftodaytarget")}
value={Util.AsOfTodayTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
bodyshop
)}
/>
</Col>
</Row>
<Row>
<Col {...statSpans}>
<Statistic value={t("scoreboard.labels.refinish")} />
</Col>
<Col {...statSpans}>
<Statistic value={bodyshop.scoreboard_target.dailyPaintTarget} />
</Col>
<Col {...statSpans}>
<Statistic
value={Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().startOf("week"),
moment().endOf("week"),
bodyshop
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().subtract(1, "week").startOf("week"),
moment().subtract(1, "week").endOf("week"),
bodyshop
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().subtract(2, "week").startOf("week"),
moment().subtract(2, "week").endOf("week"),
bodyshop
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={Util.MonthlyTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
bodyshop
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={Util.LastMonthTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
bodyshop
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={Util.AsOfTodayTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
bodyshop
)}
/>
</Col>
</Row>
<Row>
<Divider style={{ margin: 5 }} />
</Row>
<Row>
<Col {...statSpans}></Col>
<Col {...statSpans}>
<Statistic
value={(
bodyshop.scoreboard_target.dailyBodyTarget +
bodyshop.scoreboard_target.dailyPaintTarget
).toFixed(1)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={(
Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().startOf("week"),
moment().endOf("week"),
bodyshop
) +
Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().startOf("week"),
moment().endOf("week"),
bodyshop
)
).toFixed(1)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={(
Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().subtract(1, "week").startOf("week"),
moment().subtract(1, "week").endOf("week"),
bodyshop
) +
Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().subtract(1, "week").startOf("week"),
moment().subtract(1, "week").endOf("week"),
bodyshop
)
).toFixed(1)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={(
Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().subtract(2, "week").startOf("week"),
moment().subtract(2, "week").endOf("week"),
bodyshop
) +
Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().subtract(2, "week").startOf("week"),
moment().subtract(2, "week").endOf("week"),
bodyshop
)
).toFixed(1)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={(
Util.MonthlyTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
bodyshop
) +
Util.MonthlyTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
bodyshop
)
).toFixed(1)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={(
Util.LastMonthTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
bodyshop
) +
Util.LastMonthTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
bodyshop
)
).toFixed(1)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={(
Util.AsOfTodayTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
bodyshop
) +
Util.AsOfTodayTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
bodyshop
)
).toFixed(1)}
/>
</Col>
</Row>
</Col>
</Row>
</Card>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ScoreboardTimeticketsTargetsTable);

View File

@@ -25,6 +25,7 @@ export function TechLookupJobsList({ bodyshop }) {
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
variables: {
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"],
isConverted: true,
},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",

View File

@@ -154,7 +154,6 @@ export function VendorsFormComponent({
label={t("vendors.fields.phone")}
name="phone"
rules={[
{ required: true, message: t("general.validation.required") },
({ getFieldValue }) =>
PhoneItemFormatterValidation(getFieldValue, "phone"),
]}

View File

@@ -1,9 +1,9 @@
import { gql } from "@apollo/client";
export const QUERY_ALL_ACTIVE_JOBS = gql`
query QUERY_ALL_ACTIVE_JOBS($statuses: [String!]!) {
query QUERY_ALL_ACTIVE_JOBS($statuses: [String!]!, $isConverted: Boolean) {
jobs(
where: { status: { _in: $statuses } }
where: { status: { _in: $statuses }, converted: { _eq: $isConverted } }
order_by: { created_at: desc }
) {
iouparent
@@ -432,7 +432,6 @@ export const QUERY_JOB_COSTING_DETAILS = gql`
status
ca_bc_pvrt
ca_customer_gst
joblines(where: { removed: { _eq: false } }) {
id
unq_seq
@@ -1001,7 +1000,6 @@ export const QUERY_TECH_JOB_DETAILS = gql`
actual_completion
actual_delivery
actual_in
id
ins_co_nm
clm_no
@@ -1586,7 +1584,6 @@ export const QUERY_ALL_JOB_FIELDS = gql`
clm_title
clm_total
clm_zip
cust_pr
ded_amt
ded_status
@@ -1669,7 +1666,6 @@ export const QUERY_ALL_JOB_FIELDS = gql`
parts_tax_rates
pay_amt
pay_chknm
pay_type
payee_nms
plate_no
@@ -2077,6 +2073,7 @@ export const QUERY_JOB_EXPORT_DMS = gql`
}
}
`;
export const QUERY_RELATED_ROS = gql`
query QUERY_RELATED_ROS($jobid: uuid!) {
relatedjobs(
@@ -2098,6 +2095,7 @@ export const QUERY_RELATED_ROS = gql`
}
}
`;
export const INSERT_RELATED_ROS = gql`
mutation INSERT_RELATED_ROS($relationship: relatedjobs_insert_input!) {
insert_relatedjobs_one(object: $relationship) {
@@ -2115,6 +2113,7 @@ export const INSERT_RELATED_ROS = gql`
}
}
`;
export const DELETE_RELATED_RO = gql`
mutation DELETE_RELATED_RO($relationshipid: uuid!) {
delete_relatedjobs_by_pk(id: $relationshipid) {
@@ -2122,6 +2121,7 @@ export const DELETE_RELATED_RO = gql`
}
}
`;
export const GET_JOB_LINE_ORDERS = gql`
query GET_JOB_LINE_ORDERS($joblineid: uuid!) {
billlines(where: { joblineid: { _eq: $joblineid } }) {

View File

@@ -145,7 +145,7 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
$fixedEnd: date!
) {
timetickets(
where: { date: { _gte: $start, _lte: $end } }
where: { date: { _gte: $start, _lte: $end }, cost_center: {_neq: "timetickets.labels.shift"} }
order_by: { date: desc_nulls_first }
) {
actualhrs
@@ -176,7 +176,7 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
}
}
fixedperiod: timetickets(
where: { date: { _gte: $fixedStart, _lte: $fixedEnd } }
where: { date: { _gte: $fixedStart, _lte: $fixedEnd }, cost_center: {_neq: "timetickets.labels.shift"} }
order_by: { date: desc_nulls_first }
) {
actualhrs

View File

@@ -1,21 +1,22 @@
import Icon, { FieldTimeOutlined } from "@ant-design/icons";
import { Tabs } from "antd";
import queryString from "query-string";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { FaShieldAlt } from "react-icons/fa";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom";
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 ScoreboardTimeTicketsStats from "../../components/scoreboard-timetickets-stats/scoreboard-timetickets.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 queryString from "query-string";
import { useHistory, useLocation } from "react-router-dom";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -71,7 +72,7 @@ export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
tab={
<span>
<FieldTimeOutlined />
{t("scoreboard.labels.timetickets")}
{t("scoreboard.labels.timeticketsemployee")}
</span>
}
destroyInactiveTabPane
@@ -79,6 +80,18 @@ export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
>
<ScoreboardTimeTickets />
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<FieldTimeOutlined />
{t("scoreboard.labels.allemployeetimetickets")}
</span>
}
destroyInactiveTabPane
key="ticketsstats"
>
<ScoreboardTimeTicketsStats />
</Tabs.TabPane>
</Tabs>
</RbacWrapper>
</FeatureWrapper>

View File

@@ -1,15 +1,17 @@
import Fingerprint2 from "@fingerprintjs/fingerprintjs";
import FingerprintJS from "@fingerprintjs/fingerprintjs";
import * as Sentry from "@sentry/browser";
import { notification } from "antd";
import axios from "axios";
import { setUserId, setUserProperties } from "firebase/analytics";
import {
checkActionCode,
confirmPasswordReset,
sendPasswordResetEmail,
signInWithEmailAndPassword,
signOut,
sendPasswordResetEmail,
} from "firebase/auth";
import { doc } from "firebase/firestore";
import { doc, getDoc, setDoc } from "firebase/firestore";
import { getToken } from "firebase/messaging";
import i18next from "i18next";
import LogRocket from "logrocket";
import { all, call, delay, put, select, takeLatest } from "redux-saga/effects";
@@ -20,6 +22,7 @@ import {
firestore,
getCurrentUser,
logImEXEvent,
messaging,
updateCurrentUser,
} from "../../firebase/firebase.utils";
import {
@@ -28,6 +31,7 @@ import {
sendPasswordResetSuccess,
setAuthlevel,
setInstanceConflict,
setInstanceId,
setLocalFingerprint,
signInFailure,
signInSuccess,
@@ -39,9 +43,8 @@ import {
validatePasswordResetSuccess,
} from "./user.actions";
import UserActionTypes from "./user.types";
import axios from "axios";
import { messaging } from "../../firebase/firebase.utils";
import { getToken } from "firebase/messaging";
const fpPromise = FingerprintJS.load();
export function* onEmailSignInStart() {
yield takeLatest(UserActionTypes.EMAIL_SIGN_IN_START, signInWithEmail);
@@ -148,17 +151,15 @@ export function* setInstanceIdSaga({ payload: uid }) {
try {
const userInstanceRef = doc(firestore, `userInstance/${uid}`);
const fingerprint = Fingerprint2.x64hash128(
(yield Fingerprint2.getPromise({})).map((c) => c.value).join(""),
31
);
yield userInstanceRef.set({
// Get the visitor identifier when you need it.
const fp = yield fpPromise;
const result = yield fp.get();
yield setDoc(userInstanceRef, {
timestamp: new Date(),
fingerprint,
fingerprint: result.visitorId,
});
yield put(setLocalFingerprint(fingerprint));
yield put(setLocalFingerprint(result.visitorId));
yield delay(5 * 60 * 1000);
if (process.env.NODE_ENV === "production") yield put(checkInstanceId(uid));
} catch (error) {
@@ -171,11 +172,9 @@ export function* onCheckInstanceId() {
}
export function* checkInstanceIdSaga({ payload: uid }) {
try {
const userInstanceRef = doc(firestore, `userInstance/${uid}`);
const snapshot = yield userInstanceRef.get();
const snapshot = yield getDoc(doc(firestore, `userInstance/${uid}`));
let fingerprint = yield select((state) => state.user.fingerprint);
yield put(setInstanceConflict());
if (snapshot.data().fingerprint === fingerprint) {
yield delay(5 * 60 * 1000);
yield put(checkInstanceId(uid));
@@ -215,7 +214,6 @@ export function* signInSuccessSaga({ payload }) {
console.log("Error updating Crisp settings.", error);
}
// if (!payload.email.includes("@imex.")) yield put(setInstanceId(payload.uid));
setUserId(analytics, payload.email);
setUserProperties(analytics, payload);
yield logImEXEvent("redux_sign_in_success");
@@ -289,6 +287,14 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
: { validemail: false }
)
);
if (payload.features.singleDeviceOnly) {
const user = yield select((state) => state.user.currentUser);
if (!(user.email.includes("@imex.") || user.email.includes("@rome.")))
yield put(setInstanceId(user.uid));
}
try {
window.$crisp.push(["set", "user:company", [payload.shopname]]);
if (authRecord[0] && authRecord[0].user.validemail) {

View File

@@ -1115,6 +1115,7 @@
"total": "Total",
"totals": "Totals",
"tuesday": "Tuesday",
"tvmode": "TV Mode",
"unknown": "Unknown",
"username": "Username",
"view": "View",
@@ -1463,6 +1464,7 @@
"cost_dms_acctnumber": "Cost DMS Acct #",
"dms_make": "DMS Make",
"dms_model": "DMS Model",
"dms_model_override": "Override DMS Make/Model",
"dms_unsold": "New, Unsold Vehicle",
"dms_wip_acctnumber": "Cost WIP DMS Acct #",
"id": "DMS ID",
@@ -2673,8 +2675,12 @@
"painthrs": "Paint Hours"
},
"labels": {
"allemployeetimetickets": "All Employee Time Tickets",
"asoftodaytarget": "As of Today",
"body": "Body",
"bodycharttitle": "Body Targets vs Actual",
"calendarperiod": "Periods based on calendar weeks/months.",
"combinedcharttitle": "Combined Targets vs Actual",
"dailyactual": "Actual (D)",
"dailytarget": "Daily",
"efficiencyoverperiod": "Efficiency over Selected Dates",
@@ -2683,12 +2689,16 @@
"lastmonth": "Last Month",
"lastweek": "Last Week",
"monthlytarget": "Monthly",
"priorweek": "Prior Week",
"productivestatistics": "Productive Hours Statistics",
"productivetimeticketsoverdate": "Productive Hours over Selected Dates",
"refinish": "Refinish",
"refinishcharttitle": "Refinish Targets vs Actual",
"targets": "Targets",
"thismonth": "This Month",
"thisweek": "This Week",
"timetickets": "Timetickets",
"timetickets": "Time Tickets",
"timeticketsemployee": "Time Tickets by Employee",
"todateactual": "Actual (MTD)",
"totaloverperiod": "Total over Selected Dates",
"weeklyactual": "Actual (W)",

View File

@@ -1115,6 +1115,7 @@
"total": "",
"totals": "",
"tuesday": "",
"tvmode": "",
"unknown": "Desconocido",
"username": "",
"view": "",
@@ -1463,6 +1464,7 @@
"cost_dms_acctnumber": "",
"dms_make": "",
"dms_model": "",
"dms_model_override": "",
"dms_unsold": "",
"dms_wip_acctnumber": "",
"id": "",
@@ -2673,8 +2675,12 @@
"painthrs": ""
},
"labels": {
"allemployeetimetickets": "",
"asoftodaytarget": "",
"body": "",
"bodycharttitle": "",
"calendarperiod": "",
"combinedcharttitle": "",
"dailyactual": "",
"dailytarget": "",
"efficiencyoverperiod": "",
@@ -2683,12 +2689,16 @@
"lastmonth": "",
"lastweek": "",
"monthlytarget": "",
"priorweek": "",
"productivestatistics": "",
"productivetimeticketsoverdate": "",
"refinish": "",
"refinishcharttitle": "",
"targets": "",
"thismonth": "",
"thisweek": "",
"timetickets": "",
"timeticketsemployee": "",
"todateactual": "",
"totaloverperiod": "",
"weeklyactual": "",

View File

@@ -1115,6 +1115,7 @@
"total": "",
"totals": "",
"tuesday": "",
"tvmode": "",
"unknown": "Inconnu",
"username": "",
"view": "",
@@ -1463,6 +1464,7 @@
"cost_dms_acctnumber": "",
"dms_make": "",
"dms_model": "",
"dms_model_override": "",
"dms_unsold": "",
"dms_wip_acctnumber": "",
"id": "",
@@ -2673,8 +2675,12 @@
"painthrs": ""
},
"labels": {
"allemployeetimetickets": "",
"asoftodaytarget": "",
"body": "",
"bodycharttitle": "",
"calendarperiod": "",
"combinedcharttitle": "",
"dailyactual": "",
"dailytarget": "",
"efficiencyoverperiod": "",
@@ -2683,12 +2689,16 @@
"lastmonth": "",
"lastweek": "",
"monthlytarget": "",
"priorweek": "",
"productivestatistics": "",
"productivetimeticketsoverdate": "",
"refinish": "",
"refinishcharttitle": "",
"targets": "",
"thismonth": "",
"thisweek": "",
"timetickets": "",
"timeticketsemployee": "",
"todateactual": "",
"totaloverperiod": "",
"weeklyactual": "",

View File

@@ -12,6 +12,13 @@ export default async function FcmHandler({ client, payload }) {
},
},
});
client.cache.modify({
fields: {
messages_aggregate(cached) {
return { aggregate: { count: cached.aggregate.count + 1 } };
},
},
});
break;
case "messaging-outbound":
client.cache.modify({
@@ -30,6 +37,7 @@ export default async function FcmHandler({ client, payload }) {
});
break;
case "messaging-mark-conversation-read":
let previousUnreadCount = 0;
client.cache.modify({
id: client.cache.identify({
__typename: "conversations",
@@ -37,10 +45,22 @@ export default async function FcmHandler({ client, payload }) {
}),
fields: {
messages_aggregate(cached) {
previousUnreadCount = cached.aggregate.count;
return { aggregate: { count: 0 } };
},
},
});
client.cache.modify({
fields: {
messages_aggregate(cached) {
return {
aggregate: {
count: cached.aggregate.count - previousUnreadCount,
},
};
},
},
});
break;
default:
console.log("No payload type set.");

File diff suppressed because it is too large Load Diff

View File

@@ -4089,6 +4089,7 @@
- name: event-secret
value_from_env: EVENT_SECRET
request_transform:
method: POST
query_params: {}
template_engine: Kriti
url: '{{$base_url}}/job/statustransition'

View File

@@ -0,0 +1,38 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- CREATE OR REPLACE FUNCTION public.search_cccontracts(search text)
-- RETURNS SETOF cccontracts
-- LANGUAGE plpgsql
-- STABLE
-- AS $function$
-- BEGIN
-- IF search = '' THEN
-- RETURN query
-- SELECT
-- *
-- FROM
-- cccontracts c;
-- ELSE
-- RETURN query
-- SELECT
-- contracts.*
-- FROM
-- courtesycars cars,
-- jobs jobs,
-- cccontracts contracts
-- WHERE (jobs.ro_number ILIKE '%' || search || '%'
-- OR jobs.ownr_fn ILIKE '%' || search || '%'
-- OR jobs.ownr_ln ILIKE '%' || search || '%'
-- OR jobs.ownr_co_nm ILIKE '%' || search || '%'
-- OR (cast(contracts.agreementnumber as text)) ILIKE '%' || search || '%'
-- OR contracts.driver_fn ILIKE '%' || search || '%'
-- OR contracts.driver_ln ILIKE '%' || search || '%'
-- OR cars.fleetnumber ILIKE '%' || search || '%'
-- OR cars.make ILIKE '%' || search || '%'
-- OR cars.model ILIKE '%' || search || '%'
-- OR cars.plate ILIKE '%' || search || '%')
-- AND contracts.jobid = jobs.id
-- AND contracts.courtesycarid = cars.id;
-- END IF;
-- END
-- $function$;

View File

@@ -0,0 +1,36 @@
CREATE OR REPLACE FUNCTION public.search_cccontracts(search text)
RETURNS SETOF cccontracts
LANGUAGE plpgsql
STABLE
AS $function$
BEGIN
IF search = '' THEN
RETURN query
SELECT
*
FROM
cccontracts c;
ELSE
RETURN query
SELECT
contracts.*
FROM
courtesycars cars,
jobs jobs,
cccontracts contracts
WHERE (jobs.ro_number ILIKE '%' || search || '%'
OR jobs.ownr_fn ILIKE '%' || search || '%'
OR jobs.ownr_ln ILIKE '%' || search || '%'
OR jobs.ownr_co_nm ILIKE '%' || search || '%'
OR (cast(contracts.agreementnumber as text)) ILIKE '%' || search || '%'
OR contracts.driver_fn ILIKE '%' || search || '%'
OR contracts.driver_ln ILIKE '%' || search || '%'
OR cars.fleetnumber ILIKE '%' || search || '%'
OR cars.make ILIKE '%' || search || '%'
OR cars.model ILIKE '%' || search || '%'
OR cars.plate ILIKE '%' || search || '%')
AND contracts.jobid = jobs.id
AND contracts.courtesycarid = cars.id;
END IF;
END
$function$;

View File

@@ -879,6 +879,12 @@ async function UpdateDmsVehicle(socket) {
},
vehicle: {
...socket.DMSVeh.vehicle,
...(socket.txEnvelope.dms_model_override
? {
make: socket.txEnvelope.dms_make,
modelAbrev: socket.txEnvelope.dms_model,
}
: {}),
deliveryDate:
socket.txEnvelope.dms_unsold === true
? ""

View File

@@ -19,6 +19,7 @@ const aws4 = require("aws4");
const { gql } = require("graphql-request");
var host = process.env.OPEN_SEARCH_HOST;
const createAwsConnector = (credentials, region) => {
class AmazonConnection extends Connection {
buildRequestObject(params) {
@@ -207,13 +208,15 @@ async function OpenSearchUpdateHandler(req, res) {
}
}
async function OpensearchSearchHandler(req, res) {
async function OpenSearchSearchHandler(req, res) {
try {
const { search, bodyshopid, index } = req.body;
if (!req.user) {
res.sendStatus(401);
return;
}
logger.log("os-search", "DEBUG", req.user.email, null, {
search,
});
@@ -273,7 +276,7 @@ async function OpensearchSearchHandler(req, res) {
{
query_string: {
query: `*${search}*`,
// Weighted Fields
fields: [
"*ro_number^20",
"*clm_no^14",
@@ -320,4 +323,4 @@ async function OpensearchSearchHandler(req, res) {
}
exports.handler = OpenSearchUpdateHandler;
exports.search = OpensearchSearchHandler;
exports.search = OpenSearchSearchHandler;