From 9b44dd844f5cc04baa9fe8c0160bca4bdce087c9 Mon Sep 17 00:00:00 2001 From: Dave Date: Mon, 22 Dec 2025 14:18:13 -0500 Subject: [PATCH] feature/IO-3487-Auto-Add-Profile-Watchers - Fix Auto Add on a profile level --- .../contract-convert-to-ro.component.jsx | 4 +++ .../job-create-iou.component.jsx | 14 ++++---- .../jobs-available-table.container.jsx | 4 +++ .../jobs-detail-header-actions.component.jsx | 32 ++++++++++++------- ...bs-detail-header-actions.duplicate.util.js | 19 +++++++++-- .../jobs-create/jobs-create.container.jsx | 15 ++++++--- hasura/metadata/tables.yaml | 5 ++- .../down.sql | 4 +++ .../up.sql | 2 ++ server/graphql-client/queries.js | 6 ++-- server/notifications/autoAddWatchers.js | 4 ++- 11 files changed, 81 insertions(+), 28 deletions(-) create mode 100644 hasura/migrations/1766427606596_alter_table_public_jobs_add_column_created_user_email/down.sql create mode 100644 hasura/migrations/1766427606596_alter_table_public_jobs_add_column_created_user_email/up.sql diff --git a/client/src/components/contract-convert-to-ro/contract-convert-to-ro.component.jsx b/client/src/components/contract-convert-to-ro/contract-convert-to-ro.component.jsx index 36d7b977a..d53c2c151 100644 --- a/client/src/components/contract-convert-to-ro/contract-convert-to-ro.component.jsx +++ b/client/src/components/contract-convert-to-ro/contract-convert-to-ro.component.jsx @@ -253,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 = ( diff --git a/client/src/components/job-create-iou/job-create-iou.component.jsx b/client/src/components/job-create-iou/job-create-iou.component.jsx index a09b41224..2aa027218 100644 --- a/client/src/components/job-create-iou/job-create-iou.component.jsx +++ b/client/src/components/job-create-iou/job-create-iou.component.jsx @@ -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"), 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 f5e6589bd..35a53aefd 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 @@ -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; 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 655694d8f..42aa416fa 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 @@ -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) => { diff --git a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.duplicate.util.js b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.duplicate.util.js index 7706f91bc..8e0705e79 100644 --- a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.duplicate.util.js +++ b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.duplicate.util.js @@ -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] } diff --git a/client/src/pages/jobs-create/jobs-create.container.jsx b/client/src/pages/jobs-create/jobs-create.container.jsx index 85f2b3ffe..9905a72fc 100644 --- a/client/src/pages/jobs-create/jobs-create.container.jsx +++ b/client/src/pages/jobs-create/jobs-create.container.jsx @@ -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); }; diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml index 9900fa00b..8231c2dd0 100644 --- a/hasura/metadata/tables.yaml +++ b/hasura/metadata/tables.yaml @@ -3684,6 +3684,7 @@ - completed_tasks - converted - created_at + - created_user_email - cust_pr - date_estimated - date_exported @@ -3961,6 +3962,7 @@ - completed_tasks - converted - created_at + - created_user_email - cust_pr - date_estimated - date_exported @@ -4251,6 +4253,7 @@ - completed_tasks - converted - created_at + - created_user_email - cust_pr - date_estimated - date_exported @@ -4641,7 +4644,7 @@ request_transform: body: action: transform - template: "{\r\n \"event\": {\r\n \"session_variables\": {\r\n \"x-hasura-user-id\": {{$body?.event?.session_variables?.x-hasura-user-id ?? \"Internal\"}},\r\n \"x-hasura-role\": {{$body?.event?.session_variables?.x-hasura-role ?? \"Internal\"}}\r\n }, \r\n \"op\": {{$body.event.op}},\r\n \"data\": {\r\n \"new\": {\r\n \"id\": {{$body.event.data.new.id}},\r\n \"shopid\": {{$body.event.data.new?.shopid}},\r\n \"ro_number\": {{$body.event.data.new?.ro_number}}\r\n }\r\n }\r\n },\r\n \"trigger\": {\r\n \"name\": \"notifications_jobs_autoadd\"\r\n },\r\n \"table\": {\r\n \"schema\": \"public\",\r\n \"name\": \"jobs\"\r\n }\r\n}\r\n" + template: "{\r\n \"event\": {\r\n \"session_variables\": {\r\n \"x-hasura-user-id\": {{$body?.event?.session_variables?.x-hasura-user-id ?? \"Internal\"}},\r\n \"x-hasura-role\": {{$body?.event?.session_variables?.x-hasura-role ?? \"Internal\"}}\r\n }, \r\n \"op\": {{$body.event.op}},\r\n \"data\": {\r\n \"new\": {\r\n \"id\": {{$body.event.data.new.id}},\r\n \"shopid\": {{$body.event.data.new?.shopid}},\r\n \"ro_number\": {{$body.event.data.new?.ro_number}},\r\n \"created_user_email\": {{$body.event.data.new?.created_user_email}}\r\n }\r\n }\r\n },\r\n \"trigger\": {\r\n \"name\": \"notifications_jobs_autoadd\"\r\n },\r\n \"table\": {\r\n \"schema\": \"public\",\r\n \"name\": \"jobs\"\r\n }\r\n}\r\n" method: POST query_params: {} template_engine: Kriti diff --git a/hasura/migrations/1766427606596_alter_table_public_jobs_add_column_created_user_email/down.sql b/hasura/migrations/1766427606596_alter_table_public_jobs_add_column_created_user_email/down.sql new file mode 100644 index 000000000..ddb7199ff --- /dev/null +++ b/hasura/migrations/1766427606596_alter_table_public_jobs_add_column_created_user_email/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 "created_user_email" text +-- null; diff --git a/hasura/migrations/1766427606596_alter_table_public_jobs_add_column_created_user_email/up.sql b/hasura/migrations/1766427606596_alter_table_public_jobs_add_column_created_user_email/up.sql new file mode 100644 index 000000000..d8244715b --- /dev/null +++ b/hasura/migrations/1766427606596_alter_table_public_jobs_add_column_created_user_email/up.sql @@ -0,0 +1,2 @@ +alter table "public"."jobs" add column "created_user_email" text + null; diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index b51c73c49..a4ed143dc 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -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 } diff --git a/server/notifications/autoAddWatchers.js b/server/notifications/autoAddWatchers.js index 803c44984..0a0fa3e04 100644 --- a/server/notifications/autoAddWatchers.js +++ b/server/notifications/autoAddWatchers.js @@ -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 }) ]);