From 80379cdd791241450f438fd6d1c467fdb5f8db0d Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 9 Jan 2025 17:37:30 -0800 Subject: [PATCH 1/6] IO-3082 Additional IO Basic Lock Out Reports Signed-off-by: Allan Carr --- client/src/utils/TemplateConstants.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/client/src/utils/TemplateConstants.js b/client/src/utils/TemplateConstants.js index 9c282dc6c..42241d152 100644 --- a/client/src/utils/TemplateConstants.js +++ b/client/src/utils/TemplateConstants.js @@ -1444,7 +1444,8 @@ export const TemplateList = (type, context) => { object: i18n.t("reportcenter.labels.objects.jobs"), field: i18n.t("jobs.fields.date_exported") }, - group: "sales" + group: "sales", + featureNameRestricted: "export" }, gsr_by_estimator: { title: i18n.t("reportcenter.templates.gsr_by_estimator"), @@ -1865,7 +1866,8 @@ export const TemplateList = (type, context) => { object: i18n.t("reportcenter.labels.objects.jobs"), field: i18n.t("jobs.fields.date_open") }, - group: "jobs" + group: "jobs", + featureNameRestricted: "bills" }, psr_by_make: { title: i18n.t("reportcenter.templates.psr_by_make"), @@ -1901,7 +1903,8 @@ export const TemplateList = (type, context) => { object: i18n.t("reportcenter.labels.objects.parts_orders"), field: i18n.t("parts_orders.fields.order_date") }, - group: "jobs" + group: "jobs", + featureNameRestricted: "bills" }, returns_grouped_by_vendor_detailed: { title: i18n.t("reportcenter.templates.returns_grouped_by_vendor_detailed"), @@ -1913,7 +1916,8 @@ export const TemplateList = (type, context) => { object: i18n.t("reportcenter.labels.objects.parts_orders"), field: i18n.t("parts_orders.fields.order_date") }, - group: "jobs" + group: "jobs", + featureNameRestricted: "bills" }, scheduled_parts_list: { title: i18n.t("reportcenter.templates.scheduled_parts_list"), From c1b3df9c3bd04b1e0506f63a7da22a41aae069e5 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 9 Jan 2025 17:43:43 -0800 Subject: [PATCH 2/6] IO-3080 Restrict Claimable Hours Label Signed-off-by: Allan Carr --- client/src/translations/en_us/common.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index f153a242a..7f82f69f1 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -636,7 +636,7 @@ "target_touchtime": "Target Touch Time", "timezone": "Timezone", "tt_allow_post_to_invoiced": "Allow Time Tickets to be posted to Invoiced & Exported Jobs", - "tt_enforce_hours_for_tech_console": "Restrict Claimable hours from Tech Console", + "tt_enforce_hours_for_tech_console": "Restrict Claimable Hours", "use_fippa": "Conceal Customer Information on Generated Documents?", "use_paint_scale_data": "Use Paint Scale Data for Job Costing?", "uselocalmediaserver": "Use Local Media Server?", From 779cc7d9e8e363262b68a8fcc310da5c53314609 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 9 Jan 2025 18:57:21 -0800 Subject: [PATCH 3/6] IO-3078 Jobs Presets Company Setup Markup Discount Signed-off-by: Allan Carr --- client/src/components/shop-info/shop-info.general.component.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/shop-info/shop-info.general.component.jsx b/client/src/components/shop-info/shop-info.general.component.jsx index 90a17b4b1..19f1ecb9e 100644 --- a/client/src/components/shop-info/shop-info.general.component.jsx +++ b/client/src/components/shop-info/shop-info.general.component.jsx @@ -1261,7 +1261,7 @@ export function ShopInfoGeneral({ form, bodyshop }) { key={`${index}prt_dsmk_p`} name={[field.name, "prt_dsmk_p"]} > - + Date: Thu, 9 Jan 2025 19:16:45 -0800 Subject: [PATCH 4/6] IO-3022 Export Payments - Payment Method Signed-off-by: Allan Carr --- .../accounting-payments-table.component.jsx | 11 +++++++++++ client/src/graphql/accounting.queries.js | 1 + 2 files changed, 12 insertions(+) diff --git a/client/src/components/accounting-payments-table/accounting-payments-table.component.jsx b/client/src/components/accounting-payments-table/accounting-payments-table.component.jsx index f738cd0ea..edaa05187 100644 --- a/client/src/components/accounting-payments-table/accounting-payments-table.component.jsx +++ b/client/src/components/accounting-payments-table/accounting-payments-table.component.jsx @@ -85,6 +85,17 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, payments, sortOrder: state.sortedInfo.columnKey === "amount" && state.sortedInfo.order, render: (text, record) => {record.amount} }, + { + title: t("payments.fields.type"), + dataIndex: "type", + key: "type", + sorter: (a, b) => a.type.localeCompare(b.type), + sortOrder: state.sortedInfo.columnKey === "type" && state.sortedInfo.order, + filters: bodyshop.md_payment_types.map((s) => { + return { text: s, value: [s] }; + }), + onFilter: (value, record) => value.includes(record.type) + }, { title: t("payments.fields.memo"), dataIndex: "memo", diff --git a/client/src/graphql/accounting.queries.js b/client/src/graphql/accounting.queries.js index 930df6cf1..88aacedbe 100644 --- a/client/src/graphql/accounting.queries.js +++ b/client/src/graphql/accounting.queries.js @@ -73,6 +73,7 @@ export const QUERY_PAYMENTS_FOR_EXPORT = gql` transactionid paymentnum date + type exportlogs { id successful From c7f293cecac8fd5f18ddbfbb4dfe1ac6041e8b35 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 9 Jan 2025 19:28:28 -0800 Subject: [PATCH 5/6] IO-1927 Export Button as Primary Signed-off-by: Allan Carr --- .../jobs-close-export-button.component.jsx | 4 ++-- .../jobs-export-all-button.component.jsx | 10 +++++----- .../payable-export-all-button.component.jsx | 12 ++++++------ .../payable-export-button.component.jsx | 10 +++++----- .../payment-export-button.component.jsx | 10 +++++----- .../payments-export-all-button.component.jsx | 12 ++++++------ 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/client/src/components/jobs-close-export-button/jobs-close-export-button.component.jsx b/client/src/components/jobs-close-export-button/jobs-close-export-button.component.jsx index e84b31595..f22da1eab 100644 --- a/client/src/components/jobs-close-export-button/jobs-close-export-button.component.jsx +++ b/client/src/components/jobs-close-export-button/jobs-close-export-button.component.jsx @@ -173,7 +173,7 @@ export function JobsCloseExportButton({ } }); - if (!!!jobUpdateResponse.errors) { + if (!jobUpdateResponse.errors) { notification.open({ type: "success", key: "jobsuccessexport", @@ -222,7 +222,7 @@ export function JobsCloseExportButton({ }; return ( - ); diff --git a/client/src/components/jobs-export-all-button/jobs-export-all-button.component.jsx b/client/src/components/jobs-export-all-button/jobs-export-all-button.component.jsx index e149f39f0..f9670dc77 100644 --- a/client/src/components/jobs-export-all-button/jobs-export-all-button.component.jsx +++ b/client/src/components/jobs-export-all-button/jobs-export-all-button.component.jsx @@ -10,8 +10,8 @@ import { auth, logImEXEvent } from "../../firebase/firebase.utils"; import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; import { UPDATE_JOBS } from "../../graphql/jobs.queries"; import { insertAuditTrail } from "../../redux/application/application.actions"; -import AuditTrailMapping from "../../utils/AuditTrailMappings"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; +import AuditTrailMapping from "../../utils/AuditTrailMappings"; import client from "../../utils/GraphQLClient"; const mapStateToProps = createStructuredSelector({ @@ -165,7 +165,7 @@ export function JobsExportAllButton({ } }); - if (!!!jobUpdateResponse.errors) { + if (!jobUpdateResponse.errors) { notification.open({ type: "success", key: "jobsuccessexport", @@ -213,13 +213,13 @@ export function JobsExportAllButton({ }) ); - if (!!completedCallback) completedCallback([]); - if (!!loadingCallback) loadingCallback(false); + if (completedCallback) completedCallback([]); + if (loadingCallback) loadingCallback(false); setLoading(false); }; return ( - ); diff --git a/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx b/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx index dc8316be8..6e2d2b5ed 100644 --- a/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx +++ b/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx @@ -48,7 +48,7 @@ export function PayableExportAll({ let PartnerResponse; setLoading(true); - if (!!loadingCallback) loadingCallback(true); + if (loadingCallback) loadingCallback(true); if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { PartnerResponse = await axios.post(`/qbo/payables`, { bills: billids, @@ -85,7 +85,7 @@ export function PayableExportAll({ notification["error"]({ message: t("bills.errors.exporting-partner") }); - if (!!loadingCallback) loadingCallback(false); + if (loadingCallback) loadingCallback(false); setLoading(false); return; } @@ -152,7 +152,7 @@ export function PayableExportAll({ } } }); - if (!!!billUpdateResponse.errors) { + if (!billUpdateResponse.errors) { notification.open({ type: "success", key: "billsuccessexport", @@ -187,8 +187,8 @@ export function PayableExportAll({ }); await Promise.all(proms); - if (!!completedCallback) completedCallback([]); - if (!!loadingCallback) loadingCallback(false); + if (completedCallback) completedCallback([]); + if (loadingCallback) loadingCallback(false); setLoading(false); }; @@ -200,7 +200,7 @@ export function PayableExportAll({ ); return ( - ); diff --git a/client/src/components/payable-export-button/payable-export-button.component.jsx b/client/src/components/payable-export-button/payable-export-button.component.jsx index 3249da15d..f369b031c 100644 --- a/client/src/components/payable-export-button/payable-export-button.component.jsx +++ b/client/src/components/payable-export-button/payable-export-button.component.jsx @@ -46,7 +46,7 @@ export function PayableExportButton({ logImEXEvent("accounting_export_payable"); setLoading(true); - if (!!loadingCallback) loadingCallback(true); + if (loadingCallback) loadingCallback(true); //Check if it's a QBO Setup. let PartnerResponse; @@ -88,7 +88,7 @@ export function PayableExportButton({ notification["error"]({ message: t("bills.errors.exporting-partner") }); - if (!!loadingCallback) loadingCallback(false); + if (loadingCallback) loadingCallback(false); setLoading(false); return; } @@ -149,7 +149,7 @@ export function PayableExportButton({ } } }); - if (!!!billUpdateResponse.errors) { + if (!billUpdateResponse.errors) { notification.open({ type: "success", key: "billsuccessexport", @@ -186,7 +186,7 @@ export function PayableExportButton({ } } - if (!!loadingCallback) loadingCallback(false); + if (loadingCallback) loadingCallback(false); setLoading(false); }; @@ -198,7 +198,7 @@ export function PayableExportButton({ ); return ( - ); diff --git a/client/src/components/payment-export-button/payment-export-button.component.jsx b/client/src/components/payment-export-button/payment-export-button.component.jsx index 876dc5af7..35937da58 100644 --- a/client/src/components/payment-export-button/payment-export-button.component.jsx +++ b/client/src/components/payment-export-button/payment-export-button.component.jsx @@ -55,7 +55,7 @@ export function PaymentExportButton({ } else { //Default is QBD - if (!!loadingCallback) loadingCallback(true); + if (loadingCallback) loadingCallback(true); let QbXmlResponse; try { @@ -88,7 +88,7 @@ export function PaymentExportButton({ notification["error"]({ message: t("payments.errors.exporting-partner") }); - if (!!loadingCallback) loadingCallback(false); + if (loadingCallback) loadingCallback(false); setLoading(false); return; } @@ -148,7 +148,7 @@ export function PaymentExportButton({ } } }); - if (!!!paymentUpdateResponse.errors) { + if (!paymentUpdateResponse.errors) { notification.open({ type: "success", key: "paymentsuccessexport", @@ -184,12 +184,12 @@ export function PaymentExportButton({ ) ]); } - if (!!loadingCallback) loadingCallback(false); + if (loadingCallback) loadingCallback(false); setLoading(false); }; return ( - ); diff --git a/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx b/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx index 5c4246890..46131daae 100644 --- a/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx +++ b/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx @@ -44,7 +44,7 @@ export function PaymentsExportAllButton({ const handleQbxml = async () => { setLoading(true); - if (!!loadingCallback) loadingCallback(true); + if (loadingCallback) loadingCallback(true); let PartnerResponse; if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { PartnerResponse = await axios.post(`/qbo/payments`, { @@ -76,7 +76,7 @@ export function PaymentsExportAllButton({ notification["error"]({ message: t("payments.errors.exporting-partner") }); - if (!!loadingCallback) loadingCallback(false); + if (loadingCallback) loadingCallback(false); setLoading(false); return; } @@ -140,7 +140,7 @@ export function PaymentsExportAllButton({ } } }); - if (!!!paymentUpdateResponse.errors) { + if (!paymentUpdateResponse.errors) { notification.open({ type: "success", key: "paymentsuccessexport", @@ -174,13 +174,13 @@ export function PaymentsExportAllButton({ ); }); await Promise.all(proms); - if (!!completedCallback) completedCallback([]); - if (!!loadingCallback) loadingCallback(false); + if (completedCallback) completedCallback([]); + if (loadingCallback) loadingCallback(false); setLoading(false); }; return ( - ); From a06d3c9365583b8b8000a0b7f30f9029f3816035 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 14 Jan 2025 09:00:04 -0800 Subject: [PATCH 6/6] feature/IO-3060-Realtime-Notifications- Checkpoint --- hasura/metadata/tables.yaml | 38 ++++++++++++++++++++++++++++++++----- server/email/tasksEmails.js | 27 ++++++++------------------ 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml index 68dab7c39..d7a010473 100644 --- a/hasura/metadata/tables.yaml +++ b/hasura/metadata/tables.yaml @@ -706,7 +706,7 @@ method: POST query_params: {} template_engine: Kriti - url: '{{$base_url}}/job/events/handleBillsChange' + url: '{{$base_url}}/notifications/events/handleBillsChange' version: 2 - name: os_bills definition: @@ -4477,7 +4477,7 @@ method: POST query_params: {} template_engine: Kriti - url: '{{$base_url}}/job/events/handleJobsChange' + url: '{{$base_url}}/notifications/events/handleJobsChange' version: 2 - name: os_jobs definition: @@ -5137,7 +5137,7 @@ method: POST query_params: {} template_engine: Kriti - url: '{{$base_url}}/job/events/handlePartsDispatchChange' + url: '{{$base_url}}/notifications/events/handlePartsDispatchChange' version: 2 - table: name: parts_dispatch_lines @@ -6109,7 +6109,7 @@ _eq: true check: null event_triggers: - - name: tasks_assigned_changed + - name: notifications_tasks definition: enable_manual: false insert: @@ -6119,6 +6119,34 @@ - assigned_to - completed - description + retry_conf: + interval_sec: 10 + num_retries: 0 + timeout_sec: 60 + webhook_from_env: HASURA_API_URL + headers: + - name: event-secret + value_from_env: EVENT_SECRET + request_transform: + body: + action: transform + template: |- + { + "success": true + } + method: POST + query_params: {} + template_engine: Kriti + url: '{{$base_url}}/notifications/events/handleTasksChange' + version: 2 + - name: tasks_assigned_changed + definition: + enable_manual: false + insert: + columns: '*' + update: + columns: + - assigned_to retry_conf: interval_sec: 10 num_retries: 3 @@ -6291,7 +6319,7 @@ method: POST query_params: {} template_engine: Kriti - url: '{{$base_url}}/job/events/handleTimeTicketsChange' + url: '{{$base_url}}/notifications/events/handleTimeTicketsChange' version: 2 - table: name: transitions diff --git a/server/email/tasksEmails.js b/server/email/tasksEmails.js index 7a05bcb05..6d787811a 100644 --- a/server/email/tasksEmails.js +++ b/server/email/tasksEmails.js @@ -142,38 +142,27 @@ const sendMail = (type, to, subject, html, taskIds, successCallback, requestInst * @returns {Promise<*>} */ const taskAssignedEmail = async (req, res) => { - // 1. Check if we have new task data in the event body. + // We have no event Data, bail if (!req?.body?.event?.data?.new) { return res.status(400).json({ message: "No data in the event body" }); } - const { new: newTask, old: oldTask } = req.body.event.data; + const { new: newTask } = req.body.event.data; - // TODO: THIS IS HERE BECAUSE THE HANDLER NOW DOES 3 FIELDS, WILL NEED TO BE BUILT ON FOR NOTIFICATIONS - if (oldTask && oldTask.assigned_to === newTask.assigned_to) { - return res.status(200).json({ success: true, message: "assigned_to not changed" }); - } + // This is not a new task, but a reassignment. + const dirty = req.body.event.data?.old && req.body.event.data?.old?.assigned_to; - // 3. If we made it here, assigned_to changed (or oldTask is missing), - // so we continue with the rest of the logic. - - // Query to get the task with bodyshop data, assigned employee data, etc. + //Query to get the employee assigned currently. const { tasks_by_pk } = await client.request(queries.QUERY_TASK_BY_ID, { id: newTask.id }); - // Format date/time with the correct timezone const dateLine = moment().tz(tasks_by_pk.bodyshop.timezone).format("M/DD/YYYY @ hh:mm a"); - // This determines if it was re-assigned (old task exists and had an assigned_to). - const dirty = oldTask && oldTask.assigned_to; - sendMail( "assigned", tasks_by_pk.assigned_to_employee.user_email, - `A ${formatPriority(newTask.priority)} priority task has been ${ - dirty ? "reassigned" : "created" - } for you - ${newTask.title}`, + `A ${formatPriority(newTask.priority)} priority task has been ${dirty ? "reassigned to" : "created for"} you - ${newTask.title}`, generateEmailTemplate( generateTemplateArgs( newTask.title, @@ -192,8 +181,8 @@ const taskAssignedEmail = async (req, res) => { tasks_by_pk.bodyshop.convenient_company ); - // Return success so we don't block the event trigger. - return res.status(200).json({ success: true }); + // We return success regardless because we don't want to block the event trigger. + res.status(200).json({ success: true }); }; /**