Compare commits

..

1 Commits

Author SHA1 Message Date
Dave
9b44dd844f feature/IO-3487-Auto-Add-Profile-Watchers - Fix Auto Add on a profile level 2025-12-22 14:18:13 -05:00
48 changed files with 151 additions and 307 deletions

View File

@@ -19,35 +19,30 @@ const mapDispatchToProps = (dispatch) => ({
openChatByPhone: (phone) => dispatch(openChatByPhone(phone))
});
export function ChatOpenButton({ bodyshop, searchingForConversation, phone, type, jobid, openChatByPhone }) {
export function ChatOpenButton({ bodyshop, searchingForConversation, phone, jobid, openChatByPhone }) {
const { t } = useTranslation();
const { socket } = useSocket();
const notification = useNotification();
if (!phone) return <></>;
if (!bodyshop.messagingservicesid) {
return <PhoneNumberFormatter type={type}>{phone}</PhoneNumberFormatter>;
}
if (!bodyshop.messagingservicesid) return <PhoneNumberFormatter>{phone}</PhoneNumberFormatter>;
return (
<a
href="# "
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
if (searchingForConversation) return; // Prevent finding the same thing twice.
const p = parsePhoneNumber(phone, "CA");
if (searchingForConversation) return; //This is to prevent finding the same thing twice.
if (p && p.isValid()) {
openChatByPhone({ phone_num: p.formatInternational(), jobid, socket });
openChatByPhone({ phone_num: p.formatInternational(), jobid: jobid, socket });
} else {
notification["error"]({ message: t("messaging.error.invalidphone") });
}
}}
>
<PhoneNumberFormatter type={type}>{phone}</PhoneNumberFormatter>
<PhoneNumberFormatter>{phone}</PhoneNumberFormatter>
</a>
);
}

View File

@@ -133,9 +133,6 @@ export function ContractConvertToRo({ bodyshop, currentUser, contract, disabled
ownr_ln: contract.job.owner.ownr_ln,
ownr_co_nm: contract.job.owner.ownr_co_nm,
ownr_ph1: contract.job.owner.ownr_ph1,
ownr_ph2: contract.job.owner.ownr_ph2,
ownr_ph1_ty: contract.job.owner.ownr_ph1_ty,
ownr_ph2_ty: contract.job.owner.ownr_ph2_ty,
ownr_ea: contract.job.owner.ownr_ea,
v_model_desc: contract.job.vehicle && contract.job.vehicle.v_model_desc,
v_model_yr: contract.job.vehicle && contract.job.vehicle.v_model_yr,
@@ -256,6 +253,10 @@ export function ContractConvertToRo({ bodyshop, currentUser, contract, disabled
}
};
if (currentUser?.email) {
newJob.created_user_email = currentUser.email;
}
//Calcualte the new job totals.
const newTotals = (

View File

@@ -256,9 +256,9 @@ export default function DashboardScheduledDeliveryToday({ data, ...cardProps })
responsive: ["md"],
render: (text, record) => (
<Space size="small" wrap>
<ChatOpenButton type={record.ownr_ph1_ty} phone={record.ownr_ph1} jobid={record.jobid} />
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
<ChatOpenButton type={record.ownr_ph2_ty} phone={record.ownr_ph2} jobid={record.jobid} />
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
</Space>
)
},
@@ -397,8 +397,6 @@ export const DashboardScheduledDeliveryTodayGql = `
ownr_ln
ownr_ph1
ownr_ph2
ownr_ph1_ty
ownr_ph2_ty
production_vars
ro_number
scheduled_delivery

View File

@@ -48,8 +48,6 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
ownr_ln: item.job.ownr_ln,
ownr_ph1: item.job.ownr_ph1,
ownr_ph2: item.job.ownr_ph2,
ownr_ph1_ty: item.job.ownr_ph1_ty,
ownr_ph2_ty: item.job.ownr_ph2_ty,
production_vars: item.job.production_vars,
ro_number: item.job.ro_number,
suspended: item.job.suspended,
@@ -266,8 +264,8 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
responsive: ["md"],
render: (text, record) => (
<Space size="small" wrap>
<ChatOpenButton type={record.ownr_ph1_ty} phone={record.ownr_ph1} jobid={record.jobid} />
<ChatOpenButton type={record.ownr_ph2_ty} phone={record.ownr_ph2} jobid={record.jobid} />
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
</Space>
)
},
@@ -402,8 +400,6 @@ export const DashboardScheduledInTodayGql = `
ownr_ln
ownr_ph1
ownr_ph2
ownr_ph1_ty
ownr_ph2_ty
production_vars
ro_number
suspended

View File

@@ -256,9 +256,9 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
responsive: ["md"],
render: (text, record) => (
<Space size="small" wrap>
<ChatOpenButton type={record.ownr_ph1_ty} phone={record.ownr_ph1} jobid={record.jobid} />
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
<ChatOpenButton type={record.ownr_ph2_ty} phone={record.ownr_ph2} jobid={record.jobid} />
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
</Space>
)
},
@@ -397,8 +397,6 @@ export const DashboardScheduledOutTodayGql = `
ownr_ln
ownr_ph1
ownr_ph2
ownr_ph1_ty
ownr_ph2_ty
production_vars
ro_number
scheduled_completion

View File

@@ -73,8 +73,8 @@ export default function GlobalSearchOs() {
<span>
<OwnerNameDisplay ownerObject={owner} />
</span>
<PhoneNumberFormatter type={owner?.ownr_ph1_ty}>{owner.ownr_ph1}</PhoneNumberFormatter>
<PhoneNumberFormatter type={owner?.ownr_ph2_ty}>{owner.ownr_ph2}</PhoneNumberFormatter>
<PhoneNumberFormatter>{owner.ownr_ph1}</PhoneNumberFormatter>
<PhoneNumberFormatter>{owner.ownr_ph2}</PhoneNumberFormatter>
</Space>
</Link>
)

View File

@@ -63,8 +63,8 @@ export default function GlobalSearch() {
<span>
<OwnerNameDisplay ownerObject={owner} />
</span>
<PhoneNumberFormatter type={owner.ownr_ph1_ty}>{owner.ownr_ph1}</PhoneNumberFormatter>
<PhoneNumberFormatter type={owner.ownr_ph2_ty}>{owner.ownr_ph2}</PhoneNumberFormatter>
<PhoneNumberFormatter>{owner.ownr_ph1}</PhoneNumberFormatter>
<PhoneNumberFormatter>{owner.ownr_ph2}</PhoneNumberFormatter>
</Space>
</Link>
)

View File

@@ -220,10 +220,10 @@ export function ScheduleEventComponent({
</DataLabel>
<DataLabel label={t("jobs.fields.ownr_ea")}>{(event.job && event.job.ownr_ea) || ""}</DataLabel>
<DataLabel label={t("jobs.fields.ownr_ph1")}>
<ChatOpenButton phone={event?.job?.ownr_ph1} type={event?.job?.ownr_ph1_ty} jobid={event.job.id} />
<ChatOpenButton phone={event.job && event.job.ownr_ph1} jobid={event.job.id} />
</DataLabel>
<DataLabel label={t("jobs.fields.ownr_ph2")}>
<ChatOpenButton phone={event?.job?.ownr_ph2} type={event?.job?.ownr_ph2_ty} jobid={event.job.id} />
<ChatOpenButton phone={event.job && event.job.ownr_ph2} jobid={event.job.id} />
</DataLabel>
<DataLabel hideIfNull label={t("jobs.fields.loss_of_use")}>
{(event.job && event.job.loss_of_use) || ""}

View File

@@ -43,16 +43,18 @@ export function JobCreateIOU({ bodyshop, currentUser, job, selectedJobLines, tec
const handleCreateIou = async () => {
setLoading(true);
//Query all of the job details to recreate.
const iouId = await CreateIouForJob(
client,
job.id,
{
const iouId = await CreateIouForJob({
apolloClient: client,
jobLinesToKeep: selectedJobLines,
jobId: job.id,
config: {
status: bodyshop.md_ro_statuses.default_open,
bodyshopid: bodyshop.id,
useremail: currentUser.email
},
selectedJobLines
);
currentUser
});
notification.open({
type: "success",
message: t("jobs.successes.ioucreated"),

View File

@@ -154,6 +154,10 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail
: {})
};
if (currentUser?.email) {
newJob.created_user_email = currentUser.email;
}
if (selectedOwner) {
newJob.ownerid = selectedOwner;
delete newJob.owner;

View File

@@ -1,4 +1,4 @@
import { Form, Input, Select } from "antd";
import { Form, Input } from "antd";
import { useContext } from "react";
import { useTranslation } from "react-i18next";
import JobCreateContext from "../../pages/jobs-create/jobs-create.context";
@@ -7,11 +7,6 @@ import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-forma
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
export default function JobsCreateOwnerInfoNewComponent() {
const PHONE_TYPE_OPTIONS = [
{ label: t("owners.labels.home"), value: "Home" },
{ label: t("owners.labels.work"), value: "Work" },
{ label: t("owners.labels.cell"), value: "Cell" }
];
const [state] = useContext(JobCreateContext);
const { t } = useTranslation();
@@ -110,56 +105,26 @@ export default function JobsCreateOwnerInfoNewComponent() {
]}
name={["owner", "data", "ownr_ea"]}
>
<FormItemEmail disabled={!state.owner.new} />
<FormItemEmail
//email={form.getFieldValue("ownr_ea")}
disabled={!state.owner.new}
/>
</Form.Item>
{/* Phone 1 + Type */}
<Form.Item label={t("owners.fields.ownr_ph1")} style={{ marginBottom: 0 }}>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<Form.Item
name={["owner", "data", "ownr_ph1"]}
noStyle
rules={[({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, "owner.data.ownr_ph1")]}
>
<FormItemPhone disabled={!state.owner.new} style={{ flex: 1, minWidth: 150 }} />
</Form.Item>
<Form.Item name={["owner", "data", "ownr_ph1_ty"]} noStyle>
<Select
disabled={!state.owner.new}
allowClear
placeholder="Type"
options={PHONE_TYPE_OPTIONS}
style={{ width: 110 }}
/>
</Form.Item>
</div>
<Form.Item
label={t("owners.fields.ownr_ph1")}
name={["owner", "data", "ownr_ph1"]}
rules={[({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, "owner.data.ownr_ph1")]}
>
<FormItemPhone disabled={!state.owner.new} />
</Form.Item>
{/* Phone 2 + Type */}
<Form.Item label={t("owners.fields.ownr_ph2")} style={{ marginBottom: 0 }}>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<Form.Item
name={["owner", "data", "ownr_ph2"]}
noStyle
rules={[({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, "owner.data.ownr_ph2")]}
>
<FormItemPhone disabled={!state.owner.new} style={{ flex: 1, minWidth: 150 }} />
</Form.Item>
<Form.Item name={["owner", "data", "ownr_ph2_ty"]} noStyle>
<Select
disabled={!state.owner.new}
allowClear
placeholder="Type"
options={PHONE_TYPE_OPTIONS}
style={{ width: 110 }}
/>
</Form.Item>
</div>
<Form.Item
label={t("owners.fields.ownr_ph2")}
name={["owner", "data", "ownr_ph2"]}
rules={[({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, "owner.data.ownr_ph2")]}
>
<FormItemPhone disabled={!state.owner.new} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow>
<Form.Item label={t("owners.fields.preferred_contact")} name={["owner", "data", "preferred_contact"]}>
<Input disabled={!state.owner.new} />

View File

@@ -61,7 +61,7 @@ export default function JobsCreateOwnerInfoSearchComponent({ loading, owners })
title: t("owners.fields.ownr_ph1"),
dataIndex: "ownr_ph1",
key: "ownr_ph1",
render: (text, record) => <PhoneFormatter type={record.ownr_ph1_ty}>{record.ownr_ph1}</PhoneFormatter>,
render: (text, record) => <PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>,
sorter: (a, b) => alphaSort(a.ownr_ph1, b.ownr_ph1),
sortOrder: tableState.sortedInfo.columnKey === "ownr_ph1" && tableState.sortedInfo.order
},
@@ -69,7 +69,7 @@ export default function JobsCreateOwnerInfoSearchComponent({ loading, owners })
title: t("owners.fields.ownr_ph2"),
dataIndex: "ownr_ph2",
key: "ownr_ph2",
render: (text, record) => <PhoneFormatter type={record.ownr_ph2_ty}>{record.ownr_ph2}</PhoneFormatter>,
render: (text, record) => <PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>,
sorter: (a, b) => alphaSort(a.ownr_ph2, b.ownr_ph2),
sortOrder: tableState.sortedInfo.columnKey === "ownr_ph2" && tableState.sortedInfo.order
}

View File

@@ -175,25 +175,33 @@ export function JobsDetailHeaderActions({
};
const handleDuplicate = () =>
DuplicateJob(
client,
job.id,
{ defaultOpenStatus: bodyshop.md_ro_statuses.default_imported },
(newJobId) => {
DuplicateJob({
apolloClient: client,
jobId: job.id,
config: { defaultOpenStatus: bodyshop.md_ro_statuses.default_imported },
completionCallback: (newJobId) => {
history(`/manage/jobs/${newJobId}`);
notification.success({
message: t("jobs.successes.duplicated")
});
},
true
);
keepJobLines: true,
currentUser
});
const handleDuplicateConfirm = () =>
DuplicateJob(client, job.id, { defaultOpenStatus: bodyshop.md_ro_statuses.default_imported }, (newJobId) => {
history(`/manage/jobs/${newJobId}`);
notification.success({
message: t("jobs.successes.duplicated")
});
DuplicateJob({
apolloClient: client,
jobId: job.id,
config: { defaultOpenStatus: bodyshop.md_ro_statuses.default_imported },
completionCallback: (newJobId) => {
history(`/manage/jobs/${newJobId}`);
notification.success({
message: t("jobs.successes.duplicated")
});
},
keepJobLines: false,
currentUser
});
const handleFinish = async (values) => {

View File

@@ -5,7 +5,14 @@ import { INSERT_NEW_JOB, QUERY_JOB_FOR_DUPE } from "../../graphql/jobs.queries";
import dayjs from "../../utils/day";
import i18n from "i18next";
export default async function DuplicateJob(apolloClient, jobId, config, completionCallback, keepJobLines = false) {
export default async function DuplicateJob({
apolloClient,
jobId,
config,
completionCallback,
keepJobLines = false,
currentUser
}) {
logImEXEvent("job_duplicate");
const { defaultOpenStatus } = config;
@@ -19,6 +26,7 @@ export default async function DuplicateJob(apolloClient, jobId, config, completi
const existingJob = _.cloneDeep(jobs_by_pk);
delete existingJob.__typename;
delete existingJob.id;
delete existingJob.created_user_email;
delete existingJob.createdat;
delete existingJob.updatedat;
delete existingJob.cieca_stl;
@@ -29,6 +37,10 @@ export default async function DuplicateJob(apolloClient, jobId, config, completi
status: defaultOpenStatus
};
if (currentUser?.email) {
newJob.created_user_email = currentUser.email;
}
const _tempLines = _.cloneDeep(existingJob.joblines);
_tempLines.forEach((line) => {
delete line.id;
@@ -55,7 +67,7 @@ export default async function DuplicateJob(apolloClient, jobId, config, completi
return;
}
export async function CreateIouForJob(apolloClient, jobId, config, jobLinesToKeep) {
export async function CreateIouForJob({ apolloClient, jobId, config, jobLinesToKeep, currentUser }) {
logImEXEvent("job_create_iou");
const { status } = config;
@@ -109,6 +121,9 @@ export async function CreateIouForJob(apolloClient, jobId, config, jobLinesToKee
delete newJob.joblines;
newJob.joblines = { data: _tempLines };
if (currentUser?.email) {
newJob.created_user_email = currentUser.email;
}
const res2 = await apolloClient.mutate({
mutation: INSERT_NEW_JOB,
variables: { job: [newJob] }

View File

@@ -251,16 +251,16 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, is
<div>
<DataLabel key="2" label={t("jobs.fields.ownr_ph1")}>
{disabled || isPartsEntry ? (
<PhoneNumberFormatter type={job.ownr_ph1_ty}>{job.ownr_ph1}</PhoneNumberFormatter>
<PhoneNumberFormatter>{job.ownr_ph1}</PhoneNumberFormatter>
) : (
<ChatOpenButton type={job.ownr_ph1_ty} phone={job.ownr_ph1} jobid={job.id} />
<ChatOpenButton phone={job.ownr_ph1} jobid={job.id} />
)}
</DataLabel>
<DataLabel key="22" label={t("jobs.fields.ownr_ph2")}>
{disabled || isPartsEntry ? (
<PhoneNumberFormatter type={job.ownr_ph2_ty}>{job.ownr_ph2}</PhoneNumberFormatter>
<PhoneNumberFormatter>{job.ownr_ph2}</PhoneNumberFormatter>
) : (
<ChatOpenButton type={job.ownr_ph2_ty} phone={job.ownr_ph2} jobid={job.id} />
<ChatOpenButton phone={job.ownr_ph2} jobid={job.id} />
)}
</DataLabel>
<DataLabel key="3" label={t("owners.fields.address")}>

View File

@@ -64,11 +64,7 @@ export default function JobsFindModalComponent({
width: "12%",
ellipsis: true,
render: (text, record) => {
return record.ownr_ph1 ? (
<PhoneFormatter type={record.ownr_ph1_ty}>{record.ownr_ph1}</PhoneFormatter>
) : (
t("general.labels.unknown")
);
return record.ownr_ph1 ? <PhoneFormatter>{record.ownr_ph1}</PhoneFormatter> : t("general.labels.unknown");
}
},
{
@@ -78,11 +74,7 @@ export default function JobsFindModalComponent({
width: "12%",
ellipsis: true,
render: (text, record) => {
return record.ownr_ph2 ? (
<PhoneFormatter type={record.ownr_ph2_ty}>{record.ownr_ph2}</PhoneFormatter>
) : (
t("general.labels.unknown")
);
return record.ownr_ph2 ? <PhoneFormatter>{record.ownr_ph2}</PhoneFormatter> : t("general.labels.unknown");
}
},
{
@@ -253,11 +245,7 @@ export default function JobsFindModalComponent({
>
{t("jobs.labels.override_header")}
</Checkbox>
<Checkbox
checked={partsQueueToggle}
onChange={(e) => setPartsQueueToggle(e.target.checked)}
id="parts_queue_toggle"
>
<Checkbox checked={partsQueueToggle} onChange={(e) => setPartsQueueToggle(e.target.checked)} id="parts_queue_toggle">
{t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")}
</Checkbox>
<Checkbox

View File

@@ -72,14 +72,14 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
dataIndex: "ownr_ph1",
key: "ownr_ph1",
ellipsis: true,
render: (text, record) => <StartChatButton type={record.ownr_ph1_ty} phone={record.ownr_ph1} jobid={record.id} />
render: (text, record) => <StartChatButton phone={record.ownr_ph1} jobid={record.id} />
},
{
title: t("jobs.fields.ownr_ph2"),
dataIndex: "ownr_ph2",
key: "ownr_ph2",
ellipsis: true,
render: (text, record) => <StartChatButton type={record.ownr_ph2_ty} phone={record.ownr_ph2} jobid={record.id} />
render: (text, record) => <StartChatButton phone={record.ownr_ph2} jobid={record.id} />
},
{
title: t("jobs.fields.status"),

View File

@@ -139,7 +139,7 @@ export function JobsList({ bodyshop }) {
key: "ownr_ph1",
ellipsis: true,
responsive: ["md"],
render: (text, record) => <ChatOpenButton type={record.ownr_ph1_ty} phone={record.ownr_ph1} jobid={record.id} />
render: (text, record) => <ChatOpenButton phone={record.ownr_ph1} jobid={record.id} />
},
{
title: t("jobs.fields.ownr_ph2"),
@@ -147,7 +147,7 @@ export function JobsList({ bodyshop }) {
key: "ownr_ph2",
ellipsis: true,
responsive: ["md"],
render: (text, record) => <ChatOpenButton type={record.ownr_ph2_ty} phone={record.ownr_ph2} jobid={record.id} />
render: (text, record) => <ChatOpenButton phone={record.ownr_ph2} jobid={record.id} />
},
{

View File

@@ -140,7 +140,7 @@ export function JobsReadyList({ bodyshop }) {
key: "ownr_ph1",
ellipsis: true,
responsive: ["md"],
render: (text, record) => <ChatOpenButton type={record.ownr_ph1_ty} phone={record.ownr_ph1} jobid={record.id} />
render: (text, record) => <ChatOpenButton phone={record.ownr_ph1} jobid={record.id} />
},
{
title: t("jobs.fields.ownr_ph2"),
@@ -148,7 +148,7 @@ export function JobsReadyList({ bodyshop }) {
key: "ownr_ph2",
ellipsis: true,
responsive: ["md"],
render: (text, record) => <ChatOpenButton type={record.ownr_ph2_ty} phone={record.ownr_ph2} jobid={record.id} />
render: (text, record) => <ChatOpenButton phone={record.ownr_ph2} jobid={record.id} />
},
{
title: t("jobs.fields.status"),

View File

@@ -1,4 +1,4 @@
import { Form, Input, Select, Tooltip } from "antd";
import { Form, Input, Tooltip } from "antd";
import { CloseCircleFilled } from "@ant-design/icons";
import { useTranslation } from "react-i18next";
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
@@ -10,12 +10,6 @@ export default function OwnerDetailFormComponent({ form, isPhone1OptedOut, isPho
const { t } = useTranslation();
const { getFieldValue } = form;
const PHONE_TYPE_OPTIONS = [
{ label: t("owners.labels.home"), value: "Home" },
{ label: t("owners.labels.work"), value: "Work" },
{ label: t("owners.labels.cell"), value: "Cell" }
];
return (
<div>
<FormFieldsChanged form={form} />
@@ -36,7 +30,6 @@ export default function OwnerDetailFormComponent({ form, isPhone1OptedOut, isPho
<Input disabled />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("owners.forms.address")}>
<Form.Item label={t("owners.fields.ownr_addr1")} name="ownr_addr1">
<Input />
@@ -57,7 +50,6 @@ export default function OwnerDetailFormComponent({ form, isPhone1OptedOut, isPho
<Input />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("owners.forms.contact")}>
<Form.Item
label={t("owners.fields.ownr_ea")}
@@ -71,8 +63,6 @@ export default function OwnerDetailFormComponent({ form, isPhone1OptedOut, isPho
>
<FormItemEmail email={getFieldValue("ownr_ea")} />
</Form.Item>
{/* Phone 1 + Type + Opt-out icon */}
<Form.Item label={t("owners.fields.ownr_ph1")} style={{ marginBottom: 0 }}>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<Form.Item
@@ -82,11 +72,6 @@ export default function OwnerDetailFormComponent({ form, isPhone1OptedOut, isPho
>
<Input style={{ flex: 1, minWidth: "150px" }} />
</Form.Item>
<Form.Item name="ownr_ph1_ty" noStyle>
<Select allowClear placeholder="Type" options={PHONE_TYPE_OPTIONS} style={{ width: 110 }} />
</Form.Item>
{isPhone1OptedOut && (
<Tooltip title={t("consent.text_body")}>
<CloseCircleFilled
@@ -103,8 +88,6 @@ export default function OwnerDetailFormComponent({ form, isPhone1OptedOut, isPho
)}
</div>
</Form.Item>
{/* Phone 2 + Type + Opt-out icon */}
<Form.Item label={t("owners.fields.ownr_ph2")} style={{ marginBottom: 0 }}>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<Form.Item
@@ -114,11 +97,6 @@ export default function OwnerDetailFormComponent({ form, isPhone1OptedOut, isPho
>
<Input style={{ flex: 1, minWidth: "150px" }} />
</Form.Item>
<Form.Item name="ownr_ph2_ty" noStyle>
<Select allowClear placeholder="Type" options={PHONE_TYPE_OPTIONS} style={{ width: 110 }} />
</Form.Item>
{isPhone2OptedOut && (
<Tooltip title={t("consent.text_body")}>
<CloseCircleFilled
@@ -135,7 +113,6 @@ export default function OwnerDetailFormComponent({ form, isPhone1OptedOut, isPho
)}
</div>
</Form.Item>
<Form.Item label={t("owners.fields.preferred_contact")} name="preferred_contact">
<Input />
</Form.Item>
@@ -143,7 +120,6 @@ export default function OwnerDetailFormComponent({ form, isPhone1OptedOut, isPho
<Input />
</Form.Item>
</LayoutFormRow>
<Form.Item label={t("owners.fields.note")} name="note">
<Input.TextArea rows={4} />
</Form.Item>

View File

@@ -25,10 +25,8 @@ export default function OwnerDetailUpdateJobsComponent({ owner, selectedJobs, di
ownr_ea: owner["ownr_ea"],
ownr_fn: owner["ownr_fn"],
ownr_ph1: owner["ownr_ph1"],
ownr_ph1_ty: owner["ownr_ph1_ty"],
ownr_ln: owner["ownr_ln"],
ownr_ph2: owner["ownr_ph2"],
ownr_ph2_ty: owner["ownr_ph2_ty"],
ownr_st: owner["ownr_st"],
ownr_title: owner["ownr_title"],
ownr_zip: owner["ownr_zip"]

View File

@@ -47,13 +47,13 @@ export default function OwnerFindModalComponent({
title: t("owners.fields.ownr_ph1"),
dataIndex: "ownr_ph1",
key: "ownr_ph1",
render: (text, record) => <PhoneFormatter type={record.ownr_ph1_ty}>{record.ownr_ph1}</PhoneFormatter>
render: (text, record) => <PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>
},
{
title: t("owners.fields.ownr_ph2"),
dataIndex: "ownr_ph2",
key: "ownr_ph2",
render: (text, record) => <PhoneFormatter type={record.ownr_ph2_ty}>{record.ownr_ph2}</PhoneFormatter>
render: (text, record) => <PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>
},
{
title: t("owners.fields.note"),

View File

@@ -15,10 +15,10 @@ export default function OwnerTagPopoverComponent({ job }) {
<OwnerNameDisplay ownerObject={job} />
</Descriptions.Item>
<Descriptions.Item key="2" label={t("jobs.fields.ownr_ph1")}>
<PhoneFormatter type={job.ownr_ph1_ty}>{job.ownr_ph1 || ""}</PhoneFormatter>
<PhoneFormatter>{job.ownr_ph1 || ""}</PhoneFormatter>
</Descriptions.Item>
<Descriptions.Item key="22" label={t("jobs.fields.ownr_ph2")}>
<PhoneFormatter type={job.ownr_ph2_ty}>{job.ownr_ph2 || ""}</PhoneFormatter>
<PhoneFormatter>{job.ownr_ph2 || ""}</PhoneFormatter>
</Descriptions.Item>
<Descriptions.Item key="3" label={t("owners.fields.address")}>
{`${job.ownr_addr1 || ""} ${job.ownr_addr2 || ""} ${
@@ -36,10 +36,13 @@ export default function OwnerTagPopoverComponent({ job }) {
<OwnerNameDisplay ownerObject={job.owner} />
</Descriptions.Item>
<Descriptions.Item key="2" label={t("jobs.fields.ownr_ph1")}>
<PhoneFormatter type={job.owner.ownr_ph1_ty}>{job.owner.ownr_ph1 || ""}</PhoneFormatter>
<PhoneFormatter>{job.owner.ownr_ph1 || ""}</PhoneFormatter>
</Descriptions.Item>
<Descriptions.Item key="22" label={t("jobs.fields.ownr_ph2")}>
<PhoneFormatter>{job.owner.ownr_ph2 || ""}</PhoneFormatter>
</Descriptions.Item>
<Descriptions.Item key="2" label={t("jobs.fields.ownr_ph2")}>
<PhoneFormatter type={job.owner.ownr_ph2_ty}>{job.owner.ownr_ph2 || ""}</PhoneFormatter>
<PhoneFormatter>{job.owner.ownr_ph2 || ""}</PhoneFormatter>
</Descriptions.Item>
<Descriptions.Item key="3" label={t("owners.fields.address")}>
{`${job.owner.ownr_addr1 || ""} ${job.owner.ownr_addr2 || ""} ${

View File

@@ -39,7 +39,7 @@ export default function OwnersListComponent({ loading, owners, total, refetch })
dataIndex: "ownr_ph1",
key: "ownr_ph1",
render: (text, record) => {
return <PhoneFormatter type={record.ownr_ph1_ty}>{record.ownr_ph1}</PhoneFormatter>;
return <PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>;
}
},
{
@@ -47,7 +47,7 @@ export default function OwnersListComponent({ loading, owners, total, refetch })
dataIndex: "ownr_ph2",
key: "ownr_ph2",
render: (text, record) => {
return <PhoneFormatter type={record.ownr_ph2_ty}>{record.ownr_ph2}</PhoneFormatter>;
return <PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>;
}
},
{

View File

@@ -255,14 +255,14 @@ const productionListColumnsData = ({ technician, state, activeStatuses, data, bo
dataIndex: "ownr_ph1",
key: "ownr_ph1",
ellipsis: true,
render: (text, record) => <PhoneFormatter type={record.ownr_ph1_ty}>{record.ownr_ph1}</PhoneFormatter>
render: (text, record) => <PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>
},
{
title: i18n.t("jobs.fields.ownr_ph2"),
dataIndex: "ownr_ph2",
key: "ownr_ph2",
ellipsis: true,
render: (text, record) => <PhoneFormatter type={record.ownr_ph2_ty}>{record.ownr_ph2}</PhoneFormatter>
render: (text, record) => <PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>
},
{
title: i18n.t("jobs.fields.specialcoveragepolicy"),

View File

@@ -154,25 +154,13 @@ export function ProductionListDetail({ bodyshop, jobs, setPrintCenterContext, te
<OwnerNameDisplay ownerObject={theJob} />
{!technician ? (
<>
<StartChatButton
type={data.jobs_by_pk.ownr_ph1_ty}
phone={data.jobs_by_pk.ownr_ph1}
jobid={data.jobs_by_pk.id}
/>
<StartChatButton
type={data.jobs_by_pk.ownr_ph2_ty}
phone={data.jobs_by_pk.ownr_ph2}
jobid={data.jobs_by_pk.id}
/>
<StartChatButton phone={data.jobs_by_pk.ownr_ph1} jobid={data.jobs_by_pk.id} />
<StartChatButton phone={data.jobs_by_pk.ownr_ph2} jobid={data.jobs_by_pk.id} />
</>
) : (
<>
<PhoneNumberFormatter type={data.jobs_by_pk.ownr_ph1_ty}>
{data.jobs_by_pk.ownr_ph1}
</PhoneNumberFormatter>
<PhoneNumberFormatter type={data.jobs_by_pk.ownr_ph2_ty}>
{data.jobs_by_pk.ownr_ph2}
</PhoneNumberFormatter>
<PhoneNumberFormatter>{data.jobs_by_pk.ownr_ph1}</PhoneNumberFormatter>
<PhoneNumberFormatter>{data.jobs_by_pk.ownr_ph2}</PhoneNumberFormatter>
</>
)}
</Space>

View File

@@ -40,8 +40,6 @@ export const QUERY_ALL_ACTIVE_APPOINTMENTS = gql`
ownr_fn
ownr_ph1
ownr_ph2
ownr_ph1_ty
ownr_ph2_ty
ownr_ea
clm_total
id

View File

@@ -19,9 +19,7 @@ export const QUERY_ALL_ACTIVE_JOBS_PAGINATED = gql`
ownr_ln
ownr_co_nm
ownr_ph1
ownr_ph1_ty
ownr_ph2
ownr_ph2_ty
ownr_ea
ownerid
comment
@@ -71,8 +69,6 @@ export const QUERY_ALL_ACTIVE_JOBS = gql`
ownr_co_nm
ownr_ph1
ownr_ph2
ownr_ph1_ty
ownr_ph2_ty
ownr_ea
ownerid
comment
@@ -126,8 +122,6 @@ export const QUERY_PARTS_QUEUE = gql`
ownr_co_nm
ownr_ph1
ownr_ph2
ownr_ph1_ty
ownr_ph2_ty
ownr_ea
plate_no
plate_st
@@ -185,8 +179,6 @@ export const QUERY_EXACT_JOB_IN_PRODUCTION = gql`
clm_total
ownr_ph1
ownr_ph2
ownr_ph1_ty
ownr_ph2_ty
special_coverage_policy
owner_owing
production_vars
@@ -257,8 +249,6 @@ export const QUERY_EXACT_JOBS_IN_PRODUCTION = gql`
clm_total
ownr_ph1
ownr_ph2
ownr_ph1_ty
ownr_ph2_ty
special_coverage_policy
owner_owing
production_vars
@@ -625,8 +615,6 @@ export const GET_JOB_BY_PK = gql`
ownr_ln
ownr_ph1
ownr_ph2
ownr_ph1_ty
ownr_ph2_ty
ownr_st
ownr_zip
tax_number
@@ -643,8 +631,6 @@ export const GET_JOB_BY_PK = gql`
ownr_ln
ownr_ph1
ownr_ph2
ownr_ph1_ty
ownr_ph2_ty
ownr_st
ownr_zip
parts_tax_rates
@@ -728,7 +714,7 @@ export const GET_JOB_BY_PK = gql`
v_model_yr
v_model_desc
v_vin
notes(where: { pinned: { _eq: true } }, order_by: { updated_at: desc }) {
notes(where:{pinned: {_eq: true}}, order_by: {updated_at: desc}) {
created_at
created_by
critical
@@ -844,8 +830,6 @@ export const QUERY_JOB_CARD_DETAILS = gql`
ownr_co_nm
ownr_ph1
ownr_ph2
ownr_ph1_ty
ownr_ph2_ty
comment
ownr_ea
ca_gst_registrant
@@ -1016,6 +1000,7 @@ export const QUERY_JOB_CARD_DETAILS = gql`
key
type
}
}
}
`;
@@ -1245,8 +1230,6 @@ export const GET_JOB_INFO_FOR_STRIPE = gql`
ownr_co_nm
ownr_ph1
ownr_ph2
ownr_ph1_ty
ownr_ph2_ty
ownr_ea
}
}
@@ -1460,10 +1443,8 @@ export const QUERY_JOB_FOR_DUPE = gql`
ownr_ln
ownr_ph1
ownr_ph1x
ownr_ph1_ty
ownr_ph2
ownr_ph2x
ownr_ph2_ty
ownr_st
ownr_title
ownr_zip
@@ -1709,10 +1690,8 @@ export const QUERY_ALL_JOB_FIELDS = gql`
ownr_ln
ownr_ph1
ownr_ph1x
ownr_ph1_ty
ownr_ph2
ownr_ph2x
ownr_ph2_ty
ownr_st
ownr_title
ownr_zip
@@ -1850,8 +1829,6 @@ export const QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED = gql`
ownr_co_nm
ownr_ph1
ownr_ph2
ownr_ph1_ty
ownr_ph2_ty
plate_no
plate_st
v_vin
@@ -1892,8 +1869,6 @@ export const QUERY_SIMPLIFIED_PARTS_PAGINATED_STATUS_FILTERED = gql`
ownr_co_nm
ownr_ph1
ownr_ph2
ownr_ph1_ty
ownr_ph2_ty
plate_no
plate_st
v_vin
@@ -2142,8 +2117,6 @@ export const GET_JOB_FOR_CC_CONTRACT = gql`
ownr_zip
ownr_ph1
ownr_ph2
ownr_ph1_ty
ownr_ph2_ty
}
}
`;
@@ -2442,7 +2415,7 @@ export const QUERY_PARTS_QUEUE_CARD_DETAILS = gql`
start
status
}
notes(where: { pinned: { _eq: true } }, order_by: { updated_at: desc }) {
notes(where:{pinned: {_eq: true}}, order_by: {updated_at: desc}) {
created_at
created_by
critical
@@ -2530,8 +2503,6 @@ export const QUERY_PARTS_QUEUE_CARD_DETAILS = gql`
ownr_ln
ownr_ph1
ownr_ph2
ownr_ph1_ty
ownr_ph2_ty
owner {
id
preferred_contact
@@ -2629,8 +2600,6 @@ export const QUERY_JOBS_IN_PRODUCTION = gql`
clm_total
ownr_ph1
ownr_ph2
ownr_ph1_ty
ownr_ph2_ty
special_coverage_policy
owner_owing
production_vars

View File

@@ -8,8 +8,6 @@ export const QUERY_SEARCH_OWNER_BY_IDX = gql`
ownr_co_nm
ownr_ph1
ownr_ph2
ownr_ph1_ty
ownr_ph2_ty
ownr_addr1
ownr_addr2
ownr_city
@@ -59,10 +57,8 @@ export const QUERY_OWNER_BY_ID = gql`
ownr_ea
ownr_fn
ownr_ph1
ownr_ph1_ty
ownr_ln
ownr_ph2
ownr_ph2_ty
ownr_st
ownr_title
ownr_zip
@@ -116,10 +112,8 @@ export const QUERY_ALL_OWNERS = gql`
ownr_ea
ownr_fn
ownr_ph1
ownr_ph1_ty
ownr_ln
ownr_ph2
ownr_ph2_ty
ownr_st
ownr_title
ownr_zip
@@ -142,10 +136,8 @@ export const QUERY_ALL_OWNERS_PAGINATED = gql`
ownr_ea
ownr_fn
ownr_ph1
ownr_ph1_ty
ownr_ln
ownr_ph2
ownr_ph2_ty
ownr_st
ownr_title
ownr_zip
@@ -172,10 +164,8 @@ export const QUERY_OWNER_FOR_JOB_CREATION = gql`
ownr_ea
ownr_fn
ownr_ph1
ownr_ph1_ty
ownr_ln
ownr_ph2
ownr_ph2_ty
ownr_st
ownr_title
ownr_zip

View File

@@ -177,12 +177,10 @@ export const QUERY_PARTS_ORDER_OEC = gql`
ownr_fax
ownr_faxx
ownr_ph1
ownr_ph1_ty
ownr_fn
ownr_ln
ownr_ph1x
ownr_ph2
ownr_ph2_ty
ownr_ph2x
ownr_st
ownr_title

View File

@@ -22,8 +22,6 @@ export const GLOBAL_SEARCH_QUERY = gql`
ownr_co_nm
ownr_ph1
ownr_ph2
ownr_ph1_ty
ownr_ph2_ty
}
search_vehicles(args: { search: $search }, limit: 25) {
id

View File

@@ -9,21 +9,23 @@ import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { INSERT_NEW_JOB } from "../../graphql/jobs.queries";
import { QUERY_OWNER_FOR_JOB_CREATION } from "../../graphql/owners.queries";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import JobsCreateComponent from "./jobs-create.component";
import JobCreateContext from "./jobs-create.context";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
bodyshop: selectBodyshop,
currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key))
});
function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, currentUser }) {
const { t } = useTranslation();
const notification = useNotification();
@@ -74,7 +76,7 @@ function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
}, [t, setBreadcrumbs, setSelectedHeader]);
const runInsertJob = (job) => {
insertJob({ variables: { job: job } })
insertJob({ variables: { job } })
.then((resp) => {
setState({
...state,
@@ -150,6 +152,11 @@ function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
if (job.owner === null) delete job.owner;
if (job.vehicle === null) delete job.vehicle;
// Associate to the current user if one exists
if (currentUser?.email) {
job.created_user_email = currentUser.email;
}
runInsertJob(job);
};

View File

@@ -2590,10 +2590,7 @@
"fromclaim": "Current Claim",
"fromowner": "Historical Owner Record",
"relatedjobs": "Related Jobs",
"updateowner": "Update Owner",
"work": "Work",
"home": "Home",
"cell": "Cell"
"updateowner": "Update Owner"
},
"successes": {
"delete": "Owner deleted successfully.",

View File

@@ -2590,10 +2590,7 @@
"fromclaim": "",
"fromowner": "",
"relatedjobs": "",
"updateowner": "",
"work": "",
"home": "",
"cell": ""
"updateowner": ""
},
"successes": {
"delete": "",

View File

@@ -2590,10 +2590,7 @@
"fromclaim": "",
"fromowner": "",
"relatedjobs": "",
"updateowner": "",
"work": "",
"home": "",
"cell": ""
"updateowner": ""
},
"successes": {
"delete": "",

View File

@@ -1,23 +1,7 @@
import { Typography } from "antd";
//import NumberFormat from "react-number-format";
import parsePhoneNumber from "libphonenumber-js";
const { Text } = Typography;
export default function PhoneNumberFormatter({ children, type }) {
const p = parsePhoneNumber(children || "", "CA");
if (!p) return null;
const phone = p.formatNational();
return (
<span>
<Text>{phone}</Text>
{type ? (
<>
{" "}
<Text type="secondary">({type})</Text>
</>
) : null}
</span>
);
export default function PhoneNumberFormatter(props) {
const p = parsePhoneNumber(props.children || "", "CA");
return p ? <span>{p.formatNational()}</span> : null;
}

View File

@@ -3799,10 +3799,8 @@
- ownr_fn
- ownr_ln
- ownr_ph1
- ownr_ph1_ty
- ownr_ph1x
- ownr_ph2
- ownr_ph2_ty
- ownr_ph2x
- ownr_st
- ownr_title
@@ -4081,10 +4079,8 @@
- ownr_fn
- ownr_ln
- ownr_ph1
- ownr_ph1_ty
- ownr_ph1x
- ownr_ph2
- ownr_ph2_ty
- ownr_ph2x
- ownr_st
- ownr_title
@@ -4374,10 +4370,8 @@
- ownr_fn
- ownr_ln
- ownr_ph1
- ownr_ph1_ty
- ownr_ph1x
- ownr_ph2
- ownr_ph2_ty
- ownr_ph2x
- ownr_st
- ownr_title
@@ -5177,9 +5171,7 @@
- ownr_fn
- ownr_ln
- ownr_ph1
- ownr_ph1_ty
- ownr_ph2
- ownr_ph2_ty
- ownr_st
- ownr_title
- ownr_zip
@@ -5204,9 +5196,7 @@
- ownr_fn
- ownr_ln
- ownr_ph1
- ownr_ph1_ty
- ownr_ph2
- ownr_ph2_ty
- ownr_st
- ownr_title
- ownr_zip
@@ -5242,9 +5232,7 @@
- ownr_fn
- ownr_ln
- ownr_ph1
- ownr_ph1_ty
- ownr_ph2
- ownr_ph2_ty
- ownr_st
- ownr_title
- ownr_zip

View File

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

View File

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

View File

@@ -1,2 +0,0 @@
alter table "public"."owners" add column "ownr_ph1_ty" text
null;

View File

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

View File

@@ -1,2 +0,0 @@
alter table "public"."owners" add column "ownr_ph2_ty" text
null;

View File

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

View File

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

View File

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

View File

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

View File

@@ -3089,17 +3089,19 @@ exports.INSERT_JOB_WATCHERS = `
`;
exports.GET_NOTIFICATION_WATCHERS = `
query GET_NOTIFICATION_WATCHERS($shopId: uuid!, $employeeIds: [uuid!]!) {
query GET_NOTIFICATION_WATCHERS($shopId: uuid!, $employeeIds: [uuid!]!, $createdUserEmail: String!) {
associations(where: {
_and: [
{ shopid: { _eq: $shopId } },
{ active: { _eq: true } },
{ notifications_autoadd: { _eq: true } }
{ notifications_autoadd: { _eq: true } },
{ useremail: { _eq: $createdUserEmail } }
]
}) {
id
useremail
}
employees(where: { id: { _in: $employeeIds }, shopid: { _eq: $shopId }, active: { _eq: true } }) {
user_email
}

View File

@@ -39,6 +39,7 @@ const autoAddWatchers = async (req) => {
const jobId = event?.data?.new?.id;
const shopId = event?.data?.new?.shopid;
const roNumber = event?.data?.new?.ro_number || "unknown";
const createdUserEmail = event?.data?.new?.created_user_email || "Unknown";
if (!jobId || !shopId) {
throw new Error(`Missing jobId (${jobId}) or shopId (${shopId}) for auto-add watchers`);
@@ -61,7 +62,8 @@ const autoAddWatchers = async (req) => {
const [notificationData, existingWatchersData] = await Promise.all([
gqlClient.request(GET_NOTIFICATION_WATCHERS, {
shopId,
employeeIds: notificationFollowers
employeeIds: notificationFollowers,
createdUserEmail
}),
gqlClient.request(GET_JOB_WATCHERS_MINIMAL, { jobid: jobId })
]);