Compare commits

...

6 Commits

Author SHA1 Message Date
Allan Carr
521a7084b7 IO-3262 Correction for v_year in Project Mexico
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2025-12-05 09:48:32 -08:00
Allan Carr
a3c71fdfc0 IO-3262 Add email address to Usage Report
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2025-12-03 10:28:20 -08:00
Allan Carr
90edf94fee IO-3262 Tech Console Job Clock Out
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2025-12-01 12:50:11 -08:00
Dave Richer
123066f1cd Merged in release/2025-11-21 (pull request #2671)
Release/2025 11 21 into Master-AIO - IO-3435 IO-3445 IO-3440 IO-3446
2025-11-21 19:19:15 +00:00
Allan Carr
a153cca3c0 Merged in feature/IO-3440-Payment-By-Date-Excel (pull request #2669)
IO-3440 Payment By Date - Excel
2025-11-21 00:26:54 +00:00
Allan Carr
35c7c32c8e IO-3440 Payment By Date - Excel
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2025-11-20 16:29:05 -08:00
6 changed files with 122 additions and 131 deletions

View File

@@ -16,7 +16,7 @@ const mapDispatchToProps = () => ({
export function TechHeader({ technician }) { export function TechHeader({ technician }) {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<Header style={{ textAlign: "center" }}> <Header style={{ textAlign: "center", height: "auto", overflow: "visible" }}>
<Typography.Title style={{ color: "#fff" }}> <Typography.Title style={{ color: "#fff" }}>
{technician {technician
? t("tech.labels.loggedin", { ? t("tech.labels.loggedin", {

View File

@@ -1,5 +1,5 @@
import { useMutation, useQuery } from "@apollo/client"; import { useMutation, useQuery } from "@apollo/client";
import { Button, Card, Col, Form, InputNumber, Popover, Row, Select } from "antd"; import { Button, Card, Form, InputNumber, Popover, Select, Space } from "antd";
import axios from "axios"; import axios from "axios";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -124,103 +124,12 @@ export function TechClockOffButton({
cost_center: isShiftTicket ? "timetickets.labels.shift" : technician ? technician.cost_center : null cost_center: isShiftTicket ? "timetickets.labels.shift" : technician ? technician.cost_center : null
}} }}
> >
<Row gutter={[16, 16]}> <Space direction="vertical">
<Col span={!isShiftTicket ? 8 : 24}> {!isShiftTicket ? (
{!isShiftTicket ? ( <div>
<div>
<Form.Item
label={t("timetickets.fields.actualhrs")}
name="actualhrs"
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={0} precision={1} />
</Form.Item>
<Form.Item
label={t("timetickets.fields.productivehrs")}
name="productivehrs"
rules={[
{
required: true
//message: t("general.validation.required"),
},
({ getFieldValue }) => ({
validator(rule, value) {
if (!bodyshop.tt_enforce_hours_for_tech_console) {
return Promise.resolve();
}
if (!value || getFieldValue("cost_center") === null || !lineTicketData)
return Promise.resolve();
//Check the cost center,
const totals = CalculateAllocationsTotals(
bodyshop,
lineTicketData.joblines,
lineTicketData.timetickets,
lineTicketData.jobs_by_pk.lbr_adjustments
);
const fieldTypeToCheck =
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber ? "mod_lbr_ty" : "cost_center";
const costCenterDiff =
Math.round(
totals.find((total) => total[fieldTypeToCheck] === getFieldValue("cost_center"))
?.difference * 10
) / 10;
if (value > costCenterDiff)
return Promise.reject(t("timetickets.validation.hoursenteredmorethanavailable"));
else {
return Promise.resolve();
}
}
})
]}
>
<InputNumber min={0} precision={1} />
</Form.Item>
</div>
) : null}
<Form.Item
name="cost_center"
label={t("timetickets.fields.cost_center")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Select disabled={isShiftTicket}>
{isShiftTicket ? (
<Select.Option value="timetickets.labels.shift">{t("timetickets.labels.shift")}</Select.Option>
) : (
emps &&
emps.rates.map((item) => (
<Select.Option key={item.cost_center}>
{item.cost_center === "timetickets.labels.shift"
? t(item.cost_center)
: bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
? t(`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`)
: item.cost_center}
</Select.Option>
))
)}
</Select>
</Form.Item>
{isShiftTicket ? (
<div></div>
) : (
<Form.Item <Form.Item
name="status" label={t("timetickets.fields.actualhrs")}
label={t("jobs.fields.status")} name="actualhrs"
initialValue={lineTicketData && lineTicketData.jobs_by_pk.status}
rules={[ rules={[
{ {
required: true required: true
@@ -228,35 +137,117 @@ export function TechClockOffButton({
} }
]} ]}
> >
<Select> <InputNumber min={0} precision={1} />
{bodyshop.md_ro_statuses.production_statuses.map((item) => (
<Select.Option key={item}></Select.Option>
))}
</Select>
</Form.Item> </Form.Item>
)} <Form.Item
<Button type="primary" htmlType="submit" loading={loading}> label={t("timetickets.fields.productivehrs")}
{t("general.actions.save")} name="productivehrs"
</Button> rules={[
<TechJobClockoutDelete completedCallback={completedCallback} timeTicketId={timeTicketId} /> {
</Col> required: true
{!isShiftTicket && ( //message: t("general.validation.required"),
<Col span={16}> },
<LaborAllocationContainer ({ getFieldValue }) => ({
jobid={jobId || null} validator(rule, value) {
loading={queryLoading} if (!bodyshop.tt_enforce_hours_for_tech_console) {
lineTicketData={lineTicketData} return Promise.resolve();
/> }
</Col> if (!value || getFieldValue("cost_center") === null || !lineTicketData)
return Promise.resolve();
//Check the cost center,
const totals = CalculateAllocationsTotals(
bodyshop,
lineTicketData.joblines,
lineTicketData.timetickets,
lineTicketData.jobs_by_pk.lbr_adjustments
);
const fieldTypeToCheck =
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber ? "mod_lbr_ty" : "cost_center";
const costCenterDiff =
Math.round(
totals.find((total) => total[fieldTypeToCheck] === getFieldValue("cost_center"))
?.difference * 10
) / 10;
if (value > costCenterDiff)
return Promise.reject(t("timetickets.validation.hoursenteredmorethanavailable"));
else {
return Promise.resolve();
}
}
})
]}
>
<InputNumber min={0} precision={1} />
</Form.Item>
</div>
) : null}
<Form.Item
name="cost_center"
label={t("timetickets.fields.cost_center")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Select disabled={isShiftTicket}>
{isShiftTicket ? (
<Select.Option value="timetickets.labels.shift">{t("timetickets.labels.shift")}</Select.Option>
) : (
emps &&
emps.rates.map((item) => (
<Select.Option key={item.cost_center}>
{item.cost_center === "timetickets.labels.shift"
? t(item.cost_center)
: bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
? t(`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`)
: item.cost_center}
</Select.Option>
))
)}
</Select>
</Form.Item>
{isShiftTicket ? (
<div></div>
) : (
<Form.Item
name="status"
label={t("jobs.fields.status")}
initialValue={lineTicketData && lineTicketData.jobs_by_pk.status}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Select>
{bodyshop.md_ro_statuses.production_statuses.map((item) => (
<Select.Option key={item}></Select.Option>
))}
</Select>
</Form.Item>
)} )}
</Row> <Button type="primary" htmlType="submit" loading={loading}>
{t("general.actions.save")}
</Button>
<TechJobClockoutDelete completedCallback={completedCallback} timeTicketId={timeTicketId} />
{!isShiftTicket && (
<LaborAllocationContainer jobid={jobId || null} loading={queryLoading} lineTicketData={lineTicketData} />
)}
</Space>
</Form> </Form>
</div> </div>
</Card> </Card>
); );
return ( return (
<Popover content={overlay} trigger="click"> <Popover
content={<div style={{ maxHeight: "75vh", overflowY: "auto" }}>{overlay}</div>}
trigger="click"
getPopupContainer={() => document.querySelector('#time-ticket-modal')}
>
<Button loading={loading} {...otherBtnProps}> <Button loading={loading} {...otherBtnProps}>
{t("timetickets.actions.clockout")} {t("timetickets.actions.clockout")}
</Button> </Button>

View File

@@ -1221,7 +1221,7 @@ export const TemplateList = (type, context) => {
payments_by_date_excel: { payments_by_date_excel: {
title: i18n.t("reportcenter.templates.payments_by_date_excel"), title: i18n.t("reportcenter.templates.payments_by_date_excel"),
subject: i18n.t("reportcenter.templates.payments_by_date_excel"), subject: i18n.t("reportcenter.templates.payments_by_date_excel"),
key: "payments_by_date", key: "payments_by_date_excel",
reporttype: "excel", reporttype: "excel",
disabled: false, disabled: false,
rangeFilter: { rangeFilter: {

View File

@@ -234,11 +234,10 @@ const CreateRepairOrderTag = (job, errorCallback) => {
const ret = { const ret = {
ro_number: crypto.createHash("md5").update(job.id, "utf8").digest("hex"), ro_number: crypto.createHash("md5").update(job.id, "utf8").digest("hex"),
v_vin: job.v_vin || "", v_vin: job.v_vin || "",
v_year: job.v_model_yr v_year: (() => {
? parseInt(job.v_model_yr.match(/\d/g)) const y = parseInt(job.v_model_yr);
? parseInt(job.v_model_yr.match(/\d/g).join(""), 10) return isNaN(y) ? null : y < 100 ? y + (y >= (new Date().getFullYear() + 1) % 100 ? 1900 : 2000) : y;
: "" })(),
: "",
v_make: job.v_makedesc || "", v_make: job.v_makedesc || "",
v_model: job.v_model || "", v_model: job.v_model || "",

View File

@@ -286,11 +286,10 @@ const CreateRepairOrderTag = (job, errorCallback) => {
const ret = { const ret = {
ro_number: crypto.createHash("md5").update(job.ro_number, "utf8").digest("hex"), ro_number: crypto.createHash("md5").update(job.ro_number, "utf8").digest("hex"),
v_vin: job.v_vin || "", v_vin: job.v_vin || "",
v_year: job.v_model_yr v_year: (() => {
? parseInt(job.v_model_yr.match(/\d/g)) const y = parseInt(job.v_model_yr);
? parseInt(job.v_model_yr.match(/\d/g).join(""), 10) return isNaN(y) ? null : y < 100 ? y + (y >= (new Date().getFullYear() + 1) % 100 ? 1900 : 2000) : y;
: "" })(),
: "",
v_make: job.v_make_desc || "", v_make: job.v_make_desc || "",
v_model: job.v_model_desc || "", v_model: job.v_model_desc || "",

View File

@@ -55,7 +55,9 @@ exports.default = async (req, res) => {
"patrick.fic@convenient-brands.com", "patrick.fic@convenient-brands.com",
"bradley.rhoades@convenient-brands.com", "bradley.rhoades@convenient-brands.com",
"jrome@rometech.com", "jrome@rometech.com",
"ivana@imexsystems.ca" "ivana@imexsystems.ca",
"support@imexsystems.ca",
"sarah@rometech.com"
], ],
subject: `RO Usage Report - ${moment().format("MM/DD/YYYY")}`, subject: `RO Usage Report - ${moment().format("MM/DD/YYYY")}`,
text: ` text: `