diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index f5a1d8e48..207a52a84 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -4318,6 +4318,48 @@ dms + + apcontrol + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + appostingaccount + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + cashierid false @@ -14025,6 +14067,48 @@ + + scheduledintoday + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + scheduledouttoday + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + @@ -23886,6 +23970,27 @@ + + dms_unsold + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + dms_wip_acctnumber false @@ -43605,6 +43710,27 @@ + + jobs_scheduled_completion + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + lag_time false diff --git a/client/src/App/App.styles.scss b/client/src/App/App.styles.scss index ddf87ac42..50e656895 100644 --- a/client/src/App/App.styles.scss +++ b/client/src/App/App.styles.scss @@ -148,6 +148,10 @@ background: #e7f3ff !important; } +.ant-table-tbody > tr.ant-table-row-selected > td { + background: #e6f7ff !important; +} + .job-line-manual { color: tomato; font-style: italic; diff --git a/client/src/components/bill-enter-modal/bill-enter-modal.container.jsx b/client/src/components/bill-enter-modal/bill-enter-modal.container.jsx index 6fb7692a4..d2d0d928a 100644 --- a/client/src/components/bill-enter-modal/bill-enter-modal.container.jsx +++ b/client/src/components/bill-enter-modal/bill-enter-modal.container.jsx @@ -1,18 +1,18 @@ import { useApolloClient, useMutation } from "@apollo/client"; -import { Button, Checkbox, Form, Modal, notification, Space } from "antd"; +import { Button, Checkbox, Form, Modal, Space, notification } from "antd"; import _ from "lodash"; -import React, { useEffect, useState, useMemo } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { INSERT_NEW_BILL } from "../../graphql/bills.queries"; +import { UPDATE_INVENTORY_LINES } from "../../graphql/inventory.queries"; import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries"; import { QUERY_JOB_LBR_ADJUSTMENTS, UPDATE_JOB, } from "../../graphql/jobs.queries"; import { MUTATION_MARK_RETURN_RECEIVED } from "../../graphql/parts-orders.queries"; -import { UPDATE_INVENTORY_LINES } from "../../graphql/inventory.queries"; import { insertAuditTrail } from "../../redux/application/application.actions"; import { toggleModalVisible } from "../../redux/modals/modals.actions"; import { selectBillEnterModal } from "../../redux/modals/modals.selectors"; @@ -20,15 +20,15 @@ import { selectBodyshop, selectCurrentUser, } from "../../redux/user/user.selectors"; -import confirmDialog from "../../utils/asyncConfirm"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; -import BillFormContainer from "../bill-form/bill-form.container"; -import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility"; -import { handleUpload } from "../documents-upload/documents-upload.utility"; -import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility"; -import useLocalStorage from "../../utils/useLocalStorage"; import { GenerateDocument } from "../../utils/RenderTemplate"; import { TemplateList } from "../../utils/TemplateConstants"; +import confirmDialog from "../../utils/asyncConfirm"; +import useLocalStorage from "../../utils/useLocalStorage"; +import BillFormContainer from "../bill-form/bill-form.container"; +import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility"; +import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility"; +import { handleUpload } from "../documents-upload/documents-upload.utility"; const mapStateToProps = createStructuredSelector({ billEnterModal: selectBillEnterModal, @@ -126,6 +126,17 @@ function BillEnterModalContainer({ deductedfromlbr: deductedfromlbr, lbr_adjustment, joblineid: i.joblineid === "noline" ? null : i.joblineid, + applicable_taxes: { + federal: + (i.applicable_taxes && i.applicable_taxes.federal) || + false, + state: + (i.applicable_taxes && i.applicable_taxes.state) || + false, + local: + (i.applicable_taxes && i.applicable_taxes.local) || + false, + }, }; }), }, diff --git a/client/src/components/bill-form/bill-form.component.jsx b/client/src/components/bill-form/bill-form.component.jsx index 3ff123743..0fa28359a 100644 --- a/client/src/components/bill-form/bill-form.component.jsx +++ b/client/src/components/bill-form/bill-form.component.jsx @@ -1,6 +1,6 @@ import Icon, { UploadOutlined } from "@ant-design/icons"; import { useApolloClient } from "@apollo/client"; -import { MdOpenInNew } from "react-icons/md"; +import { useTreatments } from "@splitsoftware/splitio-react"; import { Alert, Divider, @@ -12,14 +12,17 @@ import { Switch, Upload, } from "antd"; +import moment from "moment"; import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; +import { MdOpenInNew } from "react-icons/md"; import { connect } from "react-redux"; import { Link } from "react-router-dom"; import { createStructuredSelector } from "reselect"; import { CHECK_BILL_INVOICE_NUMBER } from "../../graphql/bills.queries"; import { selectBodyshop } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; +import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component"; import FormDatePicker from "../form-date-picker/form-date-picker.component"; import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component"; @@ -28,8 +31,6 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component"; import BillFormLines from "./bill-form.lines.component"; import { CalculateBillTotal } from "./bill-form.totals.utility"; -import { useTreatments } from "@splitsoftware/splitio-react"; -import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -58,6 +59,11 @@ export function BillFormComponent({ {}, bodyshop.imexshopid ); + const { ClosingPeriod } = useTreatments( + ["ClosingPeriod"], + {}, + bodyshop.imexshopid + ); const handleVendorSelect = (props, opt) => { setDiscount(opt.discount); @@ -259,6 +265,37 @@ export function BillFormComponent({ required: true, //message: t("general.validation.required"), }, + ({ getFieldValue }) => ({ + validator(rule, value) { + if ( + ClosingPeriod.treatment === "on" && + bodyshop.accountingconfig.ClosingPeriod + ) { + if ( + moment(value) + .startOf("day") + .isSameOrAfter( + moment( + bodyshop.accountingconfig.ClosingPeriod[0] + ).startOf("day") + ) && + moment(value) + .startOf("day") + .isSameOrBefore( + moment( + bodyshop.accountingconfig.ClosingPeriod[1] + ).endOf("day") + ) + ) { + return Promise.resolve(); + } else { + return Promise.reject(t("bills.validation.closingperiod")); + } + } else { + return Promise.resolve(); + } + }, + }), ]} > diff --git a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx index ffc087ae2..0642884c1 100644 --- a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx +++ b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx @@ -1,13 +1,18 @@ import { Badge, List, Tag } from "antd"; -import React, { useEffect } from "react"; +import React from "react"; import { connect } from "react-redux"; +import { + AutoSizer, + CellMeasurer, + CellMeasurerCache, + List as VirtualizedList, +} from "react-virtualized"; import { createStructuredSelector } from "reselect"; import { setSelectedConversation } from "../../redux/messaging/messaging.actions"; import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors"; import { TimeAgoFormatter } from "../../utils/DateFormatter"; import PhoneFormatter from "../../utils/PhoneFormatter"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; -import { List as VirtualizedList, AutoSizer } from "react-virtualized"; import "./chat-conversation-list.styles.scss"; @@ -24,57 +29,66 @@ function ChatConversationListComponent({ conversationList, selectedConversation, setSelectedConversation, - subscribeToMoreConversations, loadMoreConversations, }) { - useEffect( - () => subscribeToMoreConversations(), - // eslint-disable-next-line react-hooks/exhaustive-deps - [] - ); + const cache = new CellMeasurerCache({ + fixedWidth: true, + defaultHeight: 60, + }); - const rowRenderer = ({ index, key, style }) => { + const rowRenderer = ({ index, key, style, parent }) => { const item = conversationList[index]; return ( - setSelectedConversation(item.id)} - className={`chat-list-item ${ - item.id === selectedConversation - ? "chat-list-selected-conversation" - : null - }`} - style={style} + cache={cache} + parent={parent} + columnIndex={0} + rowIndex={index} > -
- {item.label &&
{item.label}
} - {item.job_conversations.length > 0 ? ( -
- {item.job_conversations.map((j, idx) => ( -
- -
- ))} -
- ) : ( - {item.phone_num} - )} -
-
-
- {item.job_conversations.length > 0 - ? item.job_conversations.map((j, idx) => ( - - {j.job.ro_number} - - )) - : null} + setSelectedConversation(item.id)} + className={`chat-list-item ${ + item.id === selectedConversation + ? "chat-list-selected-conversation" + : null + }`} + style={style} + > +
+ {item.label &&
{item.label}
} + {item.job_conversations.length > 0 ? ( +
+ {item.job_conversations.map((j, idx) => ( +
+ +
+ ))} +
+ ) : ( + {item.phone_num} + )}
- {item.updated_at} -
- - +
+
+ {item.job_conversations.length > 0 + ? item.job_conversations.map((j, idx) => ( + + {j.job.ro_number} + + )) + : null} +
+ {item.updated_at} +
+ + + ); }; @@ -86,7 +100,7 @@ function ChatConversationListComponent({ height={height} width={width} rowCount={conversationList.length} - rowHeight={60} + rowHeight={cache.rowHeight} rowRenderer={rowRenderer} onScroll={({ scrollTop, scrollHeight, clientHeight }) => { if (scrollTop + clientHeight === scrollHeight) { diff --git a/client/src/components/chat-popup/chat-popup.component.jsx b/client/src/components/chat-popup/chat-popup.component.jsx index 1c80d7c4c..6d582bdab 100644 --- a/client/src/components/chat-popup/chat-popup.component.jsx +++ b/client/src/components/chat-popup/chat-popup.component.jsx @@ -4,7 +4,7 @@ import { ShrinkOutlined, SyncOutlined, } from "@ant-design/icons"; -import { useLazyQuery, useSubscription } from "@apollo/client"; +import { useLazyQuery, useQuery } from "@apollo/client"; import { Badge, Card, Col, Row, Space, Tag, Tooltip, Typography } from "antd"; import React, { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -12,8 +12,7 @@ import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { CONVERSATION_LIST_QUERY, - CONVERSATION_LIST_SUBSCRIPTION, - UNREAD_CONVERSATION_COUNT_SUBSCRIPTION, + UNREAD_CONVERSATION_COUNT, } from "../../graphql/conversations.queries"; import { toggleChatVisible } from "../../redux/messaging/messaging.actions"; import { @@ -42,19 +41,20 @@ export function ChatPopupComponent({ const { t } = useTranslation(); const [pollInterval, setpollInterval] = useState(0); - const { data: unreadData } = useSubscription( - UNREAD_CONVERSATION_COUNT_SUBSCRIPTION - ); - - const [ - getConversations, - { loading, data, called, refetch, fetchMore, subscribeToMore }, - ] = useLazyQuery(CONVERSATION_LIST_QUERY, { + const { data: unreadData } = useQuery(UNREAD_CONVERSATION_COUNT, { fetchPolicy: "network-only", nextFetchPolicy: "network-only", - skip: !chatVisible, + ...(pollInterval > 0 ? { pollInterval } : {}), }); + const [getConversations, { loading, data, refetch, fetchMore }] = + useLazyQuery(CONVERSATION_LIST_QUERY, { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + skip: !chatVisible, + ...(pollInterval > 0 ? { pollInterval } : {}), + }); + const fcmToken = sessionStorage.getItem("fcmtoken"); useEffect(() => { @@ -66,13 +66,13 @@ export function ChatPopupComponent({ }, [fcmToken]); useEffect(() => { - if (called && chatVisible) + if (chatVisible) getConversations({ variables: { offset: 0, }, }); - }, [chatVisible, called, getConversations]); + }, [chatVisible, getConversations]); const loadMoreConversations = useCallback(() => { if (data) @@ -119,43 +119,6 @@ export function ChatPopupComponent({ - subscribeToMore({ - document: CONVERSATION_LIST_SUBSCRIPTION, - variables: { offset: 0 }, - updateQuery: (prev, { subscriptionData }) => { - if ( - !subscriptionData.data || - subscriptionData.data.conversations.length === 0 - ) - return prev; - - let conversations = [...prev.conversations]; - const newConversations = - subscriptionData.data.conversations; - - for (const conversation of newConversations) { - const index = conversations.findIndex( - (prevConversation) => - prevConversation.id === conversation.id - ); - - if (index !== -1) { - conversations.splice(index, 1); - conversations.unshift(conversation); - - continue; - } - - conversations.unshift(conversation); - } - - return Object.assign({}, prev, { - conversations: conversations, - }); - }, - }) - } /> )} diff --git a/client/src/components/contract-cars/contract-cars.component.jsx b/client/src/components/contract-cars/contract-cars.component.jsx index c66ff9faa..541d6d7c3 100644 --- a/client/src/components/contract-cars/contract-cars.component.jsx +++ b/client/src/components/contract-cars/contract-cars.component.jsx @@ -59,6 +59,14 @@ export default function ContractsCarsComponent({ 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", @@ -93,6 +101,9 @@ export default function ContractsCarsComponent({ (cc.model || "") .toLowerCase() .includes(state.search.toLowerCase()) || + (cc.color || "") + .toLowerCase() + .includes(state.search.toLowerCase()) || (cc.plate || "").toLowerCase().includes(state.search.toLowerCase()) ); diff --git a/client/src/components/dashboard-components/scheduled-in-today/scheduled-in-today.component.jsx b/client/src/components/dashboard-components/scheduled-in-today/scheduled-in-today.component.jsx new file mode 100644 index 000000000..87d1980a1 --- /dev/null +++ b/client/src/components/dashboard-components/scheduled-in-today/scheduled-in-today.component.jsx @@ -0,0 +1,235 @@ +import { + BranchesOutlined, + ExclamationCircleFilled, + PauseCircleOutlined, +} from "@ant-design/icons"; +import { Card, Space, 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 ChatOpenButton from "../../chat-open-button/chat-open-button.component"; +import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component"; +import DashboardRefreshRequired from "../refresh-required.component"; + +export default function DashboardScheduledInToday({ data, ...cardProps }) { + const { t } = useTranslation(); + const [state, setState] = useState({ + sortedInfo: {}, + }); + if (!data) return null; + if (!data.scheduled_in_today) + return ; + + const appt = []; // Flatten Data + data.scheduled_in_today.forEach((item) => { + var i = { + canceled: item.canceled, + id: item.id, + alt_transport: item.job.alt_transport, + clm_no: item.job.clm_no, + jobid: item.job.jobid, + ins_co_nm: item.job.ins_co_nm, + iouparent: item.job.iouparent, + ownerid: item.job.ownerid, + ownr_co_nm: item.job.ownr_co_nm, + ownr_ea: item.job.ownr_ea, + ownr_fn: item.job.ownr_fn, + ownr_ln: item.job.ownr_ln, + ownr_ph1: item.job.ownr_ph1, + ownr_ph2: item.job.ownr_ph2, + production_vars: item.job.production_vars, + ro_number: item.job.ro_number, + suspended: item.job.suspended, + v_make_desc: item.job.v_make_desc, + v_model_desc: item.job.v_model_desc, + v_model_yr: item.job.v_model_yr, + v_vin: item.job.v_vin, + vehicleid: item.job.vehicleid, + note: item.note, + start: moment(item.start).format("hh:mm a"), + title: item.title, + }; + appt.push(i); + }); + appt.sort ( function (a, b) { return new Date(a.start) - new Date(b.start); }); + + const columns = [ + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + render: (text, record) => ( + e.stopPropagation()} + > + + {record.ro_number || t("general.labels.na")} + {record.production_vars && record.production_vars.alert ? ( + + ) : null} + {record.suspended && ( + + )} + {record.iouparent && ( + + + + )} + + + ), + }, + { + title: t("jobs.fields.owner"), + dataIndex: "owner", + key: "owner", + ellipsis: true, + responsive: ["md"], + render: (text, record) => { + return record.ownerid ? ( + e.stopPropagation()} + > + + + ) : ( + + + + ); + }, + }, + { + title: t("jobs.fields.ownr_ph1"), + dataIndex: "ownr_ph1", + key: "ownr_ph1", + ellipsis: true, + responsive: ["md"], + render: (text, record) => ( + + ), + }, + { + title: t("jobs.fields.ownr_ph2"), + dataIndex: "ownr_ph2", + key: "ownr_ph2", + ellipsis: true, + responsive: ["md"], + render: (text, record) => ( + + ), + }, + { + title: t("jobs.fields.ownr_ea"), + dataIndex: "ownr_ea", + key: "ownr_ea", + ellipsis: true, + responsive: ["md"], + render: (text, record) => ( + + ), + }, + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + ellipsis: true, + render: (text, record) => { + return record.vehicleid ? ( + e.stopPropagation()} + > + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ + record.v_model_desc || "" + }`} + + ) : ( + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ + record.v_model_desc || "" + }`} + ); + }, + }, + { + title: t("jobs.fields.ins_co_nm"), + dataIndex: "ins_co_nm", + key: "ins_co_nm", + ellipsis: true, + responsive: ["md"], + }, + { + title: t("appointments.fields.time"), + dataIndex: "start", + key: "start", + ellipsis: true, + responsive: ["md"], + }, + { + title: t("appointments.fields.alt_transport"), + dataIndex: "alt_transport", + key: "alt_transport", + ellipsis: true, + responsive: ["md"], + }, + ]; + + const handleTableChange = (sorter) => { + setState({ ...state, sortedInfo: sorter }); + }; + + return ( + +
+ + + + ); +} + +export const DashboardScheduledInTodayGql = ` + scheduled_in_today: appointments(where: {start: {_gte: "${moment() + .startOf("day") + .toISOString()}", _lte: "${moment() + .endOf("day") + .toISOString()}"}, canceled: {_eq: false}, block: {_neq: true}}) { + canceled + id + job { + alt_transport + clm_no + jobid: id + ins_co_nm + iouparent + ownerid + ownr_co_nm + ownr_ea + ownr_fn + ownr_ln + ownr_ph1 + ownr_ph2 + production_vars + ro_number + suspended + v_make_desc + v_model_desc + v_model_yr + v_vin + vehicleid + } + note + start + title + } +`; diff --git a/client/src/components/dashboard-components/scheduled-out-today/scheduled-out-today.component.jsx b/client/src/components/dashboard-components/scheduled-out-today/scheduled-out-today.component.jsx new file mode 100644 index 000000000..586e719ab --- /dev/null +++ b/client/src/components/dashboard-components/scheduled-out-today/scheduled-out-today.component.jsx @@ -0,0 +1,210 @@ +import { + BranchesOutlined, + ExclamationCircleFilled, + PauseCircleOutlined, +} from "@ant-design/icons"; +import { Card, Space, 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 ChatOpenButton from "../../chat-open-button/chat-open-button.component"; +import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component"; +import DashboardRefreshRequired from "../refresh-required.component"; + +export default function DashboardScheduledOutToday({ data, ...cardProps }) { + const { t } = useTranslation(); + const [state, setState] = useState({ + sortedInfo: {}, + }); + if (!data) return null; + if (!data.scheduled_out_today) + return ; + + data.scheduled_out_today.forEach((item) => { + item.scheduled_completion= moment(item.scheduled_completion).format("hh:mm a") + }); + data.scheduled_out_today.sort(function (a, b) { + return new Date(a.scheduled_completion) - new Date(b.scheduled_completion); + }); + + const columns = [ + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + render: (text, record) => ( + e.stopPropagation()} + > + + {record.ro_number || t("general.labels.na")} + {record.production_vars && record.production_vars.alert ? ( + + ) : null} + {record.suspended && ( + + )} + {record.iouparent && ( + + + + )} + + + ), + }, + { + title: t("jobs.fields.owner"), + dataIndex: "owner", + key: "owner", + ellipsis: true, + responsive: ["md"], + render: (text, record) => { + return record.ownerid ? ( + e.stopPropagation()} + > + + + ) : ( + + + + ); + }, + }, + { + title: t("jobs.fields.ownr_ph1"), + dataIndex: "ownr_ph1", + key: "ownr_ph1", + ellipsis: true, + responsive: ["md"], + render: (text, record) => ( + + ), + }, + { + title: t("jobs.fields.ownr_ph2"), + dataIndex: "ownr_ph2", + key: "ownr_ph2", + ellipsis: true, + responsive: ["md"], + render: (text, record) => ( + + ), + }, + { + title: t("jobs.fields.ownr_ea"), + dataIndex: "ownr_ea", + key: "ownr_ea", + ellipsis: true, + responsive: ["md"], + render: (text, record) => ( + + ), + }, + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + ellipsis: true, + render: (text, record) => { + return record.vehicleid ? ( + e.stopPropagation()} + > + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ + record.v_model_desc || "" + }`} + + ) : ( + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ + record.v_model_desc || "" + }`} + ); + }, + }, + { + title: t("jobs.fields.ins_co_nm"), + dataIndex: "ins_co_nm", + key: "ins_co_nm", + ellipsis: true, + responsive: ["md"], + }, + { + title: t("jobs.fields.scheduled_completion"), + dataIndex: "scheduled_completion", + key: "scheduled_completion", + ellipsis: true, + responsive: ["md"], + }, + { + title: t("appointments.fields.alt_transport"), + dataIndex: "alt_transport", + key: "alt_transport", + ellipsis: true, + responsive: ["md"], + }, + ]; + + const handleTableChange = (sorter) => { + setState({ ...state, sortedInfo: sorter }); + }; + + return ( + +
+
+ + + ); +} + +export const DashboardScheduledOutTodayGql = ` + scheduled_out_today: jobs(where: { + date_invoiced: {_is_null: true}, + ro_number: {_is_null: false}, + voided: {_eq: false}, + scheduled_completion: {_gte: "${moment().startOf("day").toISOString()}", + _lte: "${moment().endOf("day").toISOString()}"}}) { + alt_transport + clm_no + jobid: id + ins_co_nm + iouparent + ownerid + ownr_co_nm + ownr_ea + ownr_fn + ownr_ln + ownr_ph1 + ownr_ph2 + production_vars + ro_number + scheduled_completion + suspended + v_make_desc + v_model_desc + v_model_yr + v_vin + vehicleid + + } +`; diff --git a/client/src/components/dashboard-grid/dashboard-grid.component.jsx b/client/src/components/dashboard-grid/dashboard-grid.component.jsx index 39a5432c2..d24dd5641 100644 --- a/client/src/components/dashboard-grid/dashboard-grid.component.jsx +++ b/client/src/components/dashboard-grid/dashboard-grid.component.jsx @@ -1,6 +1,6 @@ import Icon, { SyncOutlined } from "@ant-design/icons"; import { gql, useMutation, useQuery } from "@apollo/client"; -import { Button, Dropdown, Menu, notification, PageHeader, Space } from "antd"; +import { Button, Dropdown, Menu, PageHeader, Space, notification } from "antd"; import i18next from "i18next"; import _ from "lodash"; import moment from "moment"; @@ -37,6 +37,12 @@ import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; //Combination of the following: // /node_modules/react-grid-layout/css/styles.css // /node_modules/react-resizable/css/styles.css +import DashboardScheduledInToday, { + DashboardScheduledInTodayGql, +} from "../dashboard-components/scheduled-in-today/scheduled-in-today.component"; +import DashboardScheduledOutToday, { + DashboardScheduledOutTodayGql, +} from "../dashboard-components/scheduled-out-today/scheduled-out-today.component"; import "./dashboard-grid.styles.scss"; import { GenerateDashboardData } from "./dashboard-grid.utils"; @@ -268,6 +274,28 @@ const componentList = { w: 2, h: 2, }, + ScheduleInToday: { + label: i18next.t("dashboard.titles.scheduledintoday", { + date: moment().startOf("day").format("MM/DD/YYYY"), + }), + component: DashboardScheduledInToday, + gqlFragment: DashboardScheduledInTodayGql, + minW: 10, + minH: 2, + w: 10, + h: 2, + }, + ScheduleOutToday: { + label: i18next.t("dashboard.titles.scheduledouttoday", { + date: moment().startOf("day").format("MM/DD/YYYY"), + }), + component: DashboardScheduledOutToday, + gqlFragment: DashboardScheduledOutTodayGql, + minW: 10, + minH: 2, + w: 10, + h: 2, + }, }; const createDashboardQuery = (state) => { @@ -283,8 +311,12 @@ const createDashboardQuery = (state) => { monthly_sales: jobs(where: {_and: [ { voided: {_eq: false}}, {date_invoiced: {_gte: "${moment() - .startOf("month").startOf('day').toISOString()}"}}, {date_invoiced: {_lte: "${moment() - .endOf("month").endOf('day').toISOString()}"}}]}) { + .startOf("month") + .startOf("day") + .toISOString()}"}}, {date_invoiced: {_lte: "${moment() + .endOf("month") + .endOf("day") + .toISOString()}"}}]}) { id ro_number date_invoiced diff --git a/client/src/components/dms-allocations-summary-ap/dms-allocations-summary-ap.component.jsx b/client/src/components/dms-allocations-summary-ap/dms-allocations-summary-ap.component.jsx index 1da53e3ba..54651544d 100644 --- a/client/src/components/dms-allocations-summary-ap/dms-allocations-summary-ap.component.jsx +++ b/client/src/components/dms-allocations-summary-ap/dms-allocations-summary-ap.component.jsx @@ -66,7 +66,7 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) { key: "status", }, { - title: t("jobs.fields.ro_number"), + title: t("bills.fields.invoice_number"), dataIndex: ["Posting", "Reference"], key: "reference", }, diff --git a/client/src/components/dms-post-form/dms-post-form.component.jsx b/client/src/components/dms-post-form/dms-post-form.component.jsx index c9043d050..e1358e989 100644 --- a/client/src/components/dms-post-form/dms-post-form.component.jsx +++ b/client/src/components/dms-post-form/dms-post-form.component.jsx @@ -11,6 +11,7 @@ import { Select, Space, Statistic, + Switch, Typography, } from "antd"; import Dinero from "dinero.js"; @@ -183,6 +184,13 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) { + + + )} diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx index 1ed3f526d..f0edf82d2 100644 --- a/client/src/components/header/header.component.jsx +++ b/client/src/components/header/header.component.jsx @@ -311,7 +311,9 @@ function Header({ icon={} > }> - {t("menus.header.shop_config")} + + {t("menus.header.shop_config")} + }> {t("menus.header.dashboard")} diff --git a/client/src/components/jobs-admin-dates/jobs-admin-dates.component.jsx b/client/src/components/jobs-admin-dates/jobs-admin-dates.component.jsx index d497c9618..bfddbfefc 100644 --- a/client/src/components/jobs-admin-dates/jobs-admin-dates.component.jsx +++ b/client/src/components/jobs-admin-dates/jobs-admin-dates.component.jsx @@ -1,14 +1,14 @@ -import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component"; import { useMutation } from "@apollo/client"; import { Button, Form, notification } from "antd"; +import moment from "moment"; import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { UPDATE_JOB } from "../../graphql/jobs.queries"; -import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component"; -import LayoutFormRow from "../layout-form-row/layout-form-row.component"; -import moment from "moment"; -import FormDatePicker from "../form-date-picker/form-date-picker.component"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; +import FormDatePicker from "../form-date-picker/form-date-picker.component"; +import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component"; +import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component"; +import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; @@ -38,8 +38,8 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) { setLoading(true); const result = await updateJob({ variables: { jobId: job.id, job: values }, - refetchQueries: ['GET_JOB_BY_PK'], - awaitRefetchQueries:true + refetchQueries: ["GET_JOB_BY_PK"], + awaitRefetchQueries: true, }); const changedAuditFields = form.getFieldsValue( @@ -126,7 +126,10 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) { - + + + + diff --git a/client/src/components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component.jsx b/client/src/components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component.jsx index 137c074aa..c47c30def 100644 --- a/client/src/components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component.jsx +++ b/client/src/components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component.jsx @@ -1,19 +1,18 @@ -import { useMutation } from "@apollo/client"; +import { gql, useMutation } from "@apollo/client"; import { Button, notification } from "antd"; -import { gql } from "@apollo/client"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; +import moment from "moment"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; +import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; import { selectBodyshop, selectCurrentUser, } from "../../redux/user/user.selectors"; -import moment from "moment"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; -import { insertAuditTrail } from "../../redux/application/application.actions"; -import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, currentUser: selectCurrentUser, @@ -150,6 +149,10 @@ export function JobAdminMarkReexport({ if (!result.errors) { notification["success"]({ message: t("jobs.successes.save") }); + insertAuditTrail({ + jobid: job.id, + operation: AuditTrailMapping.admin_jobuninvoice(), + }); } else { notification["error"]({ message: t("jobs.errors.saving", { diff --git a/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx b/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx index 51d5d33ed..7963fd05f 100644 --- a/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx +++ b/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx @@ -33,8 +33,9 @@ export function JobsAdminUnvoid({ mutation UNVOID_JOB($jobId: uuid!) { update_jobs_by_pk(pk_columns: {id: $jobId}, _set: {voided: false, status: "${ bodyshop.md_ro_statuses.default_imported - }"}) { + }", date_void: null}) { id + date_void voided status } diff --git a/client/src/components/jobs-detail-dates/jobs-detail-dates.component.jsx b/client/src/components/jobs-detail-dates/jobs-detail-dates.component.jsx index ac501c7e1..05cd1b289 100644 --- a/client/src/components/jobs-detail-dates/jobs-detail-dates.component.jsx +++ b/client/src/components/jobs-detail-dates/jobs-detail-dates.component.jsx @@ -141,6 +141,10 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) { + + + + ); diff --git a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx index 48118f1c0..4565864f5 100644 --- a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx +++ b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx @@ -5,10 +5,10 @@ import { Dropdown, Form, Menu, - notification, Popconfirm, Popover, Select, + notification, } from "antd"; import React, { useMemo } from "react"; import { useTranslation } from "react-i18next"; @@ -24,12 +24,12 @@ import { selectBodyshop, selectCurrentUser, } from "../../redux/user/user.selectors"; +import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component"; import JobsDetailHeaderActionsAddevent from "./jobs-detail-header-actions.addevent"; import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util"; import JobsDetaiLheaderCsi from "./jobs-detail-header-actions.csi.component"; import DuplicateJob from "./jobs-detail-header-actions.duplicate.util"; import JobsDetailHeaderActionsExportcustdataComponent from "./jobs-detail-header-actions.exportcustdata.component"; -import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -480,6 +480,7 @@ export function JobsDetailHeaderActions({ scheduled_in: null, scheduled_completion: null, inproduction: false, + date_void: new Date(), }, note: [ { diff --git a/client/src/components/scoreboard-chart/scoreboard-chart.component.jsx b/client/src/components/scoreboard-chart/scoreboard-chart.component.jsx index 545030397..4e9aa85d0 100644 --- a/client/src/components/scoreboard-chart/scoreboard-chart.component.jsx +++ b/client/src/components/scoreboard-chart/scoreboard-chart.component.jsx @@ -47,9 +47,7 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) { bodyhrs: dayAcc.bodyhrs + dayVal.bodyhrs, painthrs: dayAcc.painthrs + dayVal.painthrs, sales: - dayAcc.painthrs + - dayVal.job.job_totals.totals.subtotal.amount / 100 + - 2500, + dayAcc.sales + dayVal.job.job_totals.totals.subtotal.amount / 100, }; }, { bodyhrs: 0, painthrs: 0, sales: 0 } diff --git a/client/src/components/shop-info/shop-info.component.jsx b/client/src/components/shop-info/shop-info.component.jsx index efabb6d5b..8803500ef 100644 --- a/client/src/components/shop-info/shop-info.component.jsx +++ b/client/src/components/shop-info/shop-info.component.jsx @@ -52,7 +52,9 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) { - history.push({ search: `?tab=${search.tab}&subtab=${key}` }) + history.push({ + search: `?tab=${search.tab}&subtab=${key}`, + }) } > diff --git a/client/src/components/shop-info/shop-info.container.jsx b/client/src/components/shop-info/shop-info.container.jsx index fec10877c..dd254a4cb 100644 --- a/client/src/components/shop-info/shop-info.container.jsx +++ b/client/src/components/shop-info/shop-info.container.jsx @@ -1,14 +1,14 @@ -import React, { useEffect, useState } from "react"; -import ShopInfoComponent from "./shop-info.component"; +import { useMutation, useQuery } from "@apollo/client"; import { Form, notification } from "antd"; -import { useQuery, useMutation } from "@apollo/client"; -import { QUERY_BODYSHOP, UPDATE_SHOP } from "../../graphql/bodyshop.queries"; -import LoadingSpinner from "../loading-spinner/loading-spinner.component"; -import AlertComponent from "../alert/alert.component"; +import moment from "moment"; +import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { logImEXEvent } from "../../firebase/firebase.utils"; +import { QUERY_BODYSHOP, UPDATE_SHOP } from "../../graphql/bodyshop.queries"; +import AlertComponent from "../alert/alert.component"; import FormsFieldChanged from "../form-fields-changed-alert/form-fields-changed-alert.component"; -import moment from "moment"; +import LoadingSpinner from "../loading-spinner/loading-spinner.component"; +import ShopInfoComponent from "./shop-info.component"; export default function ShopInfoContainer() { const [form] = Form.useForm(); const { t } = useTranslation(); @@ -52,13 +52,28 @@ export default function ShopInfoContainer() { onFinish={handleFinish} initialValues={ data - ? { - ...data.bodyshops[0], - schedule_start_time: moment( - data.bodyshops[0].schedule_start_time - ), - schedule_end_time: moment(data.bodyshops[0].schedule_end_time), - } + ? data.bodyshops[0].accountingconfig.ClosingPeriod + ? { + ...data.bodyshops[0], + accountingconfig: { + ...data.bodyshops[0].accountingconfig, + ClosingPeriod: [ + moment(data.bodyshops[0].accountingconfig.ClosingPeriod[0]), + moment(data.bodyshops[0].accountingconfig.ClosingPeriod[1]), + ], + }, + schedule_start_time: moment( + data.bodyshops[0].schedule_start_time + ), + schedule_end_time: moment(data.bodyshops[0].schedule_end_time), + } + : { + ...data.bodyshops[0], + schedule_start_time: moment( + data.bodyshops[0].schedule_start_time + ), + schedule_end_time: moment(data.bodyshops[0].schedule_end_time), + } : null } > diff --git a/client/src/components/shop-info/shop-info.general.component.jsx b/client/src/components/shop-info/shop-info.general.component.jsx index 416d2f658..ebf2accaf 100644 --- a/client/src/components/shop-info/shop-info.general.component.jsx +++ b/client/src/components/shop-info/shop-info.general.component.jsx @@ -1,6 +1,8 @@ import { DeleteFilled } from "@ant-design/icons"; +import { useTreatments } from "@splitsoftware/splitio-react"; import { Button, + DatePicker, Form, Input, InputNumber, @@ -9,8 +11,13 @@ import { Space, Switch, } from "antd"; +import momentTZ from "moment-timezone"; import React from "react"; import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import DatePickerRanges from "../../utils/DatePickerRanges"; import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import FormItemEmail from "../form-items-formatted/email-form-item.component"; import PhoneFormItem, { @@ -18,12 +25,22 @@ import PhoneFormItem, { } from "../form-items-formatted/phone-form-item.component"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; - -import momentTZ from "moment-timezone"; const timeZonesList = momentTZ.tz.names(); +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, +}); +const mapDispatchToProps = (dispatch) => ({ + //setUserLanguage: language => dispatch(setUserLanguage(language)) +}); +export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoGeneral); -export default function ShopInfoGeneral({ form }) { +export function ShopInfoGeneral({ form, bodyshop }) { const { t } = useTranslation(); + const { ClosingPeriod } = useTreatments( + ["ClosingPeriod"], + {}, + bodyshop && bodyshop.imexshopid + ); return (
@@ -392,6 +409,20 @@ export default function ShopInfoGeneral({ form }) { > + + + + )} + {bodyshop.pbs_serialnumber && ( + + + {bodyshop.md_ro_statuses.production_statuses.map((item) => ( + + ))} + + + )} diff --git a/client/src/components/update-alert/update-alert.component.jsx b/client/src/components/update-alert/update-alert.component.jsx new file mode 100644 index 000000000..f0b11520e --- /dev/null +++ b/client/src/components/update-alert/update-alert.component.jsx @@ -0,0 +1,78 @@ +import { Alert, Button, Col, Row, Space } from "antd"; +import i18n from "i18next"; +import React from "react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectUpdateAvailable } from "../../redux/application/application.selectors"; +import { AlertOutlined } from "@ant-design/icons"; +import { useTranslation } from "react-i18next"; +import { setUpdateAvailable } from "../../redux/application/application.actions"; +import { store } from "../../redux/store"; +import * as serviceWorkerRegistration from "../../serviceWorkerRegistration"; + +let globalRegistration; + +const mapStateToProps = createStructuredSelector({ + updateAvailable: selectUpdateAvailable, +}); +const mapDispatchToProps = (dispatch) => ({ + //setUserLanguage: language => dispatch(setUserLanguage(language)) +}); +export default connect(mapStateToProps, mapDispatchToProps)(UpdateAlert); + +export function UpdateAlert({ updateAvailable }) { + const { t } = useTranslation(); + if (!updateAvailable) return null; + return ( + } + description={ + +
+ {t("general.messages.newversionmessage")} + + + + + + + + + } + closable={false} + type="warning" + /> + ); +} + +const onServiceWorkerUpdate = (registration) => { + console.log("onServiceWorkerUpdate", registration); + globalRegistration = registration; + store.dispatch(setUpdateAvailable(true)); +}; + +serviceWorkerRegistration.register({ onUpdate: onServiceWorkerUpdate }); diff --git a/client/src/graphql/conversations.queries.js b/client/src/graphql/conversations.queries.js index f9feb3f2f..315ba1a5d 100644 --- a/client/src/graphql/conversations.queries.js +++ b/client/src/graphql/conversations.queries.js @@ -1,37 +1,5 @@ import { gql } from "@apollo/client"; -export const CONVERSATION_LIST_SUBSCRIPTION = gql` - subscription CONVERSATION_LIST_SUBSCRIPTION($offset: Int!) { - conversations( - order_by: { updated_at: desc } - limit: 1 - offset: $offset - where: { archived: { _eq: false } } - ) { - phone_num - id - updated_at - unreadcnt - messages_aggregate( - where: { read: { _eq: false }, isoutbound: { _eq: false } } - ) { - aggregate { - count - } - } - job_conversations { - job { - id - ro_number - ownr_fn - ownr_ln - ownr_co_nm - } - } - } - } -`; - export const UNREAD_CONVERSATION_COUNT = gql` query UNREAD_CONVERSATION_COUNT { messages_aggregate( @@ -44,18 +12,6 @@ export const UNREAD_CONVERSATION_COUNT = gql` } `; -export const UNREAD_CONVERSATION_COUNT_SUBSCRIPTION = gql` - subscription UNREAD_CONVERSATION_COUNT_SUBSCRIPTION { - messages_aggregate( - where: { read: { _eq: false }, isoutbound: { _eq: false } } - ) { - aggregate { - count - } - } - } -`; - export const CONVERSATION_LIST_QUERY = gql` query CONVERSATION_LIST_QUERY($offset: Int!) { conversations( diff --git a/client/src/graphql/jobs-lines.queries.js b/client/src/graphql/jobs-lines.queries.js index 1600d5082..05af1fba4 100644 --- a/client/src/graphql/jobs-lines.queries.js +++ b/client/src/graphql/jobs-lines.queries.js @@ -34,6 +34,7 @@ export const GET_LINE_TICKET_BY_PK = gql` id lbr_adjustments converted + status } joblines(where: { jobid: { _eq: $id }, removed: { _eq: false } }) { id diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index 2dc4ff8ee..7bb943162 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -682,6 +682,7 @@ export const GET_JOB_BY_PK = gql` date_rentalresp date_exported date_repairstarted + date_void status owner_owing tax_registration_number @@ -1086,6 +1087,7 @@ export const UPDATE_JOB = gql` scheduled_completion actual_in date_repairstarted + date_void } } } @@ -1133,6 +1135,7 @@ export const VOID_JOB = gql` update_jobs_by_pk(_set: $job, pk_columns: { id: $jobId }) { id date_exported + date_void status alt_transport ro_number @@ -1227,10 +1230,10 @@ export const ACTIVE_JOBS_FOR_AUTOCOMPLETE = gql` query ACTIVE_JOBS_FOR_AUTOCOMPLETE($statuses: [String!]!) { jobs(where: { status: { _in: $statuses } }) { id + ownr_co_nm ownr_fn ownr_ln ro_number - vehicleid v_make_desc v_model_desc @@ -1258,6 +1261,7 @@ export const SEARCH_JOBS_FOR_AUTOCOMPLETE = gql` } ) { id + ownr_co_nm ownr_fn ownr_ln ro_number @@ -1274,6 +1278,7 @@ export const SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE = gql` query SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE($id: uuid!) { jobs_by_pk(id: $id) { id + ownr_co_nm ownr_fn ownr_ln ro_number @@ -1292,6 +1297,7 @@ export const SEARCH_FOR_JOBS = gql` search_jobs(args: { search: $search }, limit: 25) { id ro_number + ownr_co_nm ownr_fn ownr_ln } diff --git a/client/src/pages/dms/dms.container.jsx b/client/src/pages/dms/dms.container.jsx index 9fcfd491e..77c670e60 100644 --- a/client/src/pages/dms/dms.container.jsx +++ b/client/src/pages/dms/dms.container.jsx @@ -13,7 +13,7 @@ import queryString from "query-string"; import React, { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; -import { useHistory, useLocation, Link } from "react-router-dom"; +import { Link, useHistory, useLocation } from "react-router-dom"; import { createStructuredSelector } from "reselect"; import SocketIO from "socket.io-client"; import AlertComponent from "../../components/alert/alert.component"; @@ -22,6 +22,7 @@ import DmsCustomerSelector from "../../components/dms-customer-selector/dms-cust import DmsLogEvents from "../../components/dms-log-events/dms-log-events.component"; import DmsPostForm from "../../components/dms-post-form/dms-post-form.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; +import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component"; import { auth } from "../../firebase/firebase.utils"; import { QUERY_JOB_EXPORT_DMS } from "../../graphql/jobs.queries"; import { @@ -29,7 +30,6 @@ import { setSelectedHeader, } from "../../redux/application/application.actions"; import { selectBodyshop } from "../../redux/user/user.selectors"; -import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -46,6 +46,7 @@ export const socket = SocketIO( process.env.NODE_ENV === "production" ? process.env.REACT_APP_AXIOS_BASE_API_URL : window.location.origin, + // "http://localhost:4000", // for dev testing, { path: "/ws", withCredentials: true, diff --git a/client/src/pages/jobs-close/jobs-close.component.jsx b/client/src/pages/jobs-close/jobs-close.component.jsx index f7fcbfe1a..91c8c7385 100644 --- a/client/src/pages/jobs-close/jobs-close.component.jsx +++ b/client/src/pages/jobs-close/jobs-close.component.jsx @@ -8,7 +8,6 @@ import { Form, Input, InputNumber, - notification, PageHeader, Popconfirm, Row, @@ -17,12 +16,14 @@ import { Statistic, Switch, Typography, + notification, } from "antd"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; //import { useHistory } from "react-router-dom"; import { useTreatments } from "@splitsoftware/splitio-react"; +import Dinero from "dinero.js"; import moment from "moment"; import { Link } from "react-router-dom"; import { createStructuredSelector } from "reselect"; @@ -37,7 +38,6 @@ import { generateJobLinesUpdatesForInvoicing } from "../../graphql/jobs-lines.qu import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors"; -import Dinero from "dinero.js"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, jobRO: selectJobReadOnly, @@ -55,6 +55,11 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) { {}, bodyshop && bodyshop.imexshopid ); + const { ClosingPeriod } = useTreatments( + ["ClosingPeriod"], + {}, + bodyshop && bodyshop.imexshopid + ); const handleFinish = async ({ removefromproduction, ...values }) => { setLoading(true); @@ -254,12 +259,40 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) { if (!value || moment(value).isSameOrAfter(moment(), "day")) { return Promise.resolve(); } - return Promise.reject( new Error(t("jobs.labels.dms.invoicedatefuture")) ); }, }), + ({ getFieldValue }) => ({ + validator(_, value) { + if ( + ClosingPeriod.treatment === "on" && + bodyshop.accountingconfig.ClosingPeriod + ) { + if ( + moment(value).isSameOrAfter( + moment( + bodyshop.accountingconfig.ClosingPeriod[0] + ).startOf("day") + ) && + moment(value).isSameOrBefore( + moment( + bodyshop.accountingconfig.ClosingPeriod[1] + ).endOf("day") + ) + ) { + return Promise.resolve(); + } else { + return Promise.reject( + new Error(t("jobs.labels.closingperiod")) + ); + } + } else { + return Promise.resolve(); + } + }, + }), ]} > import("../manage-root/manage-root.page.container") @@ -394,6 +395,7 @@ export function Manage({ match, conflict, bodyshop }) { <> + diff --git a/client/src/pages/manage/manage.page.container.jsx b/client/src/pages/manage/manage.page.container.jsx index 75f4da328..04505ce2a 100644 --- a/client/src/pages/manage/manage.page.container.jsx +++ b/client/src/pages/manage/manage.page.container.jsx @@ -6,7 +6,7 @@ import AlertComponent from "../../components/alert/alert.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries"; import { setBodyshop } from "../../redux/user/user.actions"; -import "../../utils/RegisterSw"; +//import "../../utils/RegisterSw"; import ManagePage from "./manage.page.component"; const mapDispatchToProps = (dispatch) => ({ diff --git a/client/src/pages/shop/shop.page.component.jsx b/client/src/pages/shop/shop.page.component.jsx index 47107be43..de6b267d5 100644 --- a/client/src/pages/shop/shop.page.component.jsx +++ b/client/src/pages/shop/shop.page.component.jsx @@ -40,6 +40,10 @@ export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) { ]); }, [t, setSelectedHeader, setBreadcrumbs, bodyshop.shopname]); + useEffect(() => { + if (!search.tab) history.push({ search: "?tab=info" }); + }, [history, search]); + return ( import("../../components/time-ticket-modal/time-ticket-modal.container") ); @@ -59,7 +60,9 @@ export function TechPage({ technician, match }) { {technician ? null : } + + ({ type: ApplicationActionTypes.SET_PROBLEM_JOBS, payload: problemJobs, }); + +export const setUpdateAvailable = (isUpdateAvailable) => ({ + type: ApplicationActionTypes.SET_UPDATE_AVAILABLE, + payload: isUpdateAvailable, +}); diff --git a/client/src/redux/application/application.reducer.js b/client/src/redux/application/application.reducer.js index 95579574a..0b428209b 100644 --- a/client/src/redux/application/application.reducer.js +++ b/client/src/redux/application/application.reducer.js @@ -3,6 +3,7 @@ import ApplicationActionTypes from "./application.types"; const INITIAL_STATE = { loading: false, online: true, + updateAvailable: false, breadcrumbs: [], recentItems: [], selectedHeader: "home", @@ -18,6 +19,11 @@ const INITIAL_STATE = { const applicationReducer = (state = INITIAL_STATE, action) => { switch (action.type) { + case ApplicationActionTypes.SET_UPDATE_AVAILABLE: + return { + ...state, + updateAvailable: action.payload, + }; case ApplicationActionTypes.SET_SELECTED_HEADER: return { ...state, diff --git a/client/src/redux/application/application.selectors.js b/client/src/redux/application/application.selectors.js index cb5e2c679..e9b293d62 100644 --- a/client/src/redux/application/application.selectors.js +++ b/client/src/redux/application/application.selectors.js @@ -48,3 +48,7 @@ export const selectProblemJobs = createSelector( [selectApplication], (application) => application.problemJobs ); +export const selectUpdateAvailable = createSelector( + [selectApplication], + (application) => application.updateAvailable +); diff --git a/client/src/redux/application/application.types.js b/client/src/redux/application/application.types.js index 1e047bb82..9b95dd6ee 100644 --- a/client/src/redux/application/application.types.js +++ b/client/src/redux/application/application.types.js @@ -12,5 +12,6 @@ const ApplicationActionTypes = { SET_ONLINE_STATUS: "SET_ONLINE_STATUS", INSERT_AUDIT_TRAIL: "INSERT_AUDIT_TRAIL", SET_PROBLEM_JOBS: "SET_PROBLEM_JOBS", + SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE" }; export default ApplicationActionTypes; diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 07533cf11..55ac3b850 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -101,6 +101,7 @@ "messages": { "admin_jobmarkexported": "ADMIN: Job marked as exported.", "admin_jobmarkforreexport": "ADMIN: Job marked for re-export.", + "admin_jobuninvoice": "ADMIN: Job has been uninvoiced.", "admin_jobunvoid": "ADMIN: Job has been unvoided.", "billposted": "Bill with invoice number {{invoice_number}} posted.", "billupdated": "Bill with invoice number {{invoice_number}} updated.", @@ -222,6 +223,7 @@ "reexport": "Bill marked for re-export." }, "validation": { + "closingperiod": "This Bill Date is outside of the Closing Period.", "inventoryquantity": "Quantity must be greater than or equal to what has been added to inventory ({{number}}).", "manualinhouse": "Manual posting to the in house vendor is restricted. ", "unique_invoice_number": "This invoice number has already been entered for this vendor." @@ -261,6 +263,7 @@ "bill_local_tax_rate": "Bill - Provincial/State Tax Rate %", "bill_state_tax_rate": "Bill - Provincial/State Tax Rate %", "city": "City", + "closingperiod": "Closing Period", "country": "Country", "dailybodytarget": "Scoreboard - Daily Body Target", "dailypainttarget": "Scoreboard - Daily Paint Target", @@ -269,6 +272,8 @@ "templates": "Delivery Templates" }, "dms": { + "apcontrol": "AP Control Number", + "appostingaccount": "AP Posting Account", "cashierid": "Cashier ID", "default_journal": "Default Journal", "disablebillwip": "Disable bill WIP for A/P Posting", @@ -858,7 +863,9 @@ "prodhrssummary": "Production Hours Summary", "productiondollars": "Total dollars in Production", "productionhours": "Total hours in Production", - "projectedmonthlysales": "Projected Monthly Sales" + "projectedmonthlysales": "Projected Monthly Sales", + "scheduledintoday": "Sheduled In Today: {{date}}", + "scheduledouttoday": "Sheduled Out Today: {{date}}" } }, "dms": { @@ -1118,7 +1125,7 @@ }, "messages": { "exception": "$t(titles.app) has encountered an error. Please try again. If the problem persists, please submit a support ticket or contact us.", - "newversionmessage": "Click refresh below to update to the latest available version of ImEX Online. Please make sure all other tabs and windows are closed.", + "newversionmessage": "Click refresh to update to the latest available version of ImEX Online. Please make sure all other tabs and windows are closed.", "newversiontitle": "New version of ImEX Online Available", "noacctfilepath": "There is no accounting file path set. You will not be able to export any items.", "nofeatureaccess": "You do not have access to this feature of ImEX Online. Please contact support to request a license for this feature.", @@ -1436,6 +1443,7 @@ "date_repairstarted": "Repairs Started", "date_scheduled": "Scheduled", "date_towin": "Towed In", + "date_void": "Void", "ded_amt": "Deductible", "ded_note": "Deductible Note", "ded_status": "Deductible Status", @@ -1451,6 +1459,7 @@ "cost_dms_acctnumber": "Cost DMS Acct #", "dms_make": "DMS Make", "dms_model": "DMS Model", + "dms_unsold": "New, Unsold Vehicle", "dms_wip_acctnumber": "Cost WIP DMS Acct #", "id": "DMS ID", "inservicedate": "In Service Date", @@ -1682,6 +1691,7 @@ "checklists": "Checklists", "closeconfirm": "Are you sure you want to close this job? This cannot be easily undone.", "closejob": "Close Job {{ro_number}}", + "closingperiod": "This Invoice Date is outside of the Closing Period.", "contracts": "CC Contracts", "convertedtolabor": "Lines Converted to Labor", "cost": "Cost", @@ -2423,6 +2433,7 @@ "jobs": { "individual_job_note": "Job Note RO: {{ro_number}}", "parts_order": "Parts Order PO: {{ro_number}} - {{name}}", + "parts_return_slip":"Parts Return PO: {{ro_number}} - {{name}}", "sublet_order": "Sublet Order PO: {{ro_number}} - {{name}}" } }, @@ -2590,6 +2601,7 @@ "jobs_completed_not_invoiced": "Jobs Completed not Invoiced", "jobs_invoiced_not_exported": "Jobs Invoiced not Exported", "jobs_reconcile": "Parts/Sublet/Labor Reconciliation", + "jobs_scheduled_completion": "Jobs Scheduled Completion", "lag_time": "Lag Time", "open_orders": "Open Orders by Date", "open_orders_csr": "Open Orders by CSR", @@ -2638,6 +2650,7 @@ "timetickets_summary": "Time Tickets Summary", "unclaimed_hrs": "Unclaimed Hours", "void_ros": "Void ROs", + "work_in_progress_jobs": "Work in Progress - Jobs", "work_in_progress_labour": "Work in Progress - Labor", "work_in_progress_payables": "Work in Progress - Payables" } diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 0e38e3c01..03b36ef94 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -101,6 +101,7 @@ "messages": { "admin_jobmarkexported": "", "admin_jobmarkforreexport": "", + "admin_jobuninvoice": "", "admin_jobunvoid": "", "billposted": "", "billupdated": "", @@ -222,6 +223,7 @@ "reexport": "" }, "validation": { + "closingperiod": "", "inventoryquantity": "", "manualinhouse": "", "unique_invoice_number": "" @@ -261,6 +263,7 @@ "bill_local_tax_rate": "", "bill_state_tax_rate": "", "city": "", + "closingperiod": "", "country": "", "dailybodytarget": "", "dailypainttarget": "", @@ -269,6 +272,8 @@ "templates": "" }, "dms": { + "apcontrol": "", + "appostingaccount": "", "cashierid": "", "default_journal": "", "disablebillwip": "", @@ -858,7 +863,9 @@ "prodhrssummary": "", "productiondollars": "", "productionhours": "", - "projectedmonthlysales": "" + "projectedmonthlysales": "", + "scheduledintoday": "", + "scheduledouttoday": "" } }, "dms": { @@ -1436,6 +1443,7 @@ "date_repairstarted": "", "date_scheduled": "Programado", "date_towin": "", + "date_void": "", "ded_amt": "Deducible", "ded_note": "", "ded_status": "Estado deducible", @@ -1451,6 +1459,7 @@ "cost_dms_acctnumber": "", "dms_make": "", "dms_model": "", + "dms_unsold": "", "dms_wip_acctnumber": "", "id": "", "inservicedate": "", @@ -1682,6 +1691,7 @@ "checklists": "", "closeconfirm": "", "closejob": "", + "closingperiod": "", "contracts": "", "convertedtolabor": "", "cost": "", @@ -2423,6 +2433,7 @@ "jobs": { "individual_job_note": "", "parts_order": "", + "parts_return_slip": "", "sublet_order": "" } }, @@ -2590,6 +2601,7 @@ "jobs_completed_not_invoiced": "", "jobs_invoiced_not_exported": "", "jobs_reconcile": "", + "jobs_scheduled_completion": "", "lag_time": "", "open_orders": "", "open_orders_csr": "", @@ -2638,6 +2650,7 @@ "timetickets_summary": "", "unclaimed_hrs": "", "void_ros": "", + "work_in_progress_jobs": "", "work_in_progress_labour": "", "work_in_progress_payables": "" } diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 8c69db593..0d16c4e16 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -101,6 +101,7 @@ "messages": { "admin_jobmarkexported": "", "admin_jobmarkforreexport": "", + "admin_jobuninvoice": "", "admin_jobunvoid": "", "billposted": "", "billupdated": "", @@ -222,6 +223,7 @@ "reexport": "" }, "validation": { + "closingperiod": "", "inventoryquantity": "", "manualinhouse": "", "unique_invoice_number": "" @@ -261,6 +263,7 @@ "bill_local_tax_rate": "", "bill_state_tax_rate": "", "city": "", + "closingperiod": "", "country": "", "dailybodytarget": "", "dailypainttarget": "", @@ -269,6 +272,8 @@ "templates": "" }, "dms": { + "apcontrol": "", + "appostingaccount": "", "cashierid": "", "default_journal": "", "disablebillwip": "", @@ -858,7 +863,9 @@ "prodhrssummary": "", "productiondollars": "", "productionhours": "", - "projectedmonthlysales": "" + "projectedmonthlysales": "", + "scheduledintoday": "", + "scheduledouttoday": "" } }, "dms": { @@ -1436,6 +1443,7 @@ "date_repairstarted": "", "date_scheduled": "Prévu", "date_towin": "", + "date_void": "", "ded_amt": "Déductible", "ded_note": "", "ded_status": "Statut de franchise", @@ -1451,6 +1459,7 @@ "cost_dms_acctnumber": "", "dms_make": "", "dms_model": "", + "dms_unsold": "", "dms_wip_acctnumber": "", "id": "", "inservicedate": "", @@ -1682,6 +1691,7 @@ "checklists": "", "closeconfirm": "", "closejob": "", + "closingperiod": "", "contracts": "", "convertedtolabor": "", "cost": "", @@ -2423,6 +2433,7 @@ "jobs": { "individual_job_note": "", "parts_order": "", + "parts_return_slip": "", "sublet_order": "" } }, @@ -2590,6 +2601,7 @@ "jobs_completed_not_invoiced": "", "jobs_invoiced_not_exported": "", "jobs_reconcile": "", + "jobs_scheduled_completion": "", "lag_time": "", "open_orders": "", "open_orders_csr": "", @@ -2638,6 +2650,7 @@ "timetickets_summary": "", "unclaimed_hrs": "", "void_ros": "", + "work_in_progress_jobs": "", "work_in_progress_labour": "", "work_in_progress_payables": "" } diff --git a/client/src/utils/AuditTrailMappings.js b/client/src/utils/AuditTrailMappings.js index e3019c731..99a9a014b 100644 --- a/client/src/utils/AuditTrailMappings.js +++ b/client/src/utils/AuditTrailMappings.js @@ -36,6 +36,7 @@ const AuditTrailMapping = { jobnoteupdated: () => i18n.t("audit_trail.messages.jobnoteupdated"), jobnotedeleted: () => i18n.t("audit_trail.messages.jobnotedeleted"), admin_jobunvoid: () => i18n.t("audit_trail.messages.admin_jobunvoid"), + admin_jobuninvoice: () => i18n.t("audit_trail.messages.admin_jobuninvoice"), admin_jobmarkforreexport: () => i18n.t("audit_trail.messages.admin_jobmarkforreexport"), admin_jobmarkexported: () => diff --git a/client/src/utils/RegisterSw.js b/client/src/utils/RegisterSw.js index 8b2477b8e..0b3bce09c 100644 --- a/client/src/utils/RegisterSw.js +++ b/client/src/utils/RegisterSw.js @@ -3,6 +3,7 @@ import { Button, notification, Space } from "antd"; import i18n from "i18next"; import React from "react"; import * as serviceWorkerRegistration from "../serviceWorkerRegistration"; +import { store } from "../redux/store"; const onServiceWorkerUpdate = (registration) => { console.log("onServiceWorkerUpdate", registration); @@ -33,6 +34,9 @@ const onServiceWorkerUpdate = (registration) => { ); + + store.dispatch() + notification.open({ icon: , message: i18n.t("general.messages.newversiontitle"), diff --git a/client/src/utils/TemplateConstants.js b/client/src/utils/TemplateConstants.js index e4b01b79b..ce657e92c 100644 --- a/client/src/utils/TemplateConstants.js +++ b/client/src/utils/TemplateConstants.js @@ -615,7 +615,14 @@ export const TemplateList = (type, context) => { }, parts_return_slip: { title: i18n.t("printcenter.jobs.parts_return_slip"), - subject: i18n.t("printcenter.jobs.parts_return_slip"), + subject: i18n.t("printcenter.subjects.jobs.parts_return_slip", { + ro_number: context && context.job && context.job.ro_number, + name: ( + (context && context.job && context.job.ownr_ln) || + (context && context.job && context.job.ownr_co_nm) || + "" + ).trim(), + }), description: "", key: "parts_return_slip", disabled: false, @@ -1246,7 +1253,7 @@ export const TemplateList = (type, context) => { disabled: false, rangeFilter: { object: i18n.t("reportcenter.labels.objects.jobs"), - field: i18n.t("jobs.fields.date_open"), + field: i18n.t("jobs.fields.date_void"), }, group: "sales", }, @@ -1549,6 +1556,19 @@ export const TemplateList = (type, context) => { }, group: "payroll", }, + work_in_progress_jobs_excel: { + title: i18n.t("reportcenter.templates.work_in_progress_jobs"), + subject: i18n.t("reportcenter.templates.work_in_progress_jobs"), + key: "work_in_progress_jobs_excel", + //idtype: "vendor", + reporttype: "excel", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_open"), + }, + group: "jobs", + }, work_in_progress_labour: { title: i18n.t("reportcenter.templates.work_in_progress_labour"), description: "", @@ -1906,6 +1926,18 @@ export const TemplateList = (type, context) => { }, group: "sales", }, + jobs_scheduled_completion: { + title: i18n.t("reportcenter.templates.jobs_scheduled_completion"), + subject: i18n.t("reportcenter.templates.jobs_scheduled_completion"), + key: "jobs_scheduled_completion", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.scheduled_completion"), + }, + group: "jobs", + }, } : {}), ...(!type || type === "courtesycarcontract" diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml index 952644a60..a24db9297 100644 --- a/hasura/metadata/tables.yaml +++ b/hasura/metadata/tables.yaml @@ -2092,6 +2092,13 @@ table: name: employee_team_members schema: public + - name: joblines + using: + foreign_key_constraint_on: + column: assigned_team + table: + name: joblines + schema: public insert_permissions: - role: user permission: @@ -2665,6 +2672,9 @@ name: joblines schema: public object_relationships: + - name: employee_team + using: + foreign_key_constraint_on: assigned_team - name: job using: foreign_key_constraint_on: jobid @@ -2726,6 +2736,7 @@ - alt_part_i - alt_partm - alt_partno + - assigned_team - bett_amt - bett_pctg - bett_tax @@ -2734,6 +2745,7 @@ - convertedtolbr - convertedtolbr_data - created_at + - critical - db_hrs - db_price - db_ref @@ -2793,6 +2805,7 @@ - alt_part_i - alt_partm - alt_partno + - assigned_team - bett_amt - bett_pctg - bett_tax @@ -2872,6 +2885,7 @@ - alt_part_i - alt_partm - alt_partno + - assigned_team - bett_amt - bett_pctg - bett_tax @@ -3286,6 +3300,7 @@ - clm_total - clm_zip - comment + - completed_tasks - converted - created_at - cust_pr @@ -3480,6 +3495,7 @@ - v_model_yr - v_vin - vehicleid + - date_void - voided select_permissions: - role: user @@ -3550,6 +3566,7 @@ - clm_total - clm_zip - comment + - completed_tasks - converted - created_at - cust_pr @@ -3745,6 +3762,7 @@ - v_model_yr - v_vin - vehicleid + - date_void - voided filter: bodyshop: @@ -3825,6 +3843,7 @@ - clm_total - clm_zip - comment + - completed_tasks - converted - created_at - cust_pr @@ -4020,6 +4039,7 @@ - v_model_yr - v_vin - vehicleid + - date_void - voided filter: bodyshop: @@ -4629,6 +4649,7 @@ _eq: X-Hasura-User-Id - active: _eq: true + allow_aggregations: true update_permissions: - role: user permission: @@ -4662,6 +4683,74 @@ - name: parts_dispatch using: foreign_key_constraint_on: partsdispatchid + insert_permissions: + - role: user + permission: + check: + parts_dispatch: + job: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + columns: + - id + - created_at + - updated_at + - partsdispatchid + - joblineid + - quantity + - accepted_at + select_permissions: + - role: user + permission: + columns: + - quantity + - accepted_at + - created_at + - updated_at + - id + - joblineid + - partsdispatchid + filter: + parts_dispatch: + job: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + allow_aggregations: true + update_permissions: + - role: user + permission: + columns: + - id + - created_at + - updated_at + - partsdispatchid + - joblineid + - quantity + - accepted_at + filter: + parts_dispatch: + job: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + check: null - table: name: parts_order_lines schema: public @@ -5475,6 +5564,7 @@ - memo - productivehrs - rate + - task_name - ttapprovalqueueid - updated_at select_permissions: @@ -5498,6 +5588,7 @@ - memo - productivehrs - rate + - task_name - ttapprovalqueueid - updated_at filter: @@ -5530,6 +5621,7 @@ - memo - productivehrs - rate + - task_name - ttapprovalqueueid - updated_at filter: diff --git a/hasura/migrations/1686084082349_alter_table_public_parts_dispatch_lines_alter_column_accepted_at/down.sql b/hasura/migrations/1686084082349_alter_table_public_parts_dispatch_lines_alter_column_accepted_at/down.sql new file mode 100644 index 000000000..b88b44672 --- /dev/null +++ b/hasura/migrations/1686084082349_alter_table_public_parts_dispatch_lines_alter_column_accepted_at/down.sql @@ -0,0 +1 @@ +alter table "public"."parts_dispatch_lines" alter column "accepted_at" set not null; diff --git a/hasura/migrations/1686084082349_alter_table_public_parts_dispatch_lines_alter_column_accepted_at/up.sql b/hasura/migrations/1686084082349_alter_table_public_parts_dispatch_lines_alter_column_accepted_at/up.sql new file mode 100644 index 000000000..723108120 --- /dev/null +++ b/hasura/migrations/1686084082349_alter_table_public_parts_dispatch_lines_alter_column_accepted_at/up.sql @@ -0,0 +1 @@ +alter table "public"."parts_dispatch_lines" alter column "accepted_at" drop not null; diff --git a/hasura/migrations/1686335272144_set_fk_public_job_conversations_conversationid/down.sql b/hasura/migrations/1686335272144_set_fk_public_job_conversations_conversationid/down.sql new file mode 100644 index 000000000..1dfd23a52 --- /dev/null +++ b/hasura/migrations/1686335272144_set_fk_public_job_conversations_conversationid/down.sql @@ -0,0 +1,5 @@ +alter table "public"."job_conversations" drop constraint "job_conversations_conversationid_fkey", + add constraint "job_conversations_conversationid_fkey" + foreign key ("conversationid") + references "public"."conversations" + ("id") on update restrict on delete restrict; diff --git a/hasura/migrations/1686335272144_set_fk_public_job_conversations_conversationid/up.sql b/hasura/migrations/1686335272144_set_fk_public_job_conversations_conversationid/up.sql new file mode 100644 index 000000000..524d765ed --- /dev/null +++ b/hasura/migrations/1686335272144_set_fk_public_job_conversations_conversationid/up.sql @@ -0,0 +1,5 @@ +alter table "public"."job_conversations" drop constraint "job_conversations_conversationid_fkey", + add constraint "job_conversations_conversationid_fkey" + foreign key ("conversationid") + references "public"."conversations" + ("id") on update cascade on delete cascade; diff --git a/hasura/migrations/1688674667178_alter_table_public_joblines_add_column_assigned_team/down.sql b/hasura/migrations/1688674667178_alter_table_public_joblines_add_column_assigned_team/down.sql new file mode 100644 index 000000000..61af19807 --- /dev/null +++ b/hasura/migrations/1688674667178_alter_table_public_joblines_add_column_assigned_team/down.sql @@ -0,0 +1,4 @@ +-- Could not auto-generate a down migration. +-- Please write an appropriate down migration for the SQL below: +-- alter table "public"."joblines" add column "assigned_team" uuid +-- null; diff --git a/hasura/migrations/1688674667178_alter_table_public_joblines_add_column_assigned_team/up.sql b/hasura/migrations/1688674667178_alter_table_public_joblines_add_column_assigned_team/up.sql new file mode 100644 index 000000000..0128442fe --- /dev/null +++ b/hasura/migrations/1688674667178_alter_table_public_joblines_add_column_assigned_team/up.sql @@ -0,0 +1,2 @@ +alter table "public"."joblines" add column "assigned_team" uuid + null; diff --git a/hasura/migrations/1688674763806_set_fk_public_joblines_assigned_team/down.sql b/hasura/migrations/1688674763806_set_fk_public_joblines_assigned_team/down.sql new file mode 100644 index 000000000..651570671 --- /dev/null +++ b/hasura/migrations/1688674763806_set_fk_public_joblines_assigned_team/down.sql @@ -0,0 +1 @@ +alter table "public"."joblines" drop constraint "joblines_assigned_team_fkey"; diff --git a/hasura/migrations/1688674763806_set_fk_public_joblines_assigned_team/up.sql b/hasura/migrations/1688674763806_set_fk_public_joblines_assigned_team/up.sql new file mode 100644 index 000000000..4740318f8 --- /dev/null +++ b/hasura/migrations/1688674763806_set_fk_public_joblines_assigned_team/up.sql @@ -0,0 +1,5 @@ +alter table "public"."joblines" + add constraint "joblines_assigned_team_fkey" + foreign key ("assigned_team") + references "public"."employee_teams" + ("id") on update restrict on delete restrict; diff --git a/hasura/migrations/1689808180226_alter_table_public_jobs_add_column_completed_tasks/down.sql b/hasura/migrations/1689808180226_alter_table_public_jobs_add_column_completed_tasks/down.sql new file mode 100644 index 000000000..a15ccb965 --- /dev/null +++ b/hasura/migrations/1689808180226_alter_table_public_jobs_add_column_completed_tasks/down.sql @@ -0,0 +1,4 @@ +-- Could not auto-generate a down migration. +-- Please write an appropriate down migration for the SQL below: +-- alter table "public"."jobs" add column "completed_tasks" jsonb +-- null default jsonb_build_array(); diff --git a/hasura/migrations/1689808180226_alter_table_public_jobs_add_column_completed_tasks/up.sql b/hasura/migrations/1689808180226_alter_table_public_jobs_add_column_completed_tasks/up.sql new file mode 100644 index 000000000..430669277 --- /dev/null +++ b/hasura/migrations/1689808180226_alter_table_public_jobs_add_column_completed_tasks/up.sql @@ -0,0 +1,2 @@ +alter table "public"."jobs" add column "completed_tasks" jsonb + null default jsonb_build_array(); diff --git a/hasura/migrations/1689973479186_alter_table_public_jobs_add_column_void_date/down.sql b/hasura/migrations/1689973479186_alter_table_public_jobs_add_column_void_date/down.sql new file mode 100644 index 000000000..b8c658dd0 --- /dev/null +++ b/hasura/migrations/1689973479186_alter_table_public_jobs_add_column_void_date/down.sql @@ -0,0 +1,4 @@ +-- Could not auto-generate a down migration. +-- Please write an appropriate down migration for the SQL below: +-- alter table "public"."jobs" add column "void_date" Timestamp +-- null; diff --git a/hasura/migrations/1689973479186_alter_table_public_jobs_add_column_void_date/up.sql b/hasura/migrations/1689973479186_alter_table_public_jobs_add_column_void_date/up.sql new file mode 100644 index 000000000..7c41363ed --- /dev/null +++ b/hasura/migrations/1689973479186_alter_table_public_jobs_add_column_void_date/up.sql @@ -0,0 +1,2 @@ +alter table "public"."jobs" add column "void_date" Timestamp + null; diff --git a/hasura/migrations/1689973508700_alter_table_public_jobs_alter_column_void_date/down.sql b/hasura/migrations/1689973508700_alter_table_public_jobs_alter_column_void_date/down.sql new file mode 100644 index 000000000..42a67c083 --- /dev/null +++ b/hasura/migrations/1689973508700_alter_table_public_jobs_alter_column_void_date/down.sql @@ -0,0 +1 @@ +ALTER TABLE "public"."jobs" ALTER COLUMN "void_date" TYPE timestamp without time zone; diff --git a/hasura/migrations/1689973508700_alter_table_public_jobs_alter_column_void_date/up.sql b/hasura/migrations/1689973508700_alter_table_public_jobs_alter_column_void_date/up.sql new file mode 100644 index 000000000..97504d6c8 --- /dev/null +++ b/hasura/migrations/1689973508700_alter_table_public_jobs_alter_column_void_date/up.sql @@ -0,0 +1 @@ +ALTER TABLE "public"."jobs" ALTER COLUMN "void_date" TYPE timestamptz; diff --git a/hasura/migrations/1689978551428_alter_table_public_jobs_alter_column_void_date/down.sql b/hasura/migrations/1689978551428_alter_table_public_jobs_alter_column_void_date/down.sql new file mode 100644 index 000000000..085b24071 --- /dev/null +++ b/hasura/migrations/1689978551428_alter_table_public_jobs_alter_column_void_date/down.sql @@ -0,0 +1 @@ +alter table "public"."jobs" rename column "date_void" to "void_date"; diff --git a/hasura/migrations/1689978551428_alter_table_public_jobs_alter_column_void_date/up.sql b/hasura/migrations/1689978551428_alter_table_public_jobs_alter_column_void_date/up.sql new file mode 100644 index 000000000..4a6b2bfc8 --- /dev/null +++ b/hasura/migrations/1689978551428_alter_table_public_jobs_alter_column_void_date/up.sql @@ -0,0 +1 @@ +alter table "public"."jobs" rename column "void_date" to "date_void"; diff --git a/hasura/migrations/1690482057097_alter_table_public_timetickets_add_column_task_name/down.sql b/hasura/migrations/1690482057097_alter_table_public_timetickets_add_column_task_name/down.sql new file mode 100644 index 000000000..9335d1ce8 --- /dev/null +++ b/hasura/migrations/1690482057097_alter_table_public_timetickets_add_column_task_name/down.sql @@ -0,0 +1,4 @@ +-- Could not auto-generate a down migration. +-- Please write an appropriate down migration for the SQL below: +-- alter table "public"."timetickets" add column "task_name" text +-- null; diff --git a/hasura/migrations/1690482057097_alter_table_public_timetickets_add_column_task_name/up.sql b/hasura/migrations/1690482057097_alter_table_public_timetickets_add_column_task_name/up.sql new file mode 100644 index 000000000..e88d3a79b --- /dev/null +++ b/hasura/migrations/1690482057097_alter_table_public_timetickets_add_column_task_name/up.sql @@ -0,0 +1,2 @@ +alter table "public"."timetickets" add column "task_name" text + null; diff --git a/hasura/migrations/1691177401095_create_index_parts_dispatch_employeeid/down.sql b/hasura/migrations/1691177401095_create_index_parts_dispatch_employeeid/down.sql new file mode 100644 index 000000000..d49495dbb --- /dev/null +++ b/hasura/migrations/1691177401095_create_index_parts_dispatch_employeeid/down.sql @@ -0,0 +1 @@ +DROP INDEX IF EXISTS "public"."parts_dispatch_employeeid"; diff --git a/hasura/migrations/1691177401095_create_index_parts_dispatch_employeeid/up.sql b/hasura/migrations/1691177401095_create_index_parts_dispatch_employeeid/up.sql new file mode 100644 index 000000000..cfeb1460e --- /dev/null +++ b/hasura/migrations/1691177401095_create_index_parts_dispatch_employeeid/up.sql @@ -0,0 +1,2 @@ +CREATE INDEX "parts_dispatch_employeeid" on + "public"."parts_dispatch" using btree ("employeeid"); diff --git a/hasura/migrations/1691177452060_create_index_parts_dispatch_dispatchid/down.sql b/hasura/migrations/1691177452060_create_index_parts_dispatch_dispatchid/down.sql new file mode 100644 index 000000000..edc36c9f2 --- /dev/null +++ b/hasura/migrations/1691177452060_create_index_parts_dispatch_dispatchid/down.sql @@ -0,0 +1 @@ +DROP INDEX IF EXISTS "public"."parts_dispatch_dispatchid"; diff --git a/hasura/migrations/1691177452060_create_index_parts_dispatch_dispatchid/up.sql b/hasura/migrations/1691177452060_create_index_parts_dispatch_dispatchid/up.sql new file mode 100644 index 000000000..643325a9f --- /dev/null +++ b/hasura/migrations/1691177452060_create_index_parts_dispatch_dispatchid/up.sql @@ -0,0 +1,2 @@ +CREATE INDEX "parts_dispatch_dispatchid" on + "public"."parts_dispatch_lines" using btree ("partsdispatchid"); diff --git a/hasura/migrations/1691177467690_create_index_parts_dispatch_line_accepted_at/down.sql b/hasura/migrations/1691177467690_create_index_parts_dispatch_line_accepted_at/down.sql new file mode 100644 index 000000000..5268f9914 --- /dev/null +++ b/hasura/migrations/1691177467690_create_index_parts_dispatch_line_accepted_at/down.sql @@ -0,0 +1 @@ +DROP INDEX IF EXISTS "public"."parts_dispatch_line_accepted_at"; diff --git a/hasura/migrations/1691177467690_create_index_parts_dispatch_line_accepted_at/up.sql b/hasura/migrations/1691177467690_create_index_parts_dispatch_line_accepted_at/up.sql new file mode 100644 index 000000000..9d88ca6cc --- /dev/null +++ b/hasura/migrations/1691177467690_create_index_parts_dispatch_line_accepted_at/up.sql @@ -0,0 +1,2 @@ +CREATE INDEX "parts_dispatch_line_accepted_at" on + "public"."parts_dispatch_lines" using btree ("accepted_at"); diff --git a/server/accounting/pbs/pbs-ap-allocations.js b/server/accounting/pbs/pbs-ap-allocations.js index 10b11cb9b..706b1fc59 100644 --- a/server/accounting/pbs/pbs-ap-allocations.js +++ b/server/accounting/pbs/pbs-ap-allocations.js @@ -123,8 +123,14 @@ async function PbsCalculateAllocationsAp(socket, billids) { if (!billHash[cc.name]) { billHash[cc.name] = { - Account: cc.dms_acctnumber, - ControlNumber: bill.vendor.dmsid, + Account: + bodyshop.pbs_configuration.appostingaccount === "wip" + ? cc.dms_wip_acctnumber + : cc.dms_acctnumber, + ControlNumber: + bodyshop.pbs_configuration.apcontrol === "ro" + ? bill.job.ro_number + : bill.vendor.dmsid, Amount: Dinero(), // Comment: "String", AdditionalInfo: bill.vendor.name, diff --git a/server/accounting/qbxml/qbxml-payables.js b/server/accounting/qbxml/qbxml-payables.js index 5bc99cf5f..26f0f2d8a 100644 --- a/server/accounting/qbxml/qbxml-payables.js +++ b/server/accounting/qbxml/qbxml-payables.js @@ -133,7 +133,13 @@ const generateBillLine = (billLine, responsibilityCenters, jobClass) => { const findTaxCode = (billLine, taxcode) => { const { applicable_taxes: { local, state, federal }, - } = billLine; + } = + billLine.applicable_taxes === null + ? { + ...billLine, + applicable_taxes: { local: false, state: false, federal: false }, + } + : billLine; const t = taxcode.filter( (t) => !!t.local === !!local && diff --git a/server/accounting/qbxml/qbxml-receivables.js b/server/accounting/qbxml/qbxml-receivables.js index e697e0985..5489f859e 100644 --- a/server/accounting/qbxml/qbxml-receivables.js +++ b/server/accounting/qbxml/qbxml-receivables.js @@ -262,10 +262,10 @@ const generateInvoiceQbxml = ( RefNumber: jobs_by_pk.ro_number, BillAddress: { Addr1: jobs_by_pk.ownr_co_nm - ? jobs_by_pk.ownr_co_nm.substring(0, 30) - : `${`${jobs_by_pk.ownr_ln || ""} ${ - jobs_by_pk.ownr_fn || "" - }`.substring(0, 30)}`, + ? jobs_by_pk.ownr_co_nm.substring(0, 30).trim() + : `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}` + .substring(0, 30) + .trim()}`, Addr2: jobs_by_pk.ownr_addr1, Addr3: jobs_by_pk.ownr_addr2, City: jobs_by_pk.ownr_city, @@ -274,10 +274,10 @@ const generateInvoiceQbxml = ( }, ShipAddress: { Addr1: jobs_by_pk.ownr_co_nm - ? jobs_by_pk.ownr_co_nm.substring(0, 30) - : `${`${jobs_by_pk.ownr_ln || ""} ${ - jobs_by_pk.ownr_fn || "" - }`.substring(0, 30)}`, + ? jobs_by_pk.ownr_co_nm.substring(0, 30).trim() + : `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}` + .substring(0, 30) + .trim()}`, Addr2: jobs_by_pk.ownr_addr1, Addr3: jobs_by_pk.ownr_addr2, City: jobs_by_pk.ownr_city, diff --git a/server/accounting/qbxml/qbxml-utils.js b/server/accounting/qbxml/qbxml-utils.js index af59b3cf7..8909cbb2a 100644 --- a/server/accounting/qbxml/qbxml-utils.js +++ b/server/accounting/qbxml/qbxml-utils.js @@ -21,9 +21,9 @@ exports.generateOwnerTier = (jobs_by_pk, isThreeTier, twotierpref) => { ? `${jobs_by_pk.ownr_co_nm.substring(0, 30)} #${ jobs_by_pk.owner.accountingid || "" }` - : `${`${jobs_by_pk.ownr_ln || ""} ${ - jobs_by_pk.ownr_fn || "" - }`.substring(0, 30)} #${jobs_by_pk.owner.accountingid || ""}` + : `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}` + .substring(0, 30) + .trim()} #${jobs_by_pk.owner.accountingid || ""}` ) .trim() .replace(":", " "); @@ -39,9 +39,9 @@ exports.generateOwnerTier = (jobs_by_pk, isThreeTier, twotierpref) => { ? `${jobs_by_pk.ownr_co_nm.substring(0, 30)} #${ jobs_by_pk.owner.accountingid || "" }` - : `${`${jobs_by_pk.ownr_ln || ""} ${ - jobs_by_pk.ownr_fn || "" - }`.substring(0, 30)} #${jobs_by_pk.owner.accountingid || ""}` + : `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}` + .substring(0, 30) + .trim()} #${jobs_by_pk.owner.accountingid || ""}` ) .trim() .replace(":", " "); diff --git a/server/cdk/cdk-job-export.js b/server/cdk/cdk-job-export.js index c20a899a1..5fee6b30d 100644 --- a/server/cdk/cdk-job-export.js +++ b/server/cdk/cdk-job-export.js @@ -717,18 +717,24 @@ async function InsertDmsVehicle(socket) { dealer: { dealerNumber: socket.JobData.bodyshop.cdk_dealerid, ...(socket.txEnvelope.inservicedate && { - inServiceDate: moment(socket.txEnvelope.inservicedate) - //.tz(socket.JobData.bodyshop.timezone) - .startOf("day") - .toISOString(), + inServiceDate: + socket.txEnvelope.dms_unsold === true + ? "" + : moment(socket.txEnvelope.inservicedate) + //.tz(socket.JobData.bodyshop.timezone) + .startOf("day") + .toISOString(), }), vehicleId: socket.DMSVid.vehiclesVehId, }, manufacturer: {}, vehicle: { - deliveryDate: moment() - // .tz(socket.JobData.bodyshop.timezone) - .format("YYYYMMDD"), + deliveryDate: + socket.txEnvelope.dms_unsold === true + ? "" + : moment() + // .tz(socket.JobData.bodyshop.timezone) + .format("YYYYMMDD"), licensePlateNo: socket.JobData.plate_no === null ? null @@ -860,19 +866,25 @@ async function UpdateDmsVehicle(socket) { ...socket.DMSVeh.dealer, ...((socket.txEnvelope.inservicedate || socket.DMSVeh.dealer.inServiceDate) && { - inServiceDate: moment( - socket.DMSVeh.dealer.inServiceDate || - socket.txEnvelope.inservicedate - ) - // .tz(socket.JobData.bodyshop.timezone) - .toISOString(), + inServiceDate: + socket.txEnvelope.dms_unsold === true + ? "" + : moment( + socket.DMSVeh.dealer.inServiceDate || + socket.txEnvelope.inservicedate + ) + // .tz(socket.JobData.bodyshop.timezone) + .toISOString(), }), }, vehicle: { ...socket.DMSVeh.vehicle, - deliveryDate: moment(socket.DMSVeh.vehicle.deliveryDate) - //.tz(socket.JobData.bodyshop.timezone) - .toISOString(), + deliveryDate: + socket.txEnvelope.dms_unsold === true + ? "" + : moment(socket.DMSVeh.vehicle.deliveryDate) + //.tz(socket.JobData.bodyshop.timezone) + .toISOString(), }, owners: ids, }, diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index e98cdaf9a..ce28681ef 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -1697,6 +1697,7 @@ query GET_PBS_AP_ALLOCATIONS($billids: [uuid!]) { md_responsibility_centers timezone pbs_serialnumber + pbs_configuration id } bills(where: {id: {_in: $billids}, exported:{_eq: false}}) { diff --git a/server/opensearch/os-handler.js b/server/opensearch/os-handler.js index 1266e6a6c..0bc72170c 100644 --- a/server/opensearch/os-handler.js +++ b/server/opensearch/os-handler.js @@ -86,6 +86,7 @@ async function OpenSearchUpdateHandler(req, res) { "v_model_yr", "v_make_desc", "v_model_desc", + "v_vin", ]); document.bodyshopid = req.body.event.data.new.shopid; break; @@ -139,7 +140,7 @@ async function OpenSearchUpdateHandler(req, res) { "exported_at", "invoice_number", "is_credit_memo", - "total" + "total", ]), ...bill.bills_by_pk, bodyshopid: bill.bills_by_pk.job.shopid, @@ -244,17 +245,54 @@ async function OpensearchSearchHandler(req, res) { bool: { must: [ { - multi_match: { - query: search, - type: "phrase_prefix", - //fields: ["*"], - // fuzziness: "5", - //prefix_length: 2, + match: { + bodyshopid: assocs.associations[0].shopid, }, }, { - match: { - bodyshopid: assocs.associations[0].shopid, + bool: { + should: [ + { + multi_match: { + query: search, + type: "cross_fields", + fields: ["*ownr_fn", "*ownr_ln"], + }, + }, + { + multi_match: { + query: search, + type: "most_fields", + fields: [ + "*v_model_yr", + "*v_make_desc^2", + "*v_model_desc^3", + ], + }, + }, + { + query_string: { + query: `*${search}*`, + + fields: [ + "*ro_number^20", + "*clm_no^14", + "*v_vin^12", + "*plate_no^12", + "*ownr_ln^10", + "transactionid^10", + "paymentnum^10", + "invoice_number^10", + "*ownr_fn^8", + "*ownr_co_nm^8", + "*ownr_ph1^8", + "*ownr_ph2^8", + "*", + ], + }, + }, + ], + minimum_should_match: 1, }, }, ],