Compare commits

...

10 Commits

Author SHA1 Message Date
Allan Carr
99e1adbe13 Merge branch 'release/2025-04-25' into feature/IO-3213-Hit-and-Run-Toggle
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>

# Conflicts:
#	client/src/components/jobs-detail-general/jobs-detail-general.component.jsx
2025-04-24 08:42:32 -07:00
Allan Carr
eb5c797a43 Merged in feature/IO-3215-Employee-Assignment-Timeticket-Modal (pull request #2279)
IO-3215 Employee Assignment Timeticket Modal

Approved-by: Dave Richer
2025-04-24 15:34:42 +00:00
Allan Carr
0595c5545e Merged in feature/IO-3212-ACV-Amount (pull request #2281)
IO-3212 ACV Amount

Approved-by: Dave Richer
2025-04-24 15:33:41 +00:00
Allan Carr
55944257aa IO-3212 ACV Amount
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2025-04-23 13:16:49 -07:00
Allan Carr
03241778fa IO-3212 ACV Amount
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2025-04-23 13:10:42 -07:00
Allan Carr
555b81fb14 IO-3213 Hit and Run Toggle
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2025-04-23 12:04:29 -07:00
Allan Carr
a56b720e09 IO-3215 Employee Assignment Timeticket Modal
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2025-04-23 09:51:44 -07:00
Allan Carr
b89eede164 Merged in feature/IO-3164-Schedule-Completion-Business-Days (pull request #2277)
IO-3164 Schedule Completion Business Days

Approved-by: Dave Richer
2025-04-22 13:48:43 +00:00
Allan Carr
c21cc8d6b9 IO-3164 Schedule Completion Business Days
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2025-04-21 12:27:33 -07:00
Dave Richer
d02a6bc197 Merged in feature/IO-2282-VSSTA-Integration (pull request #2270)
[DO NOT MERGE] Feature/IO-2282 VSSTA Integration into release/2025-04-25
2025-04-21 17:00:07 +00:00
18 changed files with 120 additions and 37 deletions

View File

@@ -69,12 +69,17 @@ export function ScheduleEventComponent({
variables: { id: event.job.id },
onCompleted: (data) => {
if (data?.jobs_by_pk) {
const totalHours =
(data.jobs_by_pk.labhrs?.aggregate?.sum?.mod_lb_hrs || 0) +
(data.jobs_by_pk.larhrs?.aggregate?.sum?.mod_lb_hrs || 0);
form.setFieldsValue({
actual_in: data.jobs_by_pk.actual_in ? data.jobs_by_pk.actual_in : dayjs(),
scheduled_completion: data.jobs_by_pk.scheduled_completion,
actual_completion: data.jobs_by_pk.actual_completion,
scheduled_delivery: data.jobs_by_pk.scheduled_delivery,
actual_delivery: data.jobs_by_pk.actual_delivery
scheduled_completion: data.jobs_by_pk.scheduled_completion
? data.jobs_by_pk.scheduled_completion
: totalHours && bodyshop.ss_configuration.nobusinessdays
? dayjs().businessDaysAdd(totalHours / (bodyshop.target_touchtime || 1), "day")
: dayjs().add(totalHours / (bodyshop.target_touchtime || 1), "day"),
scheduled_delivery: data.jobs_by_pk.scheduled_delivery
});
}
},

View File

@@ -1,5 +1,4 @@
import { Col, Form, Input, InputNumber, Row, Select, Space, Switch } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -188,6 +187,12 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
<Form.Item label={t("jobs.fields.tlos_ind")} name="tlos_ind" valuePropName="checked">
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.hit_and_run")} name="hit_and_run" valuePropName="checked">
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.acv_amount")} name="acv_amount">
<CurrencyInput disabled={jobRO} min={0} />
</Form.Item>
</FormRow>
</Col>
<Col {...lossColDamage}>

View File

@@ -44,18 +44,16 @@ export function JobsDetailHeaderActionsToggleProduction({
variables: { id: job.id },
onCompleted: (data) => {
if (data?.jobs_by_pk) {
const totalHours =
(data.jobs_by_pk.labhrs?.aggregate?.sum?.mod_lb_hrs || 0) +
(data.jobs_by_pk.larhrs?.aggregate?.sum?.mod_lb_hrs || 0);
form.setFieldsValue({
actual_in: data.jobs_by_pk.actual_in ? data.jobs_by_pk.actual_in : dayjs(),
scheduled_completion: data.jobs_by_pk.scheduled_completion
? data.jobs_by_pk.scheduled_completion
: data.jobs_by_pk.labhrs &&
data.jobs_by_pk.larhrs &&
dayjs().businessDaysAdd(
(data.jobs_by_pk.labhrs.aggregate.sum.mod_lb_hrs ||
0 + data.jobs_by_pk.larhrs.aggregate.sum.mod_lb_hrs ||
0) / bodyshop.target_touchtime,
"day"
),
: totalHours && bodyshop.ss_configuration.nobusinessdays
? dayjs().businessDaysAdd(totalHours / (bodyshop.target_touchtime || 1), "day")
: dayjs().add(totalHours / (bodyshop.target_touchtime || 1), "day"),
actual_completion: data.jobs_by_pk.actual_completion,
scheduled_delivery: data.jobs_by_pk.scheduled_delivery,
actual_delivery: data.jobs_by_pk.actual_delivery

View File

@@ -10,6 +10,7 @@ import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import dayjs from "../../utils/day";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import DataLabel from "../data-label/data-label.component";
@@ -21,7 +22,6 @@ import ProductionListColumnComment from "../production-list-columns/production-l
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
import "./jobs-detail-header.styles.scss";
import dayjs from "../../utils/day";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
@@ -149,6 +149,14 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
</Space>
</Tag>
)}
{job.hit_and_run && (
<Tag color="green">
<Space>
<WarningFilled />
<span>{t("jobs.fields.hit_and_run")}</span>
</Space>
</Tag>
)}
</Space>
</div>
</Card>

View File

@@ -1,6 +1,6 @@
import { Button, Col, Form, Input, Row, Select, Space, Switch, Typography } from "antd";
import axios from "axios";
import React, { useState } from "react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -8,16 +8,16 @@ import { calculateScheduleLoad } from "../../redux/application/application.actio
import { selectBodyshop } from "../../redux/user/user.selectors";
import { DateFormatter } from "../../utils/DateFormatter";
import dayjs from "../../utils/day";
import BlurWrapper from "../feature-wrapper/blur-wrapper.component";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
import EmailInput from "../form-items-formatted/email-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component";
import ScheduleDayViewContainer from "../schedule-day-view/schedule-day-view.container";
import ScheduleExistingAppointmentsList from "../schedule-existing-appointments-list/schedule-existing-appointments-list.component";
import "./schedule-job-modal.scss";
import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component";
import BlurWrapper from "../feature-wrapper/blur-wrapper.component";
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
import "./schedule-job-modal.scss";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
@@ -60,10 +60,12 @@ export function ScheduleJobModalComponent({
const totalHours =
lbrHrsData.jobs_by_pk.labhrs.aggregate.sum.mod_lb_hrs + lbrHrsData.jobs_by_pk.larhrs.aggregate.sum.mod_lb_hrs;
if (values.start && !values.scheduled_completion)
form.setFieldsValue({
scheduled_completion: dayjs(values.start).businessDaysAdd(totalHours / bodyshop.target_touchtime, "day")
});
if (values.start && !values.scheduled_completion) {
const addDays = bodyshop.ss_configuration.nobusinessdays
? dayjs(values.start).add(totalHours / (bodyshop.target_touchtime || 1), "day")
: dayjs(values.start).businessDaysAdd(totalHours / (bodyshop.target_touchtime || 1), "day");
form.setFieldsValue({ scheduled_completion: addDays });
}
}
};

View File

@@ -1,16 +1,15 @@
import { DeleteFilled } from "@ant-design/icons";
import { Button, Divider, Form, Input, InputNumber, Select, Space, Switch, TimePicker } from "antd";
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 { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import ColorpickerFormItemComponent from "../form-items-formatted/colorpicker-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 { ColorPicker } from "./shop-info.rostatus.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
@@ -78,6 +77,13 @@ export function ShopInfoSchedulingComponent({ form, bodyshop }) {
>
<InputNumber min={0} />
</Form.Item>
<Form.Item
name={["ss_configuration", "nobusinessdays"]}
label={t("bodyshop.fields.ss_configuration.nobusinessdays")}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
name={["md_lost_sale_reasons"]}
label={t("bodyshop.fields.md_lost_sale_reasons")}

View File

@@ -1,7 +1,6 @@
import { useLazyQuery } from "@apollo/client";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Form, Input, InputNumber, Select, Switch } from "antd";
import React from "react";
import { Card, Form, Input, InputNumber, Select, Space, Switch } from "antd";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -19,6 +18,7 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
import TimeTicketList from "../time-ticket-list/time-ticket-list.component";
import JobEmployeeAssignmentsContainer from "./../job-employee-assignments/job-employee-assignments.container";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -319,10 +319,15 @@ export function TimeTicketModalComponent({
}
export function LaborAllocationContainer({ jobid, loading, lineTicketData, hideTimeTickets = false }) {
const { t } = useTranslation();
if (loading) return <LoadingSkeleton />;
if (!lineTicketData) return null;
if (!jobid) return null;
return (
<div>
<Space direction="vertical" style={{ width: "100%" }}>
<Card style={{ height: "100%" }} title={t("jobs.labels.employeeassignments")}>
<JobEmployeeAssignmentsContainer job={lineTicketData.jobs_by_pk} />
</Card>
<LaborAllocationsTable
jobId={jobid}
joblines={lineTicketData.joblines}
@@ -332,6 +337,6 @@ export function LaborAllocationContainer({ jobid, loading, lineTicketData, hideT
{!hideTimeTickets && (
<TimeTicketList loading={loading} timetickets={jobid ? lineTicketData.timetickets : []} techConsole />
)}
</div>
</Space>
);
}

View File

@@ -2,10 +2,11 @@ import { PageHeader } from "@ant-design/pro-layout";
import { useMutation, useQuery } from "@apollo/client";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Button, Form, Modal, Space } from "antd";
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import { QUERY_ACTIVE_EMPLOYEES } from "../../graphql/employees.queries";
import { INSERT_NEW_TIME_TICKET, UPDATE_TIME_TICKET } from "../../graphql/timetickets.queries";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
@@ -14,7 +15,6 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import dayjs from "../../utils/day";
import TimeTicketsCommitToggleComponent from "../time-tickets-commit-toggle/time-tickets-commit-toggle.component";
import TimeTicketModalComponent from "./time-ticket-modal.component";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
const mapStateToProps = createStructuredSelector({
timeTicketModal: selectTimeTicket,
@@ -81,7 +81,7 @@ export function TimeTicketModalContainer({ timeTicketModal, toggleModalVisible,
}
};
const handleMutationSuccess = (response) => {
const handleMutationSuccess = () => {
notification["success"]({
message: t("timetickets.successes.created")
});
@@ -123,7 +123,7 @@ export function TimeTicketModalContainer({ timeTicketModal, toggleModalVisible,
if (timeTicketModal.open) form.resetFields();
}, [timeTicketModal.open, form]);
const handleFieldsChange = (changedFields, allFields) => {
const handleFieldsChange = (changedFields) => {
if (!!changedFields.employeeid && !!EmployeeAutoCompleteData) {
const emps = EmployeeAutoCompleteData.employees.filter((e) => e.id === changedFields.employeeid);
form.setFieldsValue({
@@ -182,6 +182,7 @@ export function TimeTicketModalContainer({ timeTicketModal, toggleModalVisible,
</Space>
}
destroyOnClose
id="time-ticket-modal"
>
<Form
onFinish={handleFinish}

View File

@@ -35,6 +35,30 @@ export const GET_LINE_TICKET_BY_PK = gql`
lbr_adjustments
converted
status
employee_body
employee_body_rel {
id
first_name
last_name
}
employee_csr
employee_csr_rel {
id
first_name
last_name
}
employee_prep
employee_prep_rel {
id
first_name
last_name
}
employee_refinish
employee_refinish_rel {
id
first_name
last_name
}
}
joblines(where: { jobid: { _eq: $id }, removed: { _eq: false } }) {
id

View File

@@ -423,6 +423,7 @@ export const GET_JOB_BY_PK = gql`
actual_completion
actual_delivery
actual_in
acv_amount
adjustment_bottom_line
alt_transport
area_of_damage
@@ -511,6 +512,7 @@ export const GET_JOB_BY_PK = gql`
est_ph1
flat_rate_ats
federal_tax_rate
hit_and_run
id
inproduction
ins_addr1

View File

@@ -601,7 +601,8 @@
"templates": "Templates"
},
"ss_configuration": {
"dailyhrslimit": "Daily Incoming Hours Limit"
"dailyhrslimit": "Daily Incoming Hours Limit",
"nobusinessdays": "Include Weekends"
},
"ssbuckets": {
"color": "Job Color",
@@ -1635,6 +1636,7 @@
"actual_completion": "Actual Completion",
"actual_delivery": "Actual Delivery",
"actual_in": "Actual In",
"acv_amount": "ACV Amount",
"adjustment_bottom_line": "Adjustments",
"adjustmenthours": "Adjustment Hours",
"alt_transport": "Alt. Trans.",
@@ -1763,6 +1765,7 @@
"flat_rate_ats": "Flat Rate ATS?",
"federal_tax_payable": "Federal Tax Payable",
"federal_tax_rate": "Federal Tax Rate",
"hit_and_run": "Hit and Run",
"ins_addr1": "Insurance Co. Address",
"ins_city": "Insurance Co. City",
"ins_co_id": "Insurance Co. ID",

View File

@@ -601,7 +601,8 @@
"templates": ""
},
"ss_configuration": {
"dailyhrslimit": ""
"dailyhrslimit": "",
"nobusinessdays": ""
},
"ssbuckets": {
"color": "",
@@ -1635,6 +1636,7 @@
"actual_completion": "Realización real",
"actual_delivery": "Entrega real",
"actual_in": "Real en",
"acv_amount": "",
"adjustment_bottom_line": "Ajustes",
"adjustmenthours": "",
"alt_transport": "",
@@ -1763,6 +1765,7 @@
"flat_rate_ats": "",
"federal_tax_payable": "Impuesto federal por pagar",
"federal_tax_rate": "",
"hit_and_run": "",
"ins_addr1": "Dirección de Insurance Co.",
"ins_city": "Ciudad de seguros",
"ins_co_id": "ID de la compañía de seguros",

View File

@@ -601,7 +601,8 @@
"templates": ""
},
"ss_configuration": {
"dailyhrslimit": ""
"dailyhrslimit": "",
"nobusinessdays": ""
},
"ssbuckets": {
"color": "",
@@ -1636,6 +1637,7 @@
"actual_completion": "Achèvement réel",
"actual_delivery": "Livraison réelle",
"actual_in": "En réel",
"acv_amount": "",
"adjustment_bottom_line": "Ajustements",
"adjustmenthours": "",
"alt_transport": "",
@@ -1764,6 +1766,7 @@
"flat_rate_ats": "",
"federal_tax_payable": "Impôt fédéral à payer",
"federal_tax_rate": "",
"hit_and_run": "",
"ins_addr1": "Adresse Insurance Co.",
"ins_city": "Insurance City",
"ins_co_id": "ID de la compagnie d'assurance",

View File

@@ -3595,6 +3595,7 @@
- actual_completion
- actual_delivery
- actual_in
- acv_amount
- adj_g_disc
- adj_strdis
- adj_towdis
@@ -3700,6 +3701,7 @@
- federal_tax_rate
- flat_rate_ats
- g_bett_amt
- hit_and_run
- id
- inproduction
- ins_addr1
@@ -3866,6 +3868,7 @@
- actual_completion
- actual_delivery
- actual_in
- acv_amount
- adj_g_disc
- adj_strdis
- adj_towdis
@@ -3972,6 +3975,7 @@
- federal_tax_rate
- flat_rate_ats
- g_bett_amt
- hit_and_run
- id
- inproduction
- ins_addr1
@@ -4150,6 +4154,7 @@
- actual_completion
- actual_delivery
- actual_in
- acv_amount
- adj_g_disc
- adj_strdis
- adj_towdis
@@ -4256,6 +4261,7 @@
- federal_tax_rate
- flat_rate_ats
- g_bett_amt
- hit_and_run
- id
- inproduction
- ins_addr1

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."jobs" add column "hit_and_run" boolean
-- null default 'false';

View File

@@ -0,0 +1,2 @@
alter table "public"."jobs" add column "hit_and_run" boolean
null default 'false';

View File

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

View File

@@ -0,0 +1,2 @@
alter table "public"."jobs" add column "acv_amount" numeric
null;