Merged in rome/test (pull request #1057)

Rome/test
This commit is contained in:
Patrick Fic
2023-11-15 22:45:33 +00:00
58 changed files with 28950 additions and 2628 deletions

View File

@@ -1,4 +1,4 @@
<babeledit_project be_version="2.7.1" version="1.2">
<babeledit_project version="1.2" be_version="2.7.1">
<!--
BabelEdit project file
@@ -364,6 +364,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>unblock</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>viewjob</name>
<definition_loaded>false</definition_loaded>
@@ -24841,6 +24862,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>
@@ -44890,6 +44932,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>hours_sold_detail_closed_estimator</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>hours_sold_detail_closed_ins_co</name>
<definition_loaded>false</definition_loaded>
@@ -44974,6 +45037,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>hours_sold_detail_open_estimator</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>hours_sold_detail_open_ins_co</name>
<definition_loaded>false</definition_loaded>
@@ -45058,6 +45142,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>hours_sold_summary_closed_estimator</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>hours_sold_summary_closed_ins_co</name>
<definition_loaded>false</definition_loaded>
@@ -45142,6 +45247,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>hours_sold_summary_open_estimator</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>hours_sold_summary_open_ins_co</name>
<definition_loaded>false</definition_loaded>
@@ -45478,6 +45604,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>open_orders_referral</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>open_orders_specific_csr</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

@@ -38,8 +38,8 @@ const mapStateToProps = createStructuredSelector({
});
const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("billEnter")),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
insertAuditTrail: ({ jobid, billid, operation }) =>
dispatch(insertAuditTrail({ jobid, billid, operation })),
});
const Templates = TemplateList("job_special");
@@ -167,7 +167,7 @@ function BillEnterModalContainer({
},
],
},
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"],
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID", "GET_JOB_BY_PK"],
});
await Promise.all(
@@ -354,7 +354,9 @@ function BillEnterModalContainer({
insertAuditTrail({
jobid: values.jobid,
billid: billId,
operation: AuditTrailMapping.billposted(remainingValues.invoice_number),
operation: AuditTrailMapping.billposted(
r1.data.insert_bills.returning[0].invoice_number
),
});
if (enterAgain) {

View File

@@ -50,6 +50,7 @@ export function BillFormComponent({
job,
loadOutstandingReturns,
loadInventory,
preferredMake,
}) {
const { t } = useTranslation();
const client = useApolloClient();
@@ -185,6 +186,7 @@ export function BillFormComponent({
<VendorSearchSelect
disabled={disabled}
options={vendorAutoCompleteOptions}
preferredMake={preferredMake}
onSelect={handleVendorSelect}
/>
</Form.Item>

View File

@@ -1,16 +1,16 @@
import { useLazyQuery, useQuery } from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { QUERY_OUTSTANDING_INVENTORY } from "../../graphql/inventory.queries";
import { GET_JOB_LINES_TO_ENTER_BILL } from "../../graphql/jobs-lines.queries";
import { QUERY_UNRECEIVED_LINES } from "../../graphql/parts-orders.queries";
import { SEARCH_VENDOR_AUTOCOMPLETE } from "../../graphql/vendors.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import BillFormComponent from "./bill-form.component";
import BillCmdReturnsTableComponent from "../bill-cm-returns-table/bill-cm-returns-table.component";
import { QUERY_UNRECEIVED_LINES } from "../../graphql/parts-orders.queries";
import BillInventoryTable from "../bill-inventory-table/bill-inventory-table.component";
import { QUERY_OUTSTANDING_INVENTORY } from "../../graphql/inventory.queries";
import { useTreatments } from "@splitsoftware/splitio-react";
import BillFormComponent from "./bill-form.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -59,6 +59,7 @@ export function BillFormContainer({
disableInvNumber={disableInvNumber}
loadOutstandingReturns={loadOutstandingReturns}
loadInventory={loadInventory}
preferredMake={lineData ? lineData.jobs_by_pk.v_make_desc : null}
/>
{!billEdit && (
<BillCmdReturnsTableComponent

View File

@@ -8,15 +8,23 @@ import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { searchingForConversation } from "../../redux/messaging/messaging.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
searchingForConversation: searchingForConversation,
});
const mapDispatchToProps = (dispatch) => ({
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
});
export function ChatOpenButton({ bodyshop, phone, jobid, openChatByPhone }) {
export function ChatOpenButton({
bodyshop,
searchingForConversation,
phone,
jobid,
openChatByPhone,
}) {
const { t } = useTranslation();
if (!phone) return <></>;
@@ -29,7 +37,7 @@ export function ChatOpenButton({ bodyshop, phone, jobid, openChatByPhone }) {
onClick={(e) => {
e.stopPropagation();
const p = parsePhoneNumber(phone, "CA");
if (searchingForConversation) return; //This is to prevent finding the same thing twice.
if (p && p.isValid()) {
openChatByPhone({ phone_num: p.formatInternational(), jobid: jobid });
} else {

View File

@@ -9,15 +9,15 @@ import {
Table,
Tooltip,
} from "antd";
import moment from "moment";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
import moment from "moment";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort } from "../../utils/sorters";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
const [state, setState] = useState({
sortedInfo: {},
@@ -115,6 +115,14 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
sortOrder:
state.sortedInfo.columnKey === "model" && state.sortedInfo.order,
},
{
title: t("courtesycars.fields.color"),
dataIndex: "color",
key: "color",
sorter: (a, b) => alphaSort(a.color, b.color),
sortOrder:
state.sortedInfo.columnKey === "color" && state.sortedInfo.order,
},
{
title: t("courtesycars.fields.plate"),
dataIndex: "plate",

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

@@ -109,7 +109,7 @@ export function EmailOverlayContainer({
]
: []),
],
media: selectedMedia.filter((m) => m.isSelected).map((m) => m.src),
media: selectedMedia.filter((m) => m.isSelected).map((m) => m.fullsize),
//attachments,
});
notification["success"]({ message: t("emails.successes.sent") });

View File

@@ -4,6 +4,7 @@ import {
Divider,
Dropdown,
Form,
Input,
Menu,
notification,
Popover,
@@ -34,6 +35,8 @@ import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.
import ScheduleAtChange from "./job-at-change.component";
import ScheduleEventColor from "./schedule-event.color.component";
import ScheduleEventNote from "./schedule-event.note.component";
import { useMutation } from "@apollo/client";
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -58,16 +61,44 @@ export function ScheduleEventComponent({
const [visible, setVisible] = useState(false);
const history = useHistory();
const searchParams = queryString.parse(useLocation().search);
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
const [title, setTitle] = useState(event.title);
const blockContent = (
<div>
<Space direction="vertical" wrap>
<Input
value={title}
onChange={(e) => setTitle(e.currentTarget.value)}
onBlur={async () => {
await updateAppointment({
variables: {
appid: event.id,
app: {
title: title,
},
},
optimisticResponse: {
update_appointments: {
__typename: "appointments_mutation_response",
returning: [
{
...event,
title: title,
__typename: "appointments",
},
],
},
},
});
}}
/>
<Button
onClick={() => handleCancel({ id: event.id })}
disabled={event.arrived}
>
{t("appointments.actions.cancel")}
{t("appointments.actions.unblock")}
</Button>
</div>
</Space>
);
const popoverContent = (

View File

@@ -1,5 +1,6 @@
import { useMutation } from "@apollo/client";
import { Button, Card, Form, Input, notification, Switch } from "antd";
import moment from "moment-business-days";
import queryString from "query-string";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
@@ -12,16 +13,15 @@ import {
MARK_LATEST_APPOINTMENT_ARRIVED,
} from "../../../../graphql/appointments.queries";
import { UPDATE_JOB } from "../../../../graphql/jobs.queries";
import { UPDATE_OWNER } from "../../../../graphql/owners.queries";
import { insertAuditTrail } from "../../../../redux/application/application.actions";
import {
selectBodyshop,
selectCurrentUser,
} from "../../../../redux/user/user.selectors";
import AuditTrailMapping from "../../../../utils/AuditTrailMappings";
import ConfigFormComponents from "../../../config-form-components/config-form-components.component";
import DateTimePicker from "../../../form-date-time-picker/form-date-time-picker.component";
import moment from "moment-business-days";
import { insertAuditTrail } from "../../../../redux/application/application.actions";
import AuditTrailMapping from "../../../../utils/AuditTrailMappings";
import { UPDATE_OWNER } from "../../../../graphql/owners.queries";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -230,6 +230,7 @@ export function JobChecklistForm({
)),
scheduled_delivery:
job.scheduled_delivery && moment(job.scheduled_delivery),
production_vars: job.production_vars,
}),
...(type === "deliver" && {
removeFromProduction: true,

View File

@@ -8,7 +8,7 @@ export default function JobLinesBillRefernece({ jobline }) {
return (
<div style={{ color: subletRequired && "tomato" }}>
{subletRequired && <WarningFilled />}
{`${(billLine.actual_price * billLine.quantity).toFixed(2)} (${
{`${billLine.actual_price.toFixed(2)} x ${billLine.quantity} (${
billLine.bill.vendor.name
} #${billLine.bill.invoice_number})`}
</div>

View File

@@ -33,7 +33,9 @@ const JobSearchSelect = (
useLazyQuery(SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE);
const executeSearch = (v) => {
if (v && v !== "") callSearch(v);
console.log(v);
if (v && v.variables?.search !== "" && v.variables.search.length >= 2)
callSearch(v);
};
const debouncedExecuteSearch = _.debounce(executeSearch, 500);

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,7 +1,7 @@
import React, { useEffect } from "react";
import { Gallery } from "react-grid-gallery";
import { useTranslation } from "react-i18next";
import { GenerateThumbUrl } from "./job-documents.utility";
import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents.utility";
function JobsDocumentGalleryExternal({
data,
@@ -15,7 +15,7 @@ function JobsDocumentGalleryExternal({
let documents = data.reduce((acc, value) => {
if (value.type.startsWith("image")) {
acc.push({
//src: GenerateSrcUrl(value),
fullsize: GenerateSrcUrl(value),
src: GenerateThumbUrl(value),
thumbnailHeight: 225,
thumbnailWidth: 225,

View File

@@ -52,7 +52,7 @@ function JobDocumentsLocalGalleryExternal({
val.type.mime &&
val.type.mime.startsWith("image")
) {
acc.push({ ...val, src: val.thumbnail });
acc.push({ ...val, src: val.thumbnail, fullsize: val.src });
}
return acc;
}, [])

View File

@@ -231,7 +231,14 @@ export function LaborAllocationsTable({
{summary.adjustments.toFixed(1)}
</Table.Summary.Cell>
<Table.Summary.Cell>
{summary.difference.toFixed(1)}
<Typography.Text
style={{
fontWeight: "bold",
color: summary.difference >= 0 ? "green" : "red",
}}
>
{summary.difference.toFixed(1)}
</Typography.Text>
</Table.Summary.Cell>
</Table.Summary.Row>
)}

View File

@@ -21,7 +21,8 @@ const OwnerSearchSelect = ({ value, onChange, onBlur, disabled }, ref) => {
useLazyQuery(SEARCH_OWNERS_BY_ID_FOR_AUTOCOMPLETE);
const executeSearch = (v) => {
callSearch(v);
if (v && v.variables?.search !== "" && v.variables.search.length >= 2)
callSearch(v);
};
const debouncedExecuteSearch = _.debounce(executeSearch, 500);

View File

@@ -106,7 +106,11 @@ export default function OwnersListComponent({
<Input.Search
placeholder={search.search || t("general.labels.search")}
onSearch={(value) => {
search.search = value;
if (value?.length >= 3) {
search.search = value;
} else {
delete search.search;
}
history.push({ search: queryString.stringify(search) });
}}
enterButton

View File

@@ -480,8 +480,8 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
state.sortedInfo.order,
sorter: (a, b) =>
alphaSort(
bodyshop.employees.find((e) => e.id === a.employee_body)?.first_name,
bodyshop.employees.find((e) => e.id === b.employee_body)?.first_name
bodyshop.employees?.find((e) => e.id === a.employee_body)?.first_name,
bodyshop.employees?.find((e) => e.id === b.employee_body)?.first_name
),
render: (text, record) => (
<ProductionListEmployeeAssignment
@@ -499,8 +499,8 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
state.sortedInfo.order,
sorter: (a, b) =>
alphaSort(
bodyshop.employees.find((e) => e.id === a.employee_prep)?.first_name,
bodyshop.employees.find((e) => e.id === b.employee_prep)?.first_name
bodyshop.employees?.find((e) => e.id === a.employee_prep)?.first_name,
bodyshop.employees?.find((e) => e.id === b.employee_prep)?.first_name
),
render: (text, record) => (
<ProductionListEmployeeAssignment
@@ -517,8 +517,8 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
state.sortedInfo.columnKey === "employee_csr" && state.sortedInfo.order,
sorter: (a, b) =>
alphaSort(
bodyshop.employees.find((e) => e.id === a.employee_csr)?.first_name,
bodyshop.employees.find((e) => e.id === b.employee_csr)?.first_name
bodyshop.employees?.find((e) => e.id === a.employee_csr)?.first_name,
bodyshop.employees?.find((e) => e.id === b.employee_csr)?.first_name
),
render: (text, record) => (
<ProductionListEmployeeAssignment record={record} type="employee_csr" />
@@ -533,9 +533,9 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
state.sortedInfo.order,
sorter: (a, b) =>
alphaSort(
bodyshop.employees.find((e) => e.id === a.employee_refinish)
bodyshop.employees?.find((e) => e.id === a.employee_refinish)
?.first_name,
bodyshop.employees.find((e) => e.id === b.employee_refinish)
bodyshop.employees?.find((e) => e.id === b.employee_refinish)
?.first_name
),
render: (text, record) => (

View File

@@ -55,25 +55,27 @@ export function ProductionListPrint({ bodyshop }) {
<Menu.SubMenu
title={t("reportcenter.templates.production_by_technician_one")}
>
{bodyshop.employees.map((e) => (
<Menu.Item
key={e.id}
onClick={async () => {
setLoading(true);
await GenerateDocument(
{
name: production_by_technician_one.key,
variables: { id: e.id },
},
{},
"p"
);
setLoading(false);
}}
>
{e.first_name} {e.last_name}
</Menu.Item>
))}
{bodyshop.employees
.filter((e) => e.active)
.map((e) => (
<Menu.Item
key={e.id}
onClick={async () => {
setLoading(true);
await GenerateDocument(
{
name: production_by_technician_one.key,
variables: { id: e.id },
},
{},
"p"
);
setLoading(false);
}}
>
{e.first_name} {e.last_name}
</Menu.Item>
))}
</Menu.SubMenu>
<Menu.SubMenu
title={t("reportcenter.templates.production_by_category_one")}

View File

@@ -16,13 +16,17 @@ import { QUERY_LBR_HRS_BY_PK, UPDATE_JOBS } from "../../graphql/jobs.queries";
import { setEmailOptions } from "../../redux/email/email.actions";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectSchedule } from "../../redux/modals/modals.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import { TemplateList } from "../../utils/TemplateConstants";
import ScheduleJobModalComponent from "./schedule-job-modal.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
scheduleModal: selectSchedule,
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("schedule")),
@@ -34,6 +38,7 @@ export function ScheduleJobModalContainer({
bodyshop,
toggleModalVisible,
setEmailOptions,
currentUser,
}) {
const { visible, context, actions } = scheduleModal;
const { jobId, job, previousEvent } = context;
@@ -122,6 +127,7 @@ export function ScheduleJobModalContainer({
end: moment(values.start).add(bodyshop.appt_length || 60, "minutes"),
color: values.color,
note: values.note,
created_by: currentUser.email,
},
jobId: jobId,
altTransport: values.alt_transport,

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

@@ -16,13 +16,12 @@ const VehicleSearchSelect = ({ value, onChange, onBlur, disabled }, ref) => {
SEARCH_VEHICLES_FOR_AUTOCOMPLETE
);
const [
callIdSearch,
{ loading: idLoading, error: idError, data: idData },
] = useLazyQuery(SEARCH_VEHICLES_BY_ID_FOR_AUTOCOMPLETE);
const [callIdSearch, { loading: idLoading, error: idError, data: idData }] =
useLazyQuery(SEARCH_VEHICLES_BY_ID_FOR_AUTOCOMPLETE);
const executeSearch = (v) => {
callSearch(v);
if (v && v.variables?.search !== "" && v.variables.search.length >= 2)
callSearch(v);
};
const debouncedExecuteSearch = _.debounce(executeSearch, 500);

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

@@ -5,6 +5,7 @@ export const INSERT_NEW_BILL = gql`
insert_bills(objects: $bill) {
returning {
id
invoice_number
}
}
}

View File

@@ -283,6 +283,7 @@ export const GET_JOB_LINES_TO_ENTER_BILL = gql`
id
ro_number
}
v_make_desc
}
}
`;

View File

@@ -1299,7 +1299,7 @@ export const SEARCH_JOBS_FOR_AUTOCOMPLETE = gql`
) {
search_jobs(
args: { search: $search }
limit: 50
limit: 25
where: {
_and: {
converted: { _eq: $isConverted }

View File

@@ -36,7 +36,7 @@ export const SEARCH_OWNERS_FOR_AUTOCOMPLETE = gql`
query SEARCH_OWNERS_FOR_AUTOCOMPLETE($search: String) {
search_owners(
args: { search: $search }
limit: 50
limit: 25
order_by: { ownr_ln: desc_nulls_last }
) {
id

View File

@@ -154,7 +154,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
@@ -189,7 +189,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

@@ -184,7 +184,7 @@ export const SEARCH_VEHICLES_BY_ID_FOR_AUTOCOMPLETE = gql`
`;
export const SEARCH_VEHICLES_FOR_AUTOCOMPLETE = gql`
query SEARCH_VEHICLES_FOR_AUTOCOMPLETE($search: String) {
search_vehicles(args: { search: $search }, limit: 50) {
search_vehicles(args: { search: $search }, limit: 25) {
id
v_vin
v_model_yr

View File

@@ -104,6 +104,7 @@ export const SEARCH_VENDOR_AUTOCOMPLETE = gql`
id
cost_center
active
favorite
}
}
`;

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

@@ -6,6 +6,7 @@ const INITIAL_STATE = {
isSending: false,
error: null,
message: null,
searchingForConversation: false,
};
const messagingReducer = (state = INITIAL_STATE, action) => {
@@ -17,10 +18,16 @@ const messagingReducer = (state = INITIAL_STATE, action) => {
...state,
visible: !state.visible,
};
case MessagingActionTypes.OPEN_CHAT_BY_PHONE:
return {
...state,
searchingForConversation: true,
};
case MessagingActionTypes.SET_SELECTED_CONVERSATION:
return {
...state,
visible: true,
searchingForConversation: false,
selectedConversationId: action.payload,
};
case MessagingActionTypes.SEND_MESSAGE:

View File

@@ -4,7 +4,7 @@ import { all, call, put, select, takeLatest } from "redux-saga/effects";
import { logImEXEvent } from "../../firebase/firebase.utils";
import {
CONVERSATION_ID_BY_PHONE,
CREATE_CONVERSATION
CREATE_CONVERSATION,
} from "../../graphql/conversations.queries";
import { INSERT_CONVERSATION_TAG } from "../../graphql/job-conversations.queries";
import client from "../../utils/GraphQLClient";
@@ -12,7 +12,7 @@ import { selectBodyshop } from "../user/user.selectors";
import {
sendMessageFailure,
sendMessageSuccess,
setSelectedConversation
setSelectedConversation,
} from "./messaging.actions";
import MessagingActionTypes from "./messaging.types";
@@ -79,6 +79,7 @@ export function* openChatByPhone({ payload }) {
});
} else {
console.log("ERROR: Multiple conversations found. ");
yield put(setSelectedConversation(null));
}
} catch (error) {
console.log("Error in sendMessage saga.", error);

View File

@@ -26,3 +26,8 @@ export const selectMessage = createSelector(
[selectMessaging],
(messaging) => messaging.message
);
export const searchingForConversation = createSelector(
[selectMessaging],
(messaging) => messaging.searchingForConversation
);

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));
@@ -213,7 +212,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");
@@ -287,6 +285,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

@@ -27,6 +27,7 @@
"preview": "Preview",
"reschedule": "Reschedule",
"sendreminder": "Send Reminder",
"unblock": "Unblock",
"viewjob": "View Job"
},
"errors": {
@@ -1127,6 +1128,7 @@
"total": "Total",
"totals": "Totals",
"tuesday": "Tuesday",
"tvmode": "TV Mode",
"unknown": "Unknown",
"username": "Username",
"view": "View",
@@ -1500,6 +1502,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",
@@ -2660,18 +2663,22 @@
"gsr_labor_only": "Gross Sales - Labor Only",
"hours_sold_detail_closed": "Hours Sold Detail - Closed",
"hours_sold_detail_closed_csr": "Hours Sold Detail - Closed by CSR",
"hours_sold_detail_closed_estimator": "Hours Sold Detail - Closed by Estimator",
"hours_sold_detail_closed_ins_co": "Hours Sold Detail - Closed by Source",
"hours_sold_detail_closed_status": "Hours Sold Detail - Closed by Status",
"hours_sold_detail_open": "Hours Sold Detail - Open",
"hours_sold_detail_open_csr": "Hours Sold Detail - Open by CSR",
"hours_sold_detail_open_estimator": "Hours Sold Detail - Open by Estimator",
"hours_sold_detail_open_ins_co": "Hours Sold Detail - Open by Source",
"hours_sold_detail_open_status": "Hours Sold Detail - Open by Status",
"hours_sold_summary_closed": "Hours Sold Summary - Closed",
"hours_sold_summary_closed_csr": "Hours Sold Summary - Closed by CSR",
"hours_sold_summary_closed_estimator": "Hours Sold Summary - Closed by Estimator",
"hours_sold_summary_closed_ins_co": "Hours Sold Summary - Closed by Source",
"hours_sold_summary_closed_status": "Hours Sold Summary - Closed by Status",
"hours_sold_summary_open": "Hours Sold Summary - Open",
"hours_sold_summary_open_csr": "Hours Sold Summary - Open CSR",
"hours_sold_summary_open_estimator": "Hours Sold Summary - Open Estimator",
"hours_sold_summary_open_ins_co": "Hours Sold Summary - Open by Source",
"hours_sold_summary_open_status": "Hours Sold Summary - Open by Status",
"job_costing_ro_csr": "Job Costing by CSR",
@@ -2688,6 +2695,7 @@
"open_orders_csr": "Open Orders by CSR",
"open_orders_estimator": "Open Orders by Estimator",
"open_orders_ins_co": "Open Orders by Insurance Company",
"open_orders_referral": "Open Orders by Referral Source",
"open_orders_specific_csr": "Open Orders filtered by CSR",
"open_orders_status": "Open Orders by Status",
"parts_backorder": "IOU Parts List",
@@ -2763,8 +2771,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",
@@ -2773,12 +2785,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

@@ -27,6 +27,7 @@
"preview": "",
"reschedule": "Reprogramar",
"sendreminder": "",
"unblock": "",
"viewjob": "Ver trabajo"
},
"errors": {
@@ -1127,6 +1128,7 @@
"total": "",
"totals": "",
"tuesday": "",
"tvmode": "",
"unknown": "Desconocido",
"username": "",
"view": "",
@@ -1500,6 +1502,7 @@
"cost_dms_acctnumber": "",
"dms_make": "",
"dms_model": "",
"dms_model_override": "",
"dms_unsold": "",
"dms_wip_acctnumber": "",
"id": "",
@@ -2660,18 +2663,22 @@
"gsr_labor_only": "",
"hours_sold_detail_closed": "",
"hours_sold_detail_closed_csr": "",
"hours_sold_detail_closed_estimator": "",
"hours_sold_detail_closed_ins_co": "",
"hours_sold_detail_closed_status": "",
"hours_sold_detail_open": "",
"hours_sold_detail_open_csr": "",
"hours_sold_detail_open_estimator": "",
"hours_sold_detail_open_ins_co": "",
"hours_sold_detail_open_status": "",
"hours_sold_summary_closed": "",
"hours_sold_summary_closed_csr": "",
"hours_sold_summary_closed_estimator": "",
"hours_sold_summary_closed_ins_co": "",
"hours_sold_summary_closed_status": "",
"hours_sold_summary_open": "",
"hours_sold_summary_open_csr": "",
"hours_sold_summary_open_estimator": "",
"hours_sold_summary_open_ins_co": "",
"hours_sold_summary_open_status": "",
"job_costing_ro_csr": "",
@@ -2688,6 +2695,7 @@
"open_orders_csr": "",
"open_orders_estimator": "",
"open_orders_ins_co": "",
"open_orders_referral": "",
"open_orders_specific_csr": "",
"open_orders_status": "",
"parts_backorder": "",
@@ -2763,8 +2771,12 @@
"painthrs": ""
},
"labels": {
"allemployeetimetickets": "",
"asoftodaytarget": "",
"body": "",
"bodycharttitle": "",
"calendarperiod": "",
"combinedcharttitle": "",
"dailyactual": "",
"dailytarget": "",
"efficiencyoverperiod": "",
@@ -2773,12 +2785,16 @@
"lastmonth": "",
"lastweek": "",
"monthlytarget": "",
"priorweek": "",
"productivestatistics": "",
"productivetimeticketsoverdate": "",
"refinish": "",
"refinishcharttitle": "",
"targets": "",
"thismonth": "",
"thisweek": "",
"timetickets": "",
"timeticketsemployee": "",
"todateactual": "",
"totaloverperiod": "",
"weeklyactual": "",

View File

@@ -27,6 +27,7 @@
"preview": "",
"reschedule": "Replanifier",
"sendreminder": "",
"unblock": "",
"viewjob": "Voir le travail"
},
"errors": {
@@ -1127,6 +1128,7 @@
"total": "",
"totals": "",
"tuesday": "",
"tvmode": "",
"unknown": "Inconnu",
"username": "",
"view": "",
@@ -1500,6 +1502,7 @@
"cost_dms_acctnumber": "",
"dms_make": "",
"dms_model": "",
"dms_model_override": "",
"dms_unsold": "",
"dms_wip_acctnumber": "",
"id": "",
@@ -2660,18 +2663,22 @@
"gsr_labor_only": "",
"hours_sold_detail_closed": "",
"hours_sold_detail_closed_csr": "",
"hours_sold_detail_closed_estimator": "",
"hours_sold_detail_closed_ins_co": "",
"hours_sold_detail_closed_status": "",
"hours_sold_detail_open": "",
"hours_sold_detail_open_csr": "",
"hours_sold_detail_open_estimator": "",
"hours_sold_detail_open_ins_co": "",
"hours_sold_detail_open_status": "",
"hours_sold_summary_closed": "",
"hours_sold_summary_closed_csr": "",
"hours_sold_summary_closed_estimator": "",
"hours_sold_summary_closed_ins_co": "",
"hours_sold_summary_closed_status": "",
"hours_sold_summary_open": "",
"hours_sold_summary_open_csr": "",
"hours_sold_summary_open_estimator": "",
"hours_sold_summary_open_ins_co": "",
"hours_sold_summary_open_status": "",
"job_costing_ro_csr": "",
@@ -2688,6 +2695,7 @@
"open_orders_csr": "",
"open_orders_estimator": "",
"open_orders_ins_co": "",
"open_orders_referral": "",
"open_orders_specific_csr": "",
"open_orders_status": "",
"parts_backorder": "",
@@ -2763,8 +2771,12 @@
"painthrs": ""
},
"labels": {
"allemployeetimetickets": "",
"asoftodaytarget": "",
"body": "",
"bodycharttitle": "",
"calendarperiod": "",
"combinedcharttitle": "",
"dailyactual": "",
"dailytarget": "",
"efficiencyoverperiod": "",
@@ -2773,12 +2785,16 @@
"lastmonth": "",
"lastweek": "",
"monthlytarget": "",
"priorweek": "",
"productivestatistics": "",
"productivetimeticketsoverdate": "",
"refinish": "",
"refinishcharttitle": "",
"targets": "",
"thismonth": "",
"thisweek": "",
"timetickets": "",
"timeticketsemployee": "",
"todateactual": "",
"totaloverperiod": "",
"weeklyactual": "",

View File

@@ -1,15 +1,14 @@
import { gql } from "@apollo/client";
import { notification } from "antd";
import jsreport from "@jsreport/browser-client";
import { notification } from "antd";
import axios from "axios";
import _ from "lodash";
import moment from "moment";
import { auth } from "../firebase/firebase.utils";
import { setEmailOptions } from "../redux/email/email.actions";
import { store } from "../redux/store";
import client from "../utils/GraphQLClient";
import { TemplateList } from "./TemplateConstants";
import cleanAxios from "./CleanAxios";
import axios from "axios";
import { TemplateList } from "./TemplateConstants";
const server = process.env.REACT_APP_REPORTS_SERVER_URL;
jsreport.serverUrl = server;
@@ -255,7 +254,7 @@ export async function RenderTemplates(
headerpath: `/${bodyshop.imexshopid}/header.html`,
footerpath: `/${bodyshop.imexshopid}/footer.html`,
bodyshop: bodyshop,
offset: moment().utcOffset(),
offset: bodyshop.timezone,
},
};

View File

@@ -907,6 +907,74 @@ export const TemplateList = (type, context) => {
},
group: "sales",
},
hours_sold_detail_closed_estimator: {
title: i18n.t(
"reportcenter.templates.hours_sold_detail_closed_estimator"
),
description: "",
subject: i18n.t(
"reportcenter.templates.hours_sold_detail_closed_estimator"
),
key: "hours_sold_detail_closed_estimator",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_invoiced"),
},
group: "sales",
},
hours_sold_detail_open_estimator: {
title: i18n.t(
"reportcenter.templates.hours_sold_detail_open_estimator"
),
description: "",
subject: i18n.t(
"reportcenter.templates.hours_sold_detail_open_estimator"
),
key: "hours_sold_detail_open_estimator",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_open"),
},
group: "sales",
},
hours_sold_summary_closed_estimator: {
title: i18n.t(
"reportcenter.templates.hours_sold_summary_closed_estimator"
),
description: "",
subject: i18n.t(
"reportcenter.templates.hours_sold_summary_closed_estimator"
),
key: "hours_sold_summary_closed_estimator",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_invoiced"),
},
group: "sales",
},
hours_sold_summary_open_estimator: {
title: i18n.t(
"reportcenter.templates.hours_sold_summary_open_estimator"
),
description: "",
subject: i18n.t(
"reportcenter.templates.hours_sold_summary_open_estimator"
),
key: "hours_sold_summary_open_estimator",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_invoiced"),
},
group: "sales",
},
hours_sold_summary_open_status: {
title: i18n.t(
"reportcenter.templates.hours_sold_summary_open_status"
@@ -1523,6 +1591,19 @@ export const TemplateList = (type, context) => {
},
group: "jobs",
},
open_orders_specific_csr: {
title: i18n.t("reportcenter.templates.open_orders_specific_csr"),
description: "",
subject: i18n.t("reportcenter.templates.open_orders_specific_csr"),
key: "open_orders_specific_csr",
idtype: "employee",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_open"),
},
group: "jobs",
},
open_orders_estimator: {
title: i18n.t("reportcenter.templates.open_orders_estimator"),
description: "",
@@ -1549,12 +1630,12 @@ export const TemplateList = (type, context) => {
},
group: "jobs",
},
open_orders_specific_csr: {
title: i18n.t("reportcenter.templates.open_orders_specific_csr"),
open_orders_referral: {
title: i18n.t("reportcenter.templates.open_orders_referral"),
description: "",
subject: i18n.t("reportcenter.templates.open_orders_specific_csr"),
key: "open_orders_specific_csr",
idtype: "employee",
subject: i18n.t("reportcenter.templates.open_orders_referral"),
key: "open_orders_referral",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),

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

@@ -110,6 +110,7 @@
- canceled
- color
- created_at
- created_by
- end
- id
- isintake
@@ -128,6 +129,7 @@
- canceled
- color
- created_at
- created_by
- end
- id
- isintake
@@ -156,6 +158,7 @@
- canceled
- color
- created_at
- created_by
- end
- id
- isintake
@@ -4076,12 +4079,7 @@
event_triggers:
- name: job_status_transition
definition:
enable_manual: false
insert:
columns: '*'
update:
columns:
- status
enable_manual: true
retry_conf:
interval_sec: 10
num_retries: 0
@@ -4097,23 +4095,7 @@
version: 2
- name: jobs_arms
definition:
enable_manual: false
update:
columns:
- actual_delivery
- scheduled_delivery
- scheduled_completion
- actual_completion
- date_scheduled
- inproduction
- clm_total
- suspended
- job_totals
- converted
- employee_body
- ro_number
- actual_in
- scheduled_in
enable_manual: true
retry_conf:
interval_sec: 10
num_retries: 0

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."appointments" add column "created_by" text
-- null;

View File

@@ -0,0 +1,2 @@
alter table "public"."appointments" add column "created_by" text
null;

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

@@ -62,7 +62,7 @@ exports.default = async (req, res) => {
start: start
? moment(start).startOf("day")
: moment().subtract(5, "days").startOf("day"),
...(end && { end: moment(end).startOf("day") }),
...(end && { end: moment(end).endOf("day") }),
}
);

View File

@@ -17,7 +17,7 @@ let Client = require("ssh2-sftp-client");
const client = require("../graphql-client/graphql-client").client;
const { sendServerEmail } = require("../email/sendemail");
const CCDineroFormat = "0,0.00";
const AhDateFormat = "MMDDYYYY";
const AhDateFormat = "MM/DD/YYYY";
const repairOpCodes = ["OP4", "OP9", "OP10"];
const replaceOpCodes = ["OP2", "OP5", "OP11", "OP12"];
@@ -62,22 +62,24 @@ exports.default = async (req, res) => {
start: start
? moment(start).startOf("day")
: moment().subtract(5, "days").startOf("day"),
...(end && { end: moment(end).startOf("day") }),
...(end && { end: moment(end).endOf("day") }),
}
);
const claimsCorpObject = {
ClaimsCorpExport: {
ShopID: bodyshops_by_pk.claimscorpid,
ShopName: bodyshops_by_pk.shopname,
RO: jobs.map((j) =>
CreateRepairOrderTag(
{ ...j, bodyshop: bodyshops_by_pk },
function ({ job, error }) {
erroredJobs.push({ job: job, error: error.toString() });
}
)
),
DataFeed: {
ShopInfo: {
ShopID: bodyshops_by_pk.claimscorpid,
ShopName: bodyshops_by_pk.shopname,
RO: jobs.map((j) =>
CreateRepairOrderTag(
{ ...j, bodyshop: bodyshops_by_pk },
function ({ job, error }) {
erroredJobs.push({ job: job, error: error.toString() });
}
)
),
},
},
};
@@ -100,9 +102,9 @@ exports.default = async (req, res) => {
.end({ allowEmptyTags: true });
allxmlsToUpload.push({
count: claimsCorpObject.ClaimsCorpExport.RO.length,
count: claimsCorpObject.DataFeed.ShopInfo.RO.length,
xml: ret,
filename: `${bodyshop.claimscorpid}-MIS-${moment().format(
filename: `${bodyshop.claimscorpid}-${moment().format(
"YYYYMMDDTHHMMss"
)}.xml`,
});
@@ -243,7 +245,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
try {
const ret = {
RONumber: job.ro_number,
RoNumber: job.ro_number,
Customer: {
CustomerZip: (job.ownr_zip && job.ownr_zip.substring(0, 3)) || "",
CustomerState: job.ownr_st || "",
@@ -261,8 +263,8 @@ const CreateRepairOrderTag = (job, errorCallback) => {
VIN: job.v_vin || "",
},
Carrier: {
InsuranceCo: job.ins_co_nm || "",
CompanyName: job.ins_co_nm || "",
UniqueID: job.ins_co_nm || "",
InsuranceCompany: job.ins_co_nm || "",
},
Claim: job.clm_no || "",
Contacts: {
@@ -279,7 +281,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
: "",
Phone1: "",
Phone2: "",
EstimatorName: `${job.est_ct_ln ? job.est_ct_ln : ""}${
Estimator: `${job.est_ct_ln ? job.est_ct_ln : ""}${
job.est_ct_ln ? ", " : ""
}${job.est_ct_fn ? job.est_ct_fn : ""}`,
BodyTechnician: job.employee_body_rel
@@ -310,7 +312,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
(job.date_estimated &&
moment(job.date_estimated).format(AhDateFormat)) ||
"",
DateofLoss:
DateLoss:
(job.loss_date && moment(job.loss_date).format(AhDateFormat)) || "",
DateFNOL: "",
DateContact: "",
@@ -337,7 +339,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
StartDate: job.date_repairstarted
DateStart: job.date_repairstarted
? (job.date_repairstarted &&
moment(job.date_repairstarted)
.tz(job.bodyshop.timezone)
@@ -382,7 +384,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
"",
BilledDate: "",
PaidInFullDate: "",
RoStatus: job.tlos_ind
ROStatus: job.tlos_ind
? "TOT"
: StatusMapping(job.status, job.bodyshop.md_ro_statuses),
},
@@ -392,14 +394,10 @@ const CreateRepairOrderTag = (job, errorCallback) => {
.add(Dinero(job.job_totals.rates.lad.total))
.add(Dinero(job.job_totals.rates.las.total))
.toFormat(CCDineroFormat),
Refinish: Dinero(job.job_totals.rates.lar.total).toFormat(
CCDineroFormat
),
Paint: Dinero(job.job_totals.rates.lar.total).toFormat(CCDineroFormat),
Prep: Dinero().toFormat(CCDineroFormat),
Frame: Dinero(job.job_totals.rates.laf.total).toFormat(CCDineroFormat),
Mechanical: Dinero(job.job_totals.rates.lam.total).toFormat(
CCDineroFormat
),
Mech: Dinero(job.job_totals.rates.lam.total).toFormat(CCDineroFormat),
Glass: Dinero(job.job_totals.rates.lag.total).toFormat(CCDineroFormat),
Elec: Dinero(job.job_totals.rates.lae.total).toFormat(CCDineroFormat),
Detail: detailAdjustments.amount.toFormat(CCDineroFormat),
@@ -446,6 +444,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
Towing: Dinero(job.job_totals.additional.towing).toFormat(
CCDineroFormat
),
Storage: "0.00",
Rental:
job.job_totals.additional.additionalCostItems.includes(
"ATS Amount"
@@ -480,17 +479,17 @@ const CreateRepairOrderTag = (job, errorCallback) => {
.filter((line) => repairOpCodes.includes(line.lbr_op))
.reduce((acc, val) => acc + val.mod_lb_hrs, 0)
.toFixed(2),
BodyReplaceHours: job.joblines
BodyReplacehours: job.joblines
.filter((line) => replaceOpCodes.includes(line.lbr_op))
.reduce((acc, val) => acc + val.mod_lb_hrs, 0)
.toFixed(2),
Paint: job.job_totals.rates.lar.hours.toFixed(2),
Prep: "0.00",
FrameHours: job.job_totals.rates.laf.hours.toFixed(2),
MechanicalHours: job.job_totals.rates.lam.hours.toFixed(2),
GlassHours: job.job_totals.rates.lag.hours.toFixed(2),
ElectricalHours: job.job_totals.rates.lae.hours.toFixed(2),
DetailHours: detailAdjustments.hours,
Frame: job.job_totals.rates.laf.hours.toFixed(2),
Mech: job.job_totals.rates.lam.hours.toFixed(2),
Glass: job.job_totals.rates.lag.hours.toFixed(2),
Elec: job.job_totals.rates.lae.hours.toFixed(2),
Detail: detailAdjustments.hours,
Reassem: "0.00",
Other: (
job.job_totals.rates.la1.hours +
@@ -522,7 +521,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
AM: repairCosts.PartsAMCost.toFormat(CCDineroFormat),
MechParts: Dinero().toFormat(CCDineroFormat),
OtherParts: Dinero().toFormat(CCDineroFormat), //Check Synergy
OtherCosts: repairCosts.PartsOtherCost.toFormat(CCDineroFormat),
OtherCost: repairCosts.PartsOtherCost.toFormat(CCDineroFormat),
Sublet: repairCosts.SubletTotalCost.toFormat(CCDineroFormat),
Towing: repairCosts.TowingTotalCost.toFormat(CCDineroFormat),
Storage: repairCosts.StorageTotalCost.toFormat(CCDineroFormat),

View File

@@ -1,16 +1,12 @@
exports.FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID = `
query FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID(
$mssid: String!
$phone: String!
) {
query FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID($mssid: String!, $phone: String!) {
bodyshops(where: { messagingservicesid: { _eq: $mssid } }) {
id
conversations(where: { phone_num: { _eq: $phone } }) {
id
}
}
}
`;
}`;
exports.GET_JOB_BY_RO_NUMBER = `
query GET_JOB_BY_RO_NUMBER($ro_number: String!) {
@@ -20,8 +16,7 @@ exports.GET_JOB_BY_RO_NUMBER = `
id
}
}
}
`;
}`;
exports.INSERT_NEW_PAYMENT = `
mutation INSERT_NEW_PAYMENT($paymentInput: [payments_insert_input!]!) {
@@ -30,28 +25,23 @@ exports.INSERT_NEW_PAYMENT = `
id
}
}
}
`;
}`;
exports.INSERT_PAYMENT_RESPONSE = `
mutation INSERT_PAYMENT_RESPONSE(
$paymentResponse: [payment_response_insert_input!]!
) {
mutation INSERT_PAYMENT_RESPONSE($paymentResponse: [payment_response_insert_input!]!) {
insert_payment_response(objects: $paymentResponse) {
returning {
id
}
}
}
`;
}`;
exports.UNARCHIVE_CONVERSATION = `
mutation UNARCHIVE_CONVERSATION($id: uuid!) {
update_conversations_by_pk(pk_columns: {id: $id}, _set: {archived: false}) {
id
}
}
`;
}`;
exports.INSERT_NEW_JOB_LINE = `
mutation INSERT_NEW_JOB_LINE($lineInput: [joblines_insert_input!]!) {
@@ -60,8 +50,7 @@ exports.INSERT_NEW_JOB_LINE = `
id
}
}
}
`;
}`;
exports.UPDATE_JOB_LINE = `
mutation UPDATE_JOB_LINE($lineId: uuid!, $line: joblines_set_input!) {
@@ -82,8 +71,7 @@ exports.UPDATE_JOB_LINE = `
removed
}
}
}
`;
}`;
exports.RECEIVE_MESSAGE = `
mutation RECEIVE_MESSAGE($msg: [messages_insert_input!]!) {
@@ -92,7 +80,7 @@ mutation RECEIVE_MESSAGE($msg: [messages_insert_input!]!) {
conversation {
id
archived
bodyshop{
bodyshop {
imexshopid
}
created_at
@@ -114,10 +102,7 @@ mutation RECEIVE_MESSAGE($msg: [messages_insert_input!]!) {
userid
}
}
}
`;
}`;
exports.INSERT_MESSAGE = `
mutation INSERT_MESSAGE($msg: [messages_insert_input!]!, $conversationid: uuid!) {
@@ -130,7 +115,7 @@ mutation INSERT_MESSAGE($msg: [messages_insert_input!]!, $conversationid: uuid!)
conversation {
id
archived
bodyshop{
bodyshop {
imexshopid
}
created_at
@@ -152,10 +137,7 @@ mutation INSERT_MESSAGE($msg: [messages_insert_input!]!, $conversationid: uuid!)
userid
}
}
}
`;
}`;
exports.UPDATE_MESSAGE_STATUS = `
mutation UPDATE_MESSAGE($msid: String!, $fields: messages_set_input!) {
@@ -164,8 +146,7 @@ mutation UPDATE_MESSAGE($msid: String!, $fields: messages_set_input!) {
id
}
}
}
`;
}`;
exports.QUERY_JOBS_FOR_RECEIVABLES_EXPORT = `
query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
@@ -230,7 +211,7 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
owner {
accountingid
}
joblines(where:{removed: {_eq:false}}) {
joblines(where:{removed: {_eq:false}}) {
id
line_desc
part_type
@@ -260,8 +241,7 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
timezone
md_ro_statuses
}
}
`;
}`;
exports.QUERY_JOBS_FOR_CDK_EXPORT = `
query QUERY_JOBS_FOR_CDK_EXPORT($id: uuid!) {
@@ -321,19 +301,19 @@ query QUERY_JOBS_FOR_CDK_EXPORT($id: uuid!) {
v_make_desc
v_color
ca_customer_gst
bodyshop {
id
md_ro_statuses
md_responsibility_centers
accountingconfig
cdk_dealerid
cdk_configuration
timezone
}
bodyshop {
id
md_ro_statuses
md_responsibility_centers
accountingconfig
cdk_dealerid
cdk_configuration
timezone
}
owner {
accountingid
}
joblines(where:{removed: {_eq:false}}) {
joblines(where:{removed: {_eq:false}}) {
id
line_desc
part_type
@@ -348,9 +328,7 @@ query QUERY_JOBS_FOR_CDK_EXPORT($id: uuid!) {
prt_dsmk_p
}
}
}
`;
}`;
exports.QUERY_JOBS_FOR_PBS_EXPORT = `
query QUERY_JOBS_FOR_PBS_EXPORT($id: uuid!) {
@@ -413,24 +391,24 @@ query QUERY_JOBS_FOR_PBS_EXPORT($id: uuid!) {
v_make_desc
v_color
ca_customer_gst
vehicle{
vehicle {
v_trimcode
v_makecode
}
bodyshop {
id
md_ro_statuses
md_responsibility_centers
accountingconfig
pbs_serialnumber
pbs_configuration
timezone
}
bodyshop {
id
md_ro_statuses
md_responsibility_centers
accountingconfig
pbs_serialnumber
pbs_configuration
timezone
}
owner {
id
accountingid
}
joblines(where:{removed: {_eq:false}}) {
joblines(where:{removed: {_eq:false}}) {
id
line_desc
part_type
@@ -447,9 +425,7 @@ query QUERY_JOBS_FOR_PBS_EXPORT($id: uuid!) {
line_ref
}
}
}
`;
}`;
exports.QUERY_BILLS_FOR_PAYABLES_EXPORT = `
query QUERY_BILLS_FOR_PAYABLES_EXPORT($bills: [uuid!]!) {
@@ -493,18 +469,17 @@ query QUERY_BILLS_FOR_PAYABLES_EXPORT($bills: [uuid!]!) {
due_date
}
}
}
`;
}`;
exports.QUERY_PAYMENTS_FOR_EXPORT = `
query QUERY_PAYMENTS_FOR_EXPORT($payments: [uuid!]!)
{ bodyshops(where: {associations: {active: {_eq: true}}}) {
id
md_responsibility_centers
accountingconfig
timezone
md_ins_cos
}
query QUERY_PAYMENTS_FOR_EXPORT($payments: [uuid!]!) {
bodyshops(where: {associations: {active: {_eq: true}}}) {
id
md_responsibility_centers
accountingconfig
timezone
md_ins_cos
}
payments(where: {id: {_in: $payments}}) {
id
created_at
@@ -512,15 +487,14 @@ exports.QUERY_PAYMENTS_FOR_EXPORT = `
job {
id
ro_number
ownerid
ownr_ln
ownr_fn
ownr_addr1
ownr_addr2
ownr_zip
ownr_city
ownr_st
ownerid
ownr_ln
ownr_fn
ownr_addr1
ownr_addr2
ownr_zip
ownr_city
ownr_st
ins_co_nm
owner{
accountingid
@@ -528,7 +502,7 @@ exports.QUERY_PAYMENTS_FOR_EXPORT = `
ownr_fn
ownr_ln
ownr_co_nm
bodyshop{
bodyshop {
accountingconfig
md_responsibility_centers
md_ins_cos
@@ -545,8 +519,7 @@ exports.QUERY_PAYMENTS_FOR_EXPORT = `
paymentnum
date
}
}
`;
}`;
exports.QUERY_UPCOMING_APPOINTMENTS = `query QUERY_UPCOMING_APPOINTMENTS($now: timestamptz!, $jobId: uuid!) {
jobs_by_pk(id: $jobId) {
@@ -646,8 +619,7 @@ exports.QUERY_UPCOMING_APPOINTMENTS = `query QUERY_UPCOMING_APPOINTMENTS($now: t
}
}
}
}
`;
}`;
exports.QUERY_EMPLOYEE_PIN = `query QUERY_EMPLOYEE_PIN($shopId: uuid!, $employeeId: String!) {
employees(where: {_and: {shopid: {_eq: $shopId}, employee_number: {_eq: $employeeId}}}) {
@@ -838,8 +810,7 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshop
totalliquidcost
}
}
}
`;
}`;
exports.CLAIMSCORP_QUERY = `query CLAIMSCORP_EXPORT($start: timestamptz, $bodyshopid: uuid!, $end: timestamptz) {
bodyshops_by_pk(id: $bodyshopid){
@@ -1011,8 +982,7 @@ exports.CLAIMSCORP_QUERY = `query CLAIMSCORP_EXPORT($start: timestamptz, $bodysh
totalliquidcost
}
}
}
`;
}`;
exports.ENTEGRAL_EXPORT = `
query ENTEGRAL_EXPORT($bodyshopid: uuid!) {
@@ -1101,8 +1071,7 @@ query ENTEGRAL_EXPORT($bodyshopid: uuid!) {
cieca_ttl
adjustment_bottom_line
}
}
`;
}`;
exports.UPDATE_JOB = `
mutation UPDATE_JOB($jobId: uuid!, $job: jobs_set_input!) {
@@ -1117,8 +1086,7 @@ exports.UPDATE_JOB = `
lbr_adjustments
}
}
}
`;
}`;
exports.GET_JOB_BY_PK = `query GET_JOB_BY_PK($id: uuid!) {
jobs_by_pk(id: $id) {
@@ -1131,7 +1099,7 @@ exports.GET_JOB_BY_PK = `query GET_JOB_BY_PK($id: uuid!) {
kmout
comment
referral_source
referral_source_extra
referral_source_extra
unit_number
po_number
special_coverage_policy
@@ -1170,6 +1138,10 @@ exports.GET_JOB_BY_PK = `query GET_JOB_BY_PK($id: uuid!) {
cieca_pfl
cieca_pft
cieca_pfo
vehicle {
id
notes
}
est_ph1
est_ea
selling_dealer
@@ -1518,19 +1490,17 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT
use_paint_scale_data
}
}
}
`;
}`;
exports.INSERT_IOEVENT = ` mutation INSERT_IOEVENT($event: ioevents_insert_input!) {
insert_ioevents_one(object: $event) {
id
}
}
`;
}`;
exports.GET_AUTOHOUSE_SHOPS = `query GET_AUTOHOUSE_SHOPS {
bodyshops(where: {autohouseid: {_is_null: false}}){
id
bodyshops(where: {autohouseid: {_is_null: false}, _or: {autohouseid: {_neq: ""}}}){
id
shopname
address1
city
@@ -1546,11 +1516,10 @@ exports.GET_AUTOHOUSE_SHOPS = `query GET_AUTOHOUSE_SHOPS {
imexshopid
timezone
}
}
`;
}`;
exports.GET_CLAIMSCORP_SHOPS = `query GET_CLAIMSCORP_SHOPS {
bodyshops(where: {claimscorpid: {_is_null: false}}){
bodyshops(where: {claimscorpid: {_is_null: false}, _or: {claimscorpid: {_neq: ""}}}){
id
shopname
address1
@@ -1567,12 +1536,11 @@ exports.GET_CLAIMSCORP_SHOPS = `query GET_CLAIMSCORP_SHOPS {
imexshopid
timezone
}
}
`;
}`;
exports.GET_ENTEGRAL_SHOPS = `query GET_AUTOHOUSE_SHOPS {
bodyshops(where: {entegral_id: {_is_null: false}}){
id
bodyshops(where: {entegral_id: {_is_null: false}, _or: {entegral_id: {_neq: ""}}}){
id
shopname
address1
city
@@ -1588,22 +1556,19 @@ exports.GET_ENTEGRAL_SHOPS = `query GET_AUTOHOUSE_SHOPS {
timezone
features
}
}
`;
}`;
exports.DELETE_ALL_DMS_VEHICLES = `mutation DELETE_ALL_DMS_VEHICLES{
delete_dms_vehicles(where: {}) {
affected_rows
}
}
`;
}`;
exports.INSERT_DMS_VEHICLES = `mutation INSERT_DMS_VEHICLES($vehicles: [dms_vehicles_insert_input!]!) {
insert_dms_vehicles(objects: $vehicles) {
affected_rows
}
}
`;
}`;
exports.GET_CDK_ALLOCATIONS = `query QUERY_JOB_CLOSE_DETAILS($id: uuid!) {
jobs_by_pk(id: $id) {
@@ -1720,8 +1685,7 @@ exports.GET_CDK_ALLOCATIONS = `query QUERY_JOB_CLOSE_DETAILS($id: uuid!) {
unq_seq
}
}
}
`;
}`;
exports.GET_QBO_AUTH = `query GET_QBO_AUTH($email: String!) {
associations(where: {_and: {active: {_eq: true}, useremail: {_eq: $email}}}){
@@ -1735,15 +1699,13 @@ exports.SET_QBO_AUTH_WITH_REALM = `mutation SET_QBO_AUTH($email: String!, $qbo_a
update_associations(_set: {qbo_auth: $qbo_auth, qbo_realmId: $qbo_realmId}, where: {_and: {active: {_eq: true}, useremail: {_eq: $email}}}){
affected_rows
}
}
`;
}`;
exports.SET_QBO_AUTH = `mutation SET_QBO_AUTH($email: String!, $qbo_auth: jsonb!) {
update_associations(_set: {qbo_auth: $qbo_auth}, where: {_and: {active: {_eq: true}, useremail: {_eq: $email}}}){
affected_rows
}
}
`;
}`;
exports.MARK_JOB_EXPORTED = `
mutation MARK_JOB_EXPORTED($jobId: uuid!, $job: jobs_set_input!, $log: exportlog_insert_input!, $bill: bills_set_input!) {
@@ -1762,10 +1724,9 @@ mutation MARK_JOB_EXPORTED($jobId: uuid!, $job: jobs_set_input!, $log: exportlog
id
}
update_bills(where:{jobid:{_eq :$jobId}}, _set:$bill){
affected_rows
affected_rows
}
}
`;
}`;
exports.MARK_BILLS_EXPORTED = `
mutation UPDATE_BILLS($billids: [uuid!]!, $bill: bills_set_input!, $logs: [exportlog_insert_input!]!) {
@@ -1777,28 +1738,25 @@ mutation UPDATE_BILLS($billids: [uuid!]!, $bill: bills_set_input!, $logs: [expor
}
}
insert_exportlog(objects: $logs) {
returning{
id
}
returning{
id
}
}
}
`;
}`;
exports.INSERT_EXPORT_LOG = `
mutation INSERT_EXPORT_LOG($log: exportlog_insert_input!) {
insert_exportlog_one(object: $log) {
id
}
}
`;
}`;
exports.QUERY_EXISTING_TRANSITION = `
mutation INSERT_EXPORT_LOG($log: exportlog_insert_input!) {
insert_exportlog_one(object: $log) {
id
}
}
`;
}`;
exports.UPDATE_OLD_TRANSITION = `mutation UPDATE_OLD_TRANSITION($jobid: uuid!, $existingTransition: transitions_set_input!){
update_transitions(where:{jobid:{_eq:$jobid}, end:{_is_null:true
@@ -1822,9 +1780,7 @@ exports.INSERT_NEW_TRANSITION = `mutation INSERT_NEW_TRANSITION($newTransition:
update_transitions(where: {id: {_eq: $oldTransitionId}}, _set: {duration: $duration}) {
affected_rows
}
}
`;
}`;
exports.QUERY_JOB_ID_MIXDATA = `query QUERY_JOB_ID_MIXDATA($roNumbers: [String!]!) {
jobs(where: {ro_number: {_in: $roNumbers}}) {
@@ -1834,9 +1790,7 @@ exports.QUERY_JOB_ID_MIXDATA = `query QUERY_JOB_ID_MIXDATA($roNumbers: [String!]
id
}
}
}
`;
}`;
exports.QBO_MARK_JOB_EXPORTED = `
mutation QBO_MARK_JOB_EXPORTED($jobId: uuid!, $job: jobs_set_input!, $logs: [exportlog_insert_input!]!) {
@@ -1849,9 +1803,8 @@ mutation QBO_MARK_JOB_EXPORTED($jobId: uuid!, $job: jobs_set_input!, $logs: [exp
}
}
}
}`;
`;
exports.QBO_MARK_BILL_EXPORTED = `
mutation QBO_MARK_BILL_EXPORTED($billId: uuid!, $bill: bills_set_input!, $logs: [exportlog_insert_input!]!) {
insert_exportlog(objects: $logs) {
@@ -1862,9 +1815,7 @@ mutation QBO_MARK_BILL_EXPORTED($billId: uuid!, $bill: bills_set_input!, $logs:
id
}
}
}
`;
}`;
exports.QBO_MARK_PAYMENT_EXPORTED = `
mutation QBO_MARK_PAYMENT_EXPORTED($paymentId: uuid!, $payment: payments_set_input!, $logs: [exportlog_insert_input!]!) {
@@ -1879,19 +1830,17 @@ mutation QBO_MARK_PAYMENT_EXPORTED($paymentId: uuid!, $payment: payments_set_inp
}`;
exports.INSERT_EXPORT_LOG = `
mutation INSERT_EXPORT_LOG($logs: [exportlog_insert_input!]!) {
insert_exportlog(objects: $logs) {
affected_rows
}
mutation INSERT_EXPORT_LOG($logs: [exportlog_insert_input!]!) {
insert_exportlog(objects: $logs) {
affected_rows
}
`;
}`;
exports.INSERT_EMAIL_AUDIT = `mutation INSERT_EMAIL_AUDIT($email: email_audit_trail_insert_input!) {
insert_email_audit_trail_one(object: $email) {
id
}
}
`;
}`;
exports.DELETE_MEDIA_DOCUMENTS = `
mutation DELETE_DOCUMENTS($ids: [uuid!]!) {
@@ -1900,8 +1849,7 @@ mutation DELETE_DOCUMENTS($ids: [uuid!]!) {
id
}
}
}
`;
}`;
exports.UPDATE_EMAIL_AUDIT = `
mutation ($sesid: String!, $status: String, $context: jsonb) {
@@ -1953,8 +1901,7 @@ query GET_PBS_AP_ALLOCATIONS($billids: [uuid!]) {
quantity
}
}
}
`;
}`;
exports.GET_JOB_FOR_PPC = `query GET_JOB_FOR_PPC($jobid: uuid!) {
jobs_by_pk(id: $jobid) {
@@ -1986,8 +1933,7 @@ exports.QUERY_PARTS_SCAN = `query QUERY_PARTS_SCAN ($id: uuid!) {
critical
}
}
}
`;
}`;
exports.UPDATE_PARTS_CRITICAL = `mutation UPDATE_PARTS_CRITICAL ($IdsToMarkCritical:[uuid!]!, $jobid: uuid!){
critical: update_joblines(where:{id:{_in:$IdsToMarkCritical}}, _set:{critical: true}){
@@ -2003,8 +1949,7 @@ exports.ACTIVE_SHOP_BY_USER = `query ACTIVE_SHOP_BY_USER($user: String) {
id
shopid
}
}
`;
}`;
exports.QUERY_JOB_PAYROLL_DATA = `query QUERY_JOB_PAYROLL_DATA($id: uuid!) {
jobs_by_pk(id: $id) {