Compare commits

..

2 Commits

Author SHA1 Message Date
Allan Carr
cca23a5b11 IO-3509 Correction for date_estimate to handle tz correctly
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-01-23 23:07:11 -08:00
Allan Carr
2acddcb9ac IO-3509 Duplicate Job Open Estimate Date
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-01-23 22:51:53 -08:00
33 changed files with 69 additions and 107 deletions

View File

@@ -169,13 +169,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, bills, ref
refetch={refetch}
/>
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && <QboAuthorizeComponent />}
<Input
value={state.search}
onChange={handleSearch}
placeholder={t("general.labels.search")}
allowClear
enterButton
/>
<Input value={state.search} onChange={handleSearch} placeholder={t("general.labels.search")} allowClear />
</Space>
}
>

View File

@@ -182,13 +182,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, payments,
refetch={refetch}
/>
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && <QboAuthorizeComponent />}
<Input
value={state.search}
onChange={handleSearch}
placeholder={t("general.labels.search")}
allowClear
enterButton
/>
<Input value={state.search} onChange={handleSearch} placeholder={t("general.labels.search")} allowClear />
</Space>
}
>

View File

@@ -204,7 +204,6 @@ export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, r
onChange={handleSearch}
placeholder={t("general.labels.search")}
allowClear
enterButton
/>
</Space>
}

View File

@@ -232,7 +232,6 @@ export function BillsListTableComponent({
e.preventDefault();
setSearchText(e.target.value);
}}
enterButton
/>
</Space>
}

View File

@@ -99,7 +99,6 @@ export default function ContractsCarsComponent({ loading, data, selectedCarId, h
placeholder={t("general.labels.search")}
value={state.search}
onChange={(e) => setState({ ...state, search: e.target.value })}
enterButton
/>
}
>

View File

@@ -123,7 +123,6 @@ export default function ContractsJobsComponent({ loading, data, selectedJob, han
placeholder={t("general.labels.search")}
value={state.search}
onChange={(e) => setState({ ...state, search: e.target.value })}
enterButton
/>
}
>

View File

@@ -164,7 +164,6 @@ export function ContractsList({ bodyshop, loading, contracts, refetch, total, se
const updatedSearch = { ...search, search: value };
history({ search: queryString.stringify(updatedSearch) });
}}
enterButton
/>
</Space>
}

View File

@@ -96,7 +96,6 @@ export default function DashboardMonthlyJobCosting({ data, ...cardProps }) {
e.preventDefault();
setSearchText(e.target.value);
}}
enterButton
/>
</Space>
}

View File

@@ -62,7 +62,6 @@ export function DmsCdkVehicles({ form, job }) {
<Input.Search
onSearch={(val) => callSearch({ variables: { search: val } })}
placeholder={t("general.labels.search")}
enterButton
/>
)}
columns={columns}

View File

@@ -76,7 +76,6 @@ export default function JobCostingPartsTable({ data, summaryData }) {
e.preventDefault();
setSearchText(e.target.value);
}}
enterButton
/>
</Space>
);

View File

@@ -50,7 +50,8 @@ export function JobCreateIOU({ bodyshop, currentUser, job, selectedJobLines, tec
config: {
status: bodyshop.md_ro_statuses.default_open,
bodyshopid: bodyshop.id,
useremail: currentUser.email
useremail: currentUser.email,
timezone: bodyshop.timezone
},
currentUser
});

View File

@@ -682,7 +682,6 @@ export function JobLinesComponent({
e.preventDefault();
setSearchText(e.target.value);
}}
enterButton
/>
</Space>
}

View File

@@ -136,7 +136,6 @@ export function JobsAvailableScan({ partnerVersion, refetch }) {
onChange={(e) => {
setSearchText(e.currentTarget.value);
}}
enterButton
/>
</Space>
}

View File

@@ -196,16 +196,13 @@ export function JobsAvailableComponent({ bodyshop, loading, data, refetch, addJo
>
{t("general.actions.deleteall")}
</Button>
<Input.Search
placeholder={t("general.labels.search")}
onChange={(e) => {
setSearchText(e.currentTarget.value);
}}
enterButton
/>
<Link to="/manage/jobs/new">
<Button>{t("jobs.actions.manualnew")}</Button>
</Link>
</Space>
}
>

View File

@@ -24,7 +24,7 @@ export default function JobsCreateVehicleInfoPredefined({ disabled, form }) {
<div>
<Table
size="small"
title={() => <Input.Search onSearch={(value) => setSearch(value)} enterButton/>}
title={() => <Input.Search onSearch={(value) => setSearch(value)} />}
dataSource={filteredPredefinedVehicles}
columns={[
{

View File

@@ -264,7 +264,7 @@ export function JobsDetailHeaderActions({
DuplicateJob({
apolloClient: client,
jobId: job.id,
config: { defaultOpenStatus: bodyshop.md_ro_statuses.default_imported },
config: { defaultOpenStatus: bodyshop.md_ro_statuses.default_imported, timezone: bodyshop.timezone },
completionCallback: (newJobId) => {
history(`/manage/jobs/${newJobId}`);
notification.success({
@@ -279,7 +279,7 @@ export function JobsDetailHeaderActions({
DuplicateJob({
apolloClient: client,
jobId: job.id,
config: { defaultOpenStatus: bodyshop.md_ro_statuses.default_imported },
config: { defaultOpenStatus: bodyshop.md_ro_statuses.default_imported, timezone: bodyshop.timezone },
completionCallback: (newJobId) => {
history(`/manage/jobs/${newJobId}`);
notification.success({

View File

@@ -15,7 +15,7 @@ export default async function DuplicateJob({
}) {
logImEXEvent("job_duplicate");
const { defaultOpenStatus } = config;
const { defaultOpenStatus, timezone } = config;
//get a list of all fields on the job
const res = await apolloClient.query({
query: QUERY_JOB_FOR_DUPE,
@@ -31,9 +31,12 @@ export default async function DuplicateJob({
delete existingJob.updatedat;
delete existingJob.cieca_stl;
delete existingJob.cieca_ttl;
!keepJobLines && delete existingJob.clm_total;
const newJob = {
...existingJob,
date_estimated: dayjs().tz(timezone, false).format("YYYY-MM-DD"),
date_open: dayjs(),
status: defaultOpenStatus
};
@@ -70,7 +73,7 @@ export default async function DuplicateJob({
export async function CreateIouForJob({ apolloClient, jobId, config, jobLinesToKeep, currentUser }) {
logImEXEvent("job_create_iou");
const { status } = config;
const { status, timezone } = config;
//get a list of all fields on the job
const res = await apolloClient.query({
query: QUERY_JOB_FOR_DUPE,
@@ -88,10 +91,10 @@ export async function CreateIouForJob({ apolloClient, jobId, config, jobLinesToK
const newJob = {
...existingJob,
converted: true,
status: status,
iouparent: jobId,
date_estimated: dayjs().tz(timezone, false).format("YYYY-MM-DD"),
date_open: dayjs(),
audit_trails: {
data: [

View File

@@ -48,7 +48,6 @@ export default function OwnerFindModalContainer({
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
onSearch={(val) => callSearchowners({ variables: { search: val.trim() } })}
enterButton
/>
<OwnerFindModalComponent
selectedOwner={selectedOwner}

View File

@@ -101,7 +101,6 @@ export function PartDispatchTableComponent({ bodyshop, job, billsQuery }) {
e.preventDefault();
setSearchText(e.target.value);
}}
enterButton
/>
</Space>
}

View File

@@ -295,7 +295,6 @@ export function PartsOrderListTableComponent({
e.preventDefault();
setSearchText(e.target.value);
}}
enterButton
/>
</Space>
}

View File

@@ -269,7 +269,6 @@ export function PartsQueueListComponent({ bodyshop }) {
return (
<Card
title={t("titles.bc.parts-queue")}
extra={
<Space wrap>
<Button onClick={() => refetch()}>

View File

@@ -129,7 +129,6 @@ function PhoneNumberConsentList({ bodyshop }) {
placeholder={t("general.labels.search")}
onSearch={(value) => setSearch(value)}
style={{ marginBottom: 16 }}
enterButton
/>
<Table

View File

@@ -89,7 +89,7 @@ export function PrintCenterJobsComponent({ printCenterModal, bodyshop }) {
<Space wrap>
<PrintCenterJobsLabels jobId={jobId} />
<Jobd3RdPartyModal jobId={jobId} job={job} />
<Input.Search onChange={(e) => setSearch(e.target.value)} value={search} enterButton />
<Input.Search onChange={(e) => setSearch(e.target.value)} value={search} />
</Space>
}
>

View File

@@ -45,7 +45,6 @@ export function ProductionBoardFilters({ bodyshop, filter, setFilter, loading })
setFilter({ ...filter, search: e.target.value });
logImEXEvent("visual_board_filter_search", { search: e.target.value });
}}
enterButton
/>
<EmployeeSearchSelectComponent
style={{ minWidth: "20rem" }}

View File

@@ -44,7 +44,6 @@ export default function ProfileShopsComponent({ loading, data, updateActiveShop
onChange={(e) => setSearch(e.target.value)}
allowClear
placeholder={t("general.labels.search")}
enterButton
/>
}
>

View File

@@ -145,7 +145,7 @@ export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) {
return (
<div className="report-center-modal">
<Form onFinish={handleFinish} autoComplete={"off"} layout="vertical" form={form}>
<Input.Search onChange={(e) => setSearch(e.target.value)} value={search} enterButton />
<Input.Search onChange={(e) => setSearch(e.target.value)} value={search} />
<Form.Item name="defaultSorters" hidden>
<Input type="hidden" />
</Form.Item>

View File

@@ -3,7 +3,8 @@ import { Button, Form, Input, InputNumber, Select, Space, Switch } from "antd";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import FeatureWrapper from "../feature-wrapper/feature-wrapper.component";
import { selectBodyshop } from "../../redux/user/user.selectors";
import FeatureWrapper, { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import FormItemEmail from "../form-items-formatted/email-form-item.component";
import PhoneFormItem, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
@@ -11,13 +12,15 @@ import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.c
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
const timeZonesList = Intl.supportedValuesOf("timeZone");
const mapStateToProps = createStructuredSelector({});
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
const mapDispatchToProps = () => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoGeneral);
export function ShopInfoGeneral({ form }) {
export function ShopInfoGeneral({ form, bodyshop }) {
const { t } = useTranslation();
return (
@@ -375,6 +378,34 @@ export function ShopInfoGeneral({ form }) {
>
<Select mode="tags" />
</Form.Item>,
...(HasFeatureAccess({ featureName: "timetickets", bodyshop })
? [
<Form.Item
key="tt_allow_post_to_invoiced"
name={["tt_allow_post_to_invoiced"]}
label={t("bodyshop.fields.tt_allow_post_to_invoiced")}
valuePropName="checked"
>
<Switch />
</Form.Item>,
<Form.Item
key="tt_enforce_hours_for_tech_console"
name={["tt_enforce_hours_for_tech_console"]}
label={t("bodyshop.fields.tt_enforce_hours_for_tech_console")}
valuePropName="checked"
>
<Switch />
</Form.Item>,
<Form.Item
key="bill_allow_post_to_closed"
name={["bill_allow_post_to_closed"]}
label={t("bodyshop.fields.bill_allow_post_to_closed")}
valuePropName="checked"
>
<Switch />
</Form.Item>
]
: []),
<Form.Item
key="md_ded_notes"
name={["md_ded_notes"]}

View File

@@ -313,38 +313,6 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
>
<Select mode="tags" />
</Form.Item>,
...(HasFeatureAccess({ featureName: "timetickets", bodyshop })
? [
<Form.Item
key="tt_allow_post_to_invoiced"
name={["tt_allow_post_to_invoiced"]}
label={t("bodyshop.fields.tt_allow_post_to_invoiced")}
valuePropName="checked"
>
<Switch />
</Form.Item>,
<Form.Item
key="tt_enforce_hours_for_tech_console"
name={["tt_enforce_hours_for_tech_console"]}
label={t("bodyshop.fields.tt_enforce_hours_for_tech_console")}
valuePropName="checked"
>
<Switch />
</Form.Item>
]
: []),
...(HasFeatureAccess({ featureName: "bills", bodyshop })
? [
<Form.Item
key="bill_allow_post_to_closed"
name={["bill_allow_post_to_closed"]}
label={t("bodyshop.fields.bill_allow_post_to_closed")}
valuePropName="checked"
>
<Switch />
</Form.Item>
]
: []),
...(HasFeatureAccess({ featureName: "export", bodyshop })
? [
...(ClosingPeriod.treatment === "on"

View File

@@ -181,7 +181,6 @@ export function ExportLogsPageComponent() {
searchParams.search = value;
history({ search: queryString.stringify(searchParams) });
}}
enterButton
/>
</Space>
}

View File

@@ -4,6 +4,7 @@ import { PageHeader } from "@ant-design/pro-layout";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import AlertComponent from "../../components/alert/alert.component";
import JobsAvailableTableContainer from "../../components/jobs-available-table/jobs-available-table.container";
@@ -24,26 +25,6 @@ const mapDispatchToProps = (dispatch) => ({
export function JobsAvailablePageContainer({ partnerVersion, setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
const getOS = () => {
const userAgent = navigator.userAgent;
if (userAgent.indexOf("Win") !== -1) return "windows";
if (userAgent.indexOf("Mac") !== -1) return "mac";
if (userAgent.indexOf("Linux") !== -1) return "linux";
return "unknown";
};
const os = getOS();
const downloadUrl = InstanceRenderManager({
imex:
os === "windows"
? "https://imex-partner.s3.ca-central-1.amazonaws.com/imex-partner-x64.exe"
: "https://imex-partner.s3.ca-central-1.amazonaws.com/imex-partner-arm64.dmg",
rome:
os === "windows"
? "https://rome-partner.s3.us-east-2.amazonaws.com/rome-partner-x64.exe"
: "https://rome-partner.s3.us-east-2.amazonaws.com/rome-partner-arm64.dmg"
});
useEffect(() => {
document.title = t("titles.jobsavailable", {
app: InstanceRenderManager({
@@ -58,12 +39,24 @@ export function JobsAvailablePageContainer({ partnerVersion, setBreadcrumbs, set
return (
<RbacWrapper action="jobs:available-list">
<div>
<PageHeader />
<PageHeader
title={t("titles.bc.availablejobs")}
extra={
<Link to="/manage/jobs/new">
<Button>{t("jobs.actions.manualnew")}</Button>
</Link>
}
/>
{!partnerVersion && (
<AlertComponent
type="warning"
action={
<a href={downloadUrl}>
<a
href={InstanceRenderManager({
imex: "https://partner.imex.online/Setup.exe",
rome: "https://partner.romeonline.io/Setup.exe"
})}
>
<Button size="small">{t("general.actions.download")}</Button>
</a>
}

View File

@@ -167,7 +167,6 @@ export function PhonebookPageComponent({ bodyshop, authLevel }) {
searchParams.page = 1;
history({ search: queryString.stringify(searchParams) });
}}
enterButton
/>
</Space>
}

View File

@@ -47,14 +47,14 @@ exports.totalsSsu = async function (req, res) {
throw new Error("Failed to update job totals");
}
res.status(200).send();
res.status(200).json({ success: true });
} catch (error) {
logger.log("job-totals-ssu-USA-error", "error", req?.user?.email, id, {
jobid: id,
error: error.message,
stack: error.stack
});
res.status(503).send();
res.status(503).json({ error: "Failed to calculate totals" });
}
};

View File

@@ -47,14 +47,14 @@ exports.totalsSsu = async function (req, res) {
throw new Error("Failed to update job totals");
}
res.status(200).send();
res.status(200).json({ success: true });
} catch (error) {
logger.log("job-totals-ssu-error", "error", req.user.email, id, {
jobid: id,
error: error.message,
stack: error.stack
});
res.status(503).send();
res.status(503).json({ error: "Failed to calculate totals" });
}
};