diff --git a/client/src/components/courtesy-car-form/courtesy-car-form.component.jsx b/client/src/components/courtesy-car-form/courtesy-car-form.component.jsx index 29edf5243..a6da8ddc3 100644 --- a/client/src/components/courtesy-car-form/courtesy-car-form.component.jsx +++ b/client/src/components/courtesy-car-form/courtesy-car-form.component.jsx @@ -34,7 +34,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) { {/* */} - - + - - - +
+ + + + + p.mileage !== c.mileage || p.nextservicekm !== c.nextservicekm + } + > + {() => { + const nextservicekm = form.getFieldValue("nextservicekm"); + const mileageOver = + nextservicekm <= form.getFieldValue("mileage"); + + if (mileageOver) + return ( + + + + {t("contracts.labels.cardueforservice")} + + {`${nextservicekm} km`} + + ); + + return <>; + }} + +
- p.mileage !== c.mileage || - p.nextservicedate !== c.nextservicedate || - p.nextservicekm !== c.nextservicekm - } + shouldUpdate={(p, c) => p.nextservicedate !== c.nextservicedate} > {() => { const nextservicedate = form.getFieldValue("nextservicedate"); - const nextservicekm = form.getFieldValue("nextservicekm"); - - const mileageOver = - nextservicekm <= form.getFieldValue("mileage"); - const dueForService = - nextservicedate && moment(nextservicedate).isBefore(moment()); + nextservicedate && + moment(nextservicedate).endOf("day").isSameOrBefore(moment()); - if (mileageOver || dueForService) + if (dueForService) return ( {t("contracts.labels.cardueforservice")} - {`${nextservicekm} km`} {nextservicedate} @@ -282,7 +299,8 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) { {() => { const expires = form.getFieldValue("registrationexpires"); - const dateover = expires && moment(expires).isBefore(moment()); + const dateover = + expires && moment(expires).endOf("day").isBefore(moment()); if (dateover) return ( @@ -317,7 +335,8 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) { {() => { const expires = form.getFieldValue("insuranceexpires"); - const dateover = expires && moment(expires).isBefore(moment()); + const dateover = + expires && moment(expires).endOf("day").isBefore(moment()); if (dateover) return ( diff --git a/client/src/components/job-at-change/schedule-event.container.jsx b/client/src/components/job-at-change/schedule-event.container.jsx index 89065f0f7..60abf9c61 100644 --- a/client/src/components/job-at-change/schedule-event.container.jsx +++ b/client/src/components/job-at-change/schedule-event.container.jsx @@ -2,12 +2,16 @@ import { useMutation } from "@apollo/client"; import { notification } from "antd"; import React from "react"; import { useTranslation } from "react-i18next"; +import { useDispatch } from "react-redux"; import { logImEXEvent } from "../../firebase/firebase.utils"; import { CANCEL_APPOINTMENT_BY_ID } from "../../graphql/appointments.queries"; import { UPDATE_JOB } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import AuditTrailMapping from "../../utils/AuditTrailMappings"; import ScheduleEventComponent from "./schedule-event.component"; export default function ScheduleEventContainer({ bodyshop, event, refetch }) { + const dispatch = useDispatch(); const { t } = useTranslation(); const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID); const [updateJob] = useMutation(UPDATE_JOB); @@ -34,16 +38,24 @@ export default function ScheduleEventContainer({ bodyshop, event, refetch }) { const jobUpdate = await updateJob({ variables: { jobId: event.job.id, - job: { date_scheduled: null, scheduled_in: null, scheduled_completion: null, lost_sale_reason, + date_lost_sale: new Date(), status: bodyshop.md_ro_statuses.default_imported, }, }, }); + if (!jobUpdate.errors) { + dispatch( + insertAuditTrail({ + jobid: event.job.id, + operation: AuditTrailMapping.appointmentcancel(lost_sale_reason), + }) + ); + } if (!!jobUpdate.errors) { notification["error"]({ message: t("jobs.errors.updating", { 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 bfddbfefc..5ac31da74 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 @@ -13,6 +13,7 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { insertAuditTrail } from "../../redux/application/application.actions"; +import { DateTimeFormat } from "./../../utils/DateFormatter"; const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser @@ -53,7 +54,7 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) { operation: AuditTrailMapping.admin_jobfieldchange( key, changedAuditFields[key] instanceof moment - ? moment(changedAuditFields[key]).format("MM/DD/YYYY hh:mm a") + ? DateTimeFormat(changedAuditFields[key]) : changedAuditFields[key] ), }); @@ -179,6 +180,12 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) { + + + diff --git a/client/src/components/jobs-available-table/jobs-available-table.container.jsx b/client/src/components/jobs-available-table/jobs-available-table.container.jsx index 0e74276b2..12d10baf9 100644 --- a/client/src/components/jobs-available-table/jobs-available-table.container.jsx +++ b/client/src/components/jobs-available-table/jobs-available-table.container.jsx @@ -73,6 +73,8 @@ export function JobsAvailableContainer({ const [selectedJob, setSelectedJob] = useState(null); const [selectedOwner, setSelectedOwner] = useState(null); + const [partsQueueToggle, setPartsQueueToggle] = useState(bodyshop.md_functionality_toggles.parts_queue_toggle); + const [insertLoading, setInsertLoading] = useState(false); const [insertNote] = useMutation(INSERT_NEW_NOTE); @@ -94,6 +96,7 @@ export function JobsAvailableContainer({ logImEXEvent("job_import_new"); setOwnerModalVisible(false); + setInsertLoading(true); const estData = replaceEmpty(estDataRaw.data.available_jobs_by_pk); @@ -120,7 +123,7 @@ export function JobsAvailableContainer({ let existingVehicles; if (estData.est_data.v_vin) { - //There's vehicle data, need to double check the VIN. + //There's vehicle data, need to double-check the VIN. existingVehicles = await client.query({ query: SEARCH_VEHICLE_BY_VIN, variables: { @@ -143,7 +146,7 @@ export function JobsAvailableContainer({ text: t("jobs.labels.importnote"), }, }, - queued_for_parts: true, + queued_for_parts: partsQueueToggle, ...(existingVehicles && existingVehicles.data.vehicles.length > 0 ? { vehicleid: existingVehicles.data.vehicles[0].id, vehicle: null } : {}), @@ -157,46 +160,51 @@ export function JobsAvailableContainer({ delete newJob.vehicle; } - insertNewJob({ - variables: { - job: newJob, - }, - }) - .then((r) => { - if (CriticalPartsScanning.treatment === "on") { - CriticalPartsScan(r.data.insert_jobs.returning[0].id); - } - notification["success"]({ - message: t("jobs.successes.created"), - onClick: () => { - history.push(`/manage/jobs/${r.data.insert_jobs.returning[0].id}`); - }, - }); - //Job has been inserted. Clean up the available jobs record. + try { + const r = await insertNewJob({ + variables: { + job: newJob, + }, + }); - insertAuditTrail({ - jobid: r.data.insert_jobs.returning[0].id, - operation: AuditTrailMapping.jobimported(), - }); + if (CriticalPartsScanning.treatment === "on") { + CriticalPartsScan(r.data.insert_jobs.returning[0].id); + } - deleteJob({ - variables: { id: estData.id }, - }).then((r) => { - refetch(); - setInsertLoading(false); - }); - }) - .catch((r) => { - //error while inserting - notification["error"]({ - message: t("jobs.errors.creating", { error: r.message }), - }); + notification["success"]({ + message: t("jobs.successes.created"), + onClick: () => { + history.push(`/manage/jobs/${r.data.insert_jobs.returning[0].id}`); + }, + }); + //Job has been inserted. Clean up the available jobs record. + + insertAuditTrail({ + jobid: r.data.insert_jobs.returning[0].id, + operation: AuditTrailMapping.jobimported(), + }); + + deleteJob({ + variables: { id: estData.id }, + }).then((r) => { refetch(); setInsertLoading(false); }); + + setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle); + } catch (err) { + //error while inserting + notification["error"]({ + message: t("jobs.errors.creating", { error: err.message }), + }); + refetch().catch(e => {console.error(`Something went wrong in jobs available table container - ${err.message || ''}`)}); + setInsertLoading(false); + setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle); + } + }; - //Suplement scenario + //Supplement scenario const onJobFindModalOk = async () => { logImEXEvent("job_import_supplement"); @@ -248,11 +256,14 @@ export function JobsAvailableContainer({ // "0.00" // ), // job_totals: newTotals, - // queued_for_parts: true, + queued_for_parts: partsQueueToggle, }, }, }); - if (CriticalPartsScanning.treatment === "on") { + + setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle); + + if (CriticalPartsScanning.treatment === "on") { CriticalPartsScan(updateResult.data.update_jobs.returning[0].id); } if (updateResult.errors) { @@ -327,12 +338,14 @@ export function JobsAvailableContainer({ const onOwnerModalCancel = () => { setOwnerModalVisible(false); setSelectedOwner(null); + setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle); }; const onJobModalCancel = () => { setJobModalVisible(false); modalSearchState[1](""); setSelectedJob(null); + setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle); }; const addJobAsNew = (record) => { @@ -353,6 +366,8 @@ export function JobsAvailableContainer({ }, [addJobAsSupp, availableJobId, clm_no]); if (error) return ; + + return ( 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 05cd1b289..dcd6fd941 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 @@ -145,6 +145,13 @@ 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 8531ddfe0..ae3ba9a7b 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 @@ -18,12 +18,14 @@ import { createStructuredSelector } from "reselect"; import { logImEXEvent } from "../../firebase/firebase.utils"; import { CANCEL_APPOINTMENTS_BY_JOB_ID } from "../../graphql/appointments.queries"; import { DELETE_JOB, UPDATE_JOB, VOID_JOB } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { setModalContext } from "../../redux/modals/modals.actions"; import { selectBodyshop, selectCurrentUser, } from "../../redux/user/user.selectors"; +import AuditTrailMapping from "../../utils/AuditTrailMappings"; 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"; @@ -50,6 +52,8 @@ const mapDispatchToProps = (dispatch) => ({ dispatch(setModalContext({ context: context, modal: "timeTicket" })), setCardPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "cardPayment" })), + insertAuditTrail: ({ jobid, operation }) => + dispatch(insertAuditTrail({ jobid, operation })), }); export function JobsDetailHeaderActions({ @@ -64,6 +68,7 @@ export function JobsDetailHeaderActions({ jobRO, setTimeTicketContext, setCardPaymentContext, + insertAuditTrail, }) { const { t } = useTranslation(); const client = useApolloClient(); @@ -158,6 +163,7 @@ export function JobsDetailHeaderActions({ scheduled_in: null, scheduled_completion: null, lost_sale_reason, + date_lost_sale: new Date(), status: bodyshop.md_ro_statuses.default_imported, }, }, @@ -166,6 +172,11 @@ export function JobsDetailHeaderActions({ notification["success"]({ message: t("appointments.successes.canceled"), }); + insertAuditTrail({ + jobid: job.id, + operation: + AuditTrailMapping.appointmentcancel(lost_sale_reason), + }); return; } }} diff --git a/client/src/components/jobs-find-modal/jobs-find-modal.component.jsx b/client/src/components/jobs-find-modal/jobs-find-modal.component.jsx index 330c0e5db..37a88e1ef 100644 --- a/client/src/components/jobs-find-modal/jobs-find-modal.component.jsx +++ b/client/src/components/jobs-find-modal/jobs-find-modal.component.jsx @@ -14,6 +14,8 @@ export default function JobsFindModalComponent({ importOptionsState, modalSearchState, jobsListRefetch, + partsQueueToggle, + setPartsQueueToggle, }) { const { t } = useTranslation(); const [modalSearch, setModalSearch] = modalSearchState; @@ -199,6 +201,12 @@ export default function JobsFindModalComponent({ > {t("jobs.labels.override_header")} + setPartsQueueToggle(e.target.checked)} + > + {t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")} + ); } diff --git a/client/src/components/jobs-find-modal/jobs-find-modal.container.jsx b/client/src/components/jobs-find-modal/jobs-find-modal.container.jsx index 7bb3591cb..64dc8be9a 100644 --- a/client/src/components/jobs-find-modal/jobs-find-modal.container.jsx +++ b/client/src/components/jobs-find-modal/jobs-find-modal.container.jsx @@ -24,6 +24,8 @@ export default connect( setSelectedJob, importOptionsState, modalSearchState, + partsQueueToggle, + setPartsQueueToggle, ...modalProps }) { const { t } = useTranslation(); @@ -91,6 +93,8 @@ export default connect( jobsListRefetch={jobsList.refetch} jobsList={jobsData} modalSearchState={modalSearchState} + partsQueueToggle={partsQueueToggle} + setPartsQueueToggle={setPartsQueueToggle} /> ) : null} diff --git a/client/src/components/owner-find-modal/owner-find-modal.component.jsx b/client/src/components/owner-find-modal/owner-find-modal.component.jsx index 177b4326e..3a5f313cd 100644 --- a/client/src/components/owner-find-modal/owner-find-modal.component.jsx +++ b/client/src/components/owner-find-modal/owner-find-modal.component.jsx @@ -8,10 +8,11 @@ export default function OwnerFindModalComponent({ setSelectedOwner, ownersListLoading, ownersList, + partsQueueToggle, + setPartsQueueToggle, }) { //setSelectedOwner is used to set the record id of the owner to use for adding the job. const { t } = useTranslation(); - const columns = [ { title: t("owners.fields.ownr_ln"), @@ -109,6 +110,12 @@ export default function OwnerFindModalComponent({ > {t("owners.labels.create_new")} + setPartsQueueToggle(e.target.checked)} + > + {t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")} + ); } diff --git a/client/src/components/owner-find-modal/owner-find-modal.container.jsx b/client/src/components/owner-find-modal/owner-find-modal.container.jsx index 9f1397ba1..f6c9453d7 100644 --- a/client/src/components/owner-find-modal/owner-find-modal.container.jsx +++ b/client/src/components/owner-find-modal/owner-find-modal.container.jsx @@ -14,6 +14,8 @@ export default function OwnerFindModalContainer({ owner, selectedOwner, setSelectedOwner, + partsQueueToggle, + setPartsQueueToggle, ...modalProps }) { //use owner object to run query and find what possible owners there are. @@ -59,6 +61,8 @@ export default function OwnerFindModalContainer({ selectedOwner={selectedOwner} setSelectedOwner={setSelectedOwner} ownersListLoading={ownersList.loading} + partsQueueToggle={partsQueueToggle} + setPartsQueueToggle={setPartsQueueToggle} ownersList={ ownersList.data && ownersList.data.search_owners ? ownersList.data.search_owners diff --git a/client/src/components/production-board-kanban/production-board-kanban.component.jsx b/client/src/components/production-board-kanban/production-board-kanban.component.jsx index 6d9750396..b1061f973 100644 --- a/client/src/components/production-board-kanban/production-board-kanban.component.jsx +++ b/client/src/components/production-board-kanban/production-board-kanban.component.jsx @@ -20,9 +20,9 @@ import ProductionBoardCard from "../production-board-kanban-card/production-boar import ProductionListDetailComponent from "../production-list-detail/production-list-detail.component"; import ProductionBoardKanbanCardSettings from "./production-board-kanban.card-settings.component"; //import "@asseinfo/react-kanban/dist/styles.css"; +import CardColorLegend from "../production-board-kanban-card/production-board-kanban-card-color-legend.component"; import "./production-board-kanban.styles.scss"; import { createBoardData } from "./production-board-kanban.utils.js"; -import CardColorLegend from "../production-board-kanban-card/production-board-kanban-card-color-legend.component"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, technician: selectTechnician, @@ -153,6 +153,18 @@ export function ProductionBoardKanbanComponent({ 0 ) .toFixed(1); + const totalLAB = data + .reduce( + (acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0), + 0 + ) + .toFixed(1); + const totalLAR = data + .reduce( + (acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0), + 0 + ) + .toFixed(1); const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) .filter((screen) => !!screen[1]) .slice(-1)[0]; @@ -236,6 +248,14 @@ export function ProductionBoardKanbanComponent({ title={t("dashboard.titles.productionhours")} value={totalHrs} /> + + acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0), + 0 + ) + .toFixed(1); + const totalLAR = data + .reduce( + (acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0), + 0 + ) + .toFixed(1); return (
+ + ({ toggleModalVisible: () => dispatch(toggleModalVisible("schedule")), setEmailOptions: (e) => dispatch(setEmailOptions(e)), + insertAuditTrail: ({ jobid, operation }) => + dispatch(insertAuditTrail({ jobid, operation })), }); export function ScheduleJobModalContainer({ @@ -39,6 +44,7 @@ export function ScheduleJobModalContainer({ toggleModalVisible, setEmailOptions, currentUser, + insertAuditTrail, }) { const { visible, context, actions } = scheduleModal; const { jobId, job, previousEvent } = context; @@ -134,6 +140,15 @@ export function ScheduleJobModalContainer({ }, }); + if (!appt.errors) { + insertAuditTrail({ + jobid: job.id, + operation: AuditTrailMapping.appointmentinsert( + DateTimeFormat(values.start) + ), + }); + } + if (!!appt.errors) { notification["error"]({ message: t("appointments.errors.saving", { @@ -155,6 +170,7 @@ export function ScheduleJobModalContainer({ scheduled_in: values.start, scheduled_completion: values.scheduled_completion, lost_sale_reason: null, + date_lost_sale: null, }, }, }); diff --git a/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx index d2ced5334..0117279d5 100644 --- a/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx +++ b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx @@ -2,11 +2,9 @@ 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"; @@ -31,12 +29,8 @@ export default connect( 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 startDate = moment().startOf("month") + const endDate = moment().endOf("month"); const fixedPeriods = useMemo(() => { const endOfThisMonth = moment().endOf("month"); 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 ebf2accaf..5f7d31c03 100644 --- a/client/src/components/shop-info/shop-info.general.component.jsx +++ b/client/src/components/shop-info/shop-info.general.component.jsx @@ -42,6 +42,7 @@ export function ShopInfoGeneral({ form, bodyshop }) { bodyshop && bodyshop.imexshopid ); + return (