From 55842faedde30ec301a5060592d60d5f788bb9ef Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Tue, 21 Nov 2023 14:01:33 -0800 Subject: [PATCH 1/9] IO-2468 CC Mileage and Service KMs --- .../courtesy-car-form.component.jsx | 65 ++++++++++++------- 1 file changed, 42 insertions(+), 23 deletions(-) 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 ( From 14ebb280a3e8245672e1b72b8f4bcce956721fcf Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 21 Nov 2023 17:46:49 -0500 Subject: [PATCH 2/9] Add toggles to two modals to allow for auto parts queue toggle --- .../jobs-available-table.container.jsx | 94 +++++++++++-------- .../jobs-find-modal.component.jsx | 8 ++ .../jobs-find-modal.container.jsx | 4 + .../owner-find-modal.component.jsx | 9 +- .../owner-find-modal.container.jsx | 4 + .../shop-info/shop-info.general.component.jsx | 8 ++ client/src/graphql/bodyshop.queries.js | 2 + client/src/translations/en_us/common.json | 3 + client/src/translations/es/common.json | 3 + client/src/translations/fr/common.json | 3 + hasura/metadata/tables.yaml | 4 + .../down.sql | 4 + .../up.sql | 2 + 13 files changed, 110 insertions(+), 38 deletions(-) create mode 100644 hasura/migrations/1700599672091_alter_table_public_bodyshops_add_column_md_functionality_toggles/down.sql create mode 100644 hasura/migrations/1700599672091_alter_table_public_bodyshops_add_column_md_functionality_toggles/up.sql 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-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/shop-info/shop-info.general.component.jsx b/client/src/components/shop-info/shop-info.general.component.jsx index ebf2accaf..73861abc4 100644 --- a/client/src/components/shop-info/shop-info.general.component.jsx +++ b/client/src/components/shop-info/shop-info.general.component.jsx @@ -42,12 +42,20 @@ export function ShopInfoGeneral({ form, bodyshop }) { bodyshop && bodyshop.imexshopid ); + return (
+ + + Date: Tue, 21 Nov 2023 17:00:33 -0800 Subject: [PATCH 3/9] IO-2435 Total LAB & LAR in Production Board Header --- .../production-list-table.component.jsx | 20 +++++++++++++++++++ client/src/translations/en_us/common.json | 6 ++++-- client/src/translations/es/common.json | 2 ++ client/src/translations/fr/common.json | 2 ++ 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/client/src/components/production-list-table/production-list-table.component.jsx b/client/src/components/production-list-table/production-list-table.component.jsx index 1585c6480..3ea3391d3 100644 --- a/client/src/components/production-list-table/production-list-table.component.jsx +++ b/client/src/components/production-list-table/production-list-table.component.jsx @@ -184,6 +184,18 @@ export function ProductionListTable({ 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); return (
+ + Date: Tue, 21 Nov 2023 17:11:27 -0800 Subject: [PATCH 4/9] IO-2435 Production Board Visual Breakout --- .../production-board-kanban.component.jsx | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) 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} /> + + Date: Wed, 22 Nov 2023 17:32:35 -0800 Subject: [PATCH 5/9] IO-2214 Lost Sale Date Add date_lost_sale to track date sale was lost, add in Audit Trail for both cancel and insertion of schedule, standardize date format on output. --- .../job-at-change/schedule-event.container.jsx | 14 +++++++++++++- .../jobs-admin-dates.component.jsx | 9 ++++++++- .../jobs-detail-dates.component.jsx | 7 +++++++ .../jobs-detail-header-actions.component.jsx | 11 +++++++++++ .../schedule-job-modal.container.jsx | 16 ++++++++++++++++ client/src/graphql/appointments.queries.js | 1 + client/src/graphql/jobs.queries.js | 2 ++ .../jobs-detail/jobs-detail.page.component.jsx | 17 +++++++++-------- client/src/translations/en_us/common.json | 3 +++ client/src/translations/es/common.json | 3 +++ client/src/translations/fr/common.json | 3 +++ client/src/utils/AuditTrailMappings.js | 4 ++++ client/src/utils/DateFormatter.jsx | 4 ++++ hasura/metadata/tables.yaml | 6 ++++++ .../down.sql | 4 ++++ .../up.sql | 2 ++ .../down.sql | 1 + .../up.sql | 1 + 18 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 hasura/migrations/1700680020194_alter_table_public_jobs_add_column_date_lost_sale/down.sql create mode 100644 hasura/migrations/1700680020194_alter_table_public_jobs_add_column_date_lost_sale/up.sql create mode 100644 hasura/migrations/1700682617632_alter_table_public_jobs_alter_column_date_lost_sale/down.sql create mode 100644 hasura/migrations/1700682617632_alter_table_public_jobs_alter_column_date_lost_sale/up.sql 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-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/schedule-job-modal/schedule-job-modal.container.jsx b/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx index c8b85be38..19c360a21 100644 --- a/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx +++ b/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx @@ -13,6 +13,7 @@ import { QUERY_APPOINTMENTS_BY_JOBID, } from "../../graphql/appointments.queries"; import { QUERY_LBR_HRS_BY_PK, UPDATE_JOBS } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions"; import { setEmailOptions } from "../../redux/email/email.actions"; import { toggleModalVisible } from "../../redux/modals/modals.actions"; import { selectSchedule } from "../../redux/modals/modals.selectors"; @@ -20,6 +21,8 @@ import { selectBodyshop, selectCurrentUser, } from "../../redux/user/user.selectors"; +import AuditTrailMapping from "../../utils/AuditTrailMappings"; +import { DateTimeFormat } from "../../utils/DateFormatter"; import { TemplateList } from "../../utils/TemplateConstants"; import ScheduleJobModalComponent from "./schedule-job-modal.component"; @@ -31,6 +34,8 @@ const mapStateToProps = createStructuredSelector({ const mapDispatchToProps = (dispatch) => ({ 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/graphql/appointments.queries.js b/client/src/graphql/appointments.queries.js index fa2a3d7a0..9bef78daa 100644 --- a/client/src/graphql/appointments.queries.js +++ b/client/src/graphql/appointments.queries.js @@ -271,6 +271,7 @@ export const CANCEL_APPOINTMENTS_BY_JOB_ID = gql` scheduled_completion status lost_sale_reason + date_lost_sale } } `; diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index 21e82350b..b14e0e03b 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -675,6 +675,7 @@ export const GET_JOB_BY_PK = gql` date_scheduled date_invoiced date_last_contacted + date_lost_sale date_next_contact date_towin date_rentalresp @@ -1077,6 +1078,7 @@ export const UPDATE_JOB = gql` actual_in date_repairstarted date_void + date_lost_sale } } } diff --git a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx index c98e8b1a9..001a18166 100644 --- a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx +++ b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx @@ -3,19 +3,19 @@ import Icon, { CalendarFilled, DollarCircleOutlined, FileImageFilled, - PrinterFilled, - ToolFilled, HistoryOutlined, + PrinterFilled, SyncOutlined, + ToolFilled, } from "@ant-design/icons"; import { Button, Divider, Form, - notification, PageHeader, Space, Tabs, + notification, } from "antd"; import Axios from "axios"; import moment from "moment"; @@ -27,6 +27,7 @@ import { connect } from "react-redux"; import { useHistory, useLocation } from "react-router-dom"; import { createStructuredSelector } from "reselect"; import FormFieldsChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component"; +import JobAuditTrail from "../../components/job-audit-trail/job-audit-trail.component"; import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container"; import JobLineUpsertModalContainer from "../../components/job-lines-upsert-modal/job-lines-upsert-modal.container"; import JobReconciliationModal from "../../components/job-reconciliation-modal/job-reconciliation.modal.container"; @@ -42,17 +43,17 @@ import JobsDetailPliContainer from "../../components/jobs-detail-pli/jobs-detail import JobsDetailRates from "../../components/jobs-detail-rates/jobs-detail-rates.component"; import JobsDetailTotals from "../../components/jobs-detail-totals/jobs-detail-totals.component"; import JobsDocumentsGalleryContainer from "../../components/jobs-documents-gallery/jobs-documents-gallery.container"; +import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container"; import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container"; +import NoteUpsertModalComponent from "../../components/note-upsert-modal/note-upsert-modal.container"; import ScheduleJobModalContainer from "../../components/schedule-job-modal/schedule-job-modal.container"; +import { insertAuditTrail } from "../../redux/application/application.actions"; import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { setModalContext } from "../../redux/modals/modals.actions"; import { selectBodyshop } from "../../redux/user/user.selectors"; -import JobAuditTrail from "../../components/job-audit-trail/job-audit-trail.component"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; -import { insertAuditTrail } from "../../redux/application/application.actions"; -import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container"; import UndefinedToNull from "../../utils/undefinedtonull"; -import NoteUpsertModalComponent from "../../components/note-upsert-modal/note-upsert-modal.container"; +import { DateTimeFormat } from "./../../utils/DateFormatter"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -172,7 +173,7 @@ export function JobsDetailPage({ operation: AuditTrailMapping.jobfieldchange( key, changedAuditFields[key] instanceof moment - ? moment(changedAuditFields[key]).format("MM/DD/YYYY hh:mm a") + ? DateTimeFormat(changedAuditFields[key]) : changedAuditFields[key] ), }); diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 0695dce7b..6071d2695 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -103,6 +103,8 @@ "admin_jobmarkforreexport": "ADMIN: Job marked for re-export.", "admin_jobuninvoice": "ADMIN: Job has been uninvoiced.", "admin_jobunvoid": "ADMIN: Job has been unvoided.", + "appointmentcancel": "Appointment canceled. Lost Reason: {{lost_sale_reason}}.", + "appointmentinsert": "Appointment created. Appointment Date: {{start}}.", "billposted": "Bill with invoice number {{invoice_number}} posted.", "billupdated": "Bill with invoice number {{invoice_number}} updated.", "failedpayment": "Failed payment", @@ -1442,6 +1444,7 @@ "date_exported": "Exported", "date_invoiced": "Invoiced", "date_last_contacted": "Last Contacted Date", + "date_lost_sale": "Lost Sale Date", "date_next_contact": "Next Contact Date", "date_open": "Open", "date_rentalresp": "Shop Rental Responsibility Start", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index d04cb960e..1fa919eb4 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -103,6 +103,8 @@ "admin_jobmarkforreexport": "", "admin_jobuninvoice": "", "admin_jobunvoid": "", + "appointmentcancel": "", + "appointmentinsert": "", "billposted": "", "billupdated": "", "failedpayment": "", @@ -1442,6 +1444,7 @@ "date_exported": "Exportado", "date_invoiced": "Facturado", "date_last_contacted": "", + "date_lost_sale": "", "date_next_contact": "", "date_open": "Abierto", "date_rentalresp": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 4931aed2b..340d64d04 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -103,6 +103,8 @@ "admin_jobmarkforreexport": "", "admin_jobuninvoice": "", "admin_jobunvoid": "", + "appointmentcancel": "", + "appointmentinsert": "", "billposted": "", "billupdated": "", "failedpayment": "", @@ -1442,6 +1444,7 @@ "date_exported": "Exportés", "date_invoiced": "Facturé", "date_last_contacted": "", + "date_lost_sale": "", "date_next_contact": "", "date_open": "Ouvrir", "date_rentalresp": "", diff --git a/client/src/utils/AuditTrailMappings.js b/client/src/utils/AuditTrailMappings.js index 47fdc4535..afa0de1e5 100644 --- a/client/src/utils/AuditTrailMappings.js +++ b/client/src/utils/AuditTrailMappings.js @@ -1,6 +1,10 @@ import i18n from "i18next"; const AuditTrailMapping = { + appointmentcancel: (lost_sale_reason) => + i18n.t("audit_trail.messages.appointmentcancel", { lost_sale_reason }), + appointmentinsert: (start) => + i18n.t("audit_trail.messages.appointmentinsert", { start }), jobstatuschange: (status) => i18n.t("audit_trail.messages.jobstatuschange", { status }), admin_jobstatuschange: (status) => diff --git a/client/src/utils/DateFormatter.jsx b/client/src/utils/DateFormatter.jsx index 134095c78..d034266e3 100644 --- a/client/src/utils/DateFormatter.jsx +++ b/client/src/utils/DateFormatter.jsx @@ -31,3 +31,7 @@ export function TimeAgoFormatter(props) { ) : null; } + +export function DateTimeFormat(value) { + return moment(value).format("MM/DD/YYYY hh:mm A"); +} diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml index 773f384af..d94a97af0 100644 --- a/hasura/metadata/tables.yaml +++ b/hasura/metadata/tables.yaml @@ -890,11 +890,13 @@ - appt_colors - appt_length - attach_pdf_to_email + - autohouseid - bill_allow_post_to_closed - bill_tax_rates - cdk_configuration - cdk_dealerid - city + - claimscorpid - country - created_at - default_adjustment_rate @@ -928,6 +930,7 @@ - md_estimators - md_filehandlers - md_from_emails + - md_functionality_toggles - md_hour_split - md_ins_cos - md_jobline_presets @@ -1026,6 +1029,7 @@ - md_estimators - md_filehandlers - md_from_emails + - md_functionality_toggles - md_hour_split - md_ins_cos - md_jobline_presets @@ -3583,6 +3587,7 @@ - date_exported - date_invoiced - date_last_contacted + - date_lost_sale - date_next_contact - date_open - date_rentalresp @@ -3863,6 +3868,7 @@ - date_exported - date_invoiced - date_last_contacted + - date_lost_sale - date_next_contact - date_open - date_rentalresp diff --git a/hasura/migrations/1700680020194_alter_table_public_jobs_add_column_date_lost_sale/down.sql b/hasura/migrations/1700680020194_alter_table_public_jobs_add_column_date_lost_sale/down.sql new file mode 100644 index 000000000..1313da0c3 --- /dev/null +++ b/hasura/migrations/1700680020194_alter_table_public_jobs_add_column_date_lost_sale/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 "date_lost_sale" timestamp with time zone +-- null; diff --git a/hasura/migrations/1700680020194_alter_table_public_jobs_add_column_date_lost_sale/up.sql b/hasura/migrations/1700680020194_alter_table_public_jobs_add_column_date_lost_sale/up.sql new file mode 100644 index 000000000..933f0acba --- /dev/null +++ b/hasura/migrations/1700680020194_alter_table_public_jobs_add_column_date_lost_sale/up.sql @@ -0,0 +1,2 @@ +alter table "public"."jobs" add column "date_lost_sale" timestamp with time zone + null; diff --git a/hasura/migrations/1700682617632_alter_table_public_jobs_alter_column_date_lost_sale/down.sql b/hasura/migrations/1700682617632_alter_table_public_jobs_alter_column_date_lost_sale/down.sql new file mode 100644 index 000000000..32c37a9fe --- /dev/null +++ b/hasura/migrations/1700682617632_alter_table_public_jobs_alter_column_date_lost_sale/down.sql @@ -0,0 +1 @@ +ALTER TABLE "public"."jobs" ALTER COLUMN "date_lost_sale" TYPE timestamp with time zone; diff --git a/hasura/migrations/1700682617632_alter_table_public_jobs_alter_column_date_lost_sale/up.sql b/hasura/migrations/1700682617632_alter_table_public_jobs_alter_column_date_lost_sale/up.sql new file mode 100644 index 000000000..32c37a9fe --- /dev/null +++ b/hasura/migrations/1700682617632_alter_table_public_jobs_alter_column_date_lost_sale/up.sql @@ -0,0 +1 @@ +ALTER TABLE "public"."jobs" ALTER COLUMN "date_lost_sale" TYPE timestamp with time zone; From 74da3ec1ca5e1e1f0a2e2e9a9101eb276664623c Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 22 Nov 2023 17:51:06 -0800 Subject: [PATCH 6/9] IO-2214 Add new Lost Sales report to Report Center --- client/src/translations/en_us/common.json | 3 ++- client/src/translations/es/common.json | 1 + client/src/translations/fr/common.json | 1 + client/src/utils/TemplateConstants.js | 12 ++++++++++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 6071d2695..03362c86c 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -1444,7 +1444,7 @@ "date_exported": "Exported", "date_invoiced": "Invoiced", "date_last_contacted": "Last Contacted Date", - "date_lost_sale": "Lost Sale Date", + "date_lost_sale": "Lost Sale", "date_next_contact": "Next Contact Date", "date_open": "Open", "date_rentalresp": "Shop Rental Responsibility Start", @@ -2599,6 +2599,7 @@ "jobs_reconcile": "Parts/Sublet/Labor Reconciliation", "jobs_scheduled_completion": "Jobs Scheduled Completion", "lag_time": "Lag Time", + "lost_sales": "Lost Sales", "open_orders": "Open Orders by Date", "open_orders_csr": "Open Orders by CSR", "open_orders_estimator": "Open Orders by Estimator", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 1fa919eb4..02fd16938 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -2599,6 +2599,7 @@ "jobs_reconcile": "", "jobs_scheduled_completion": "", "lag_time": "", + "lost_sales": "", "open_orders": "", "open_orders_csr": "", "open_orders_estimator": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 340d64d04..3fbb38bfa 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -2599,6 +2599,7 @@ "jobs_reconcile": "", "jobs_scheduled_completion": "", "lag_time": "", + "lost_sales": "", "open_orders": "", "open_orders_csr": "", "open_orders_estimator": "", diff --git a/client/src/utils/TemplateConstants.js b/client/src/utils/TemplateConstants.js index 79d9aa353..c5e10d712 100644 --- a/client/src/utils/TemplateConstants.js +++ b/client/src/utils/TemplateConstants.js @@ -2014,6 +2014,18 @@ export const TemplateList = (type, context) => { }, group: "jobs", }, + lost_sales: { + title: i18n.t("reportcenter.templates.lost_sales"), + subject: i18n.t("reportcenter.templates.lost_sales"), + key: "lost_sales", + //idtype: "vendor", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_lost_sale"), + }, + group: "customers", + }, } : {}), ...(!type || type === "courtesycarcontract" From 7dcdd64a176c5b956051b4d7d8f43d34a7992834 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 23 Nov 2023 19:11:09 -0800 Subject: [PATCH 7/9] IO-2438 Adjust Start & End dates for timetickets --- .../scoreboard-timetickets.component.jsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) 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..c0ad5d1fc 100644 --- a/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx +++ b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx @@ -31,12 +31,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"); From 07119e4e7e7f6df617df27ae0aaada54984930df Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 23 Nov 2023 19:14:00 -0800 Subject: [PATCH 8/9] IO-2438 Remove unneeded imports --- .../scoreboard-timetickets.component.jsx | 2 -- 1 file changed, 2 deletions(-) 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 c0ad5d1fc..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"; From 59075ee61001425f22874ae78629a32c82d9fac9 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 24 Nov 2023 12:43:55 -0500 Subject: [PATCH 9/9] Update translations, move configuration toggle for autopartsqueue --- .../shop-info/shop-info.general.component.jsx | 14 +++++++------- client/src/translations/en_us/common.json | 2 +- client/src/translations/es/common.json | 2 +- client/src/translations/fr/common.json | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) 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 73861abc4..5f7d31c03 100644 --- a/client/src/components/shop-info/shop-info.general.component.jsx +++ b/client/src/components/shop-info/shop-info.general.component.jsx @@ -49,13 +49,6 @@ export function ShopInfoGeneral({ form, bodyshop }) { header={t("bodyshop.labels.businessinformation")} id="businessinformation" > - - -