Merged in release/2025-01-17 (pull request #2053)

Release/2025 01 17 into test-AIO - IO-1927, IO-3022, IO-3060, IO-3078, IO-3080, IO-3082
This commit is contained in:
Dave Richer
2025-01-14 17:02:26 +00:00
13 changed files with 92 additions and 59 deletions

View File

@@ -85,6 +85,17 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, payments,
sortOrder: state.sortedInfo.columnKey === "amount" && state.sortedInfo.order, sortOrder: state.sortedInfo.columnKey === "amount" && state.sortedInfo.order,
render: (text, record) => <CurrencyFormatter>{record.amount}</CurrencyFormatter> render: (text, record) => <CurrencyFormatter>{record.amount}</CurrencyFormatter>
}, },
{
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"), title: t("payments.fields.memo"),
dataIndex: "memo", dataIndex: "memo",

View File

@@ -173,7 +173,7 @@ export function JobsCloseExportButton({
} }
}); });
if (!!!jobUpdateResponse.errors) { if (!jobUpdateResponse.errors) {
notification.open({ notification.open({
type: "success", type: "success",
key: "jobsuccessexport", key: "jobsuccessexport",
@@ -222,7 +222,7 @@ export function JobsCloseExportButton({
}; };
return ( return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled}> <Button onClick={handleQbxml} loading={loading} disabled={disabled} type="primary">
{t("jobs.actions.export")} {t("jobs.actions.export")}
</Button> </Button>
); );

View File

@@ -10,8 +10,8 @@ import { auth, logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { UPDATE_JOBS } from "../../graphql/jobs.queries"; import { UPDATE_JOBS } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions"; import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import client from "../../utils/GraphQLClient"; import client from "../../utils/GraphQLClient";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -165,7 +165,7 @@ export function JobsExportAllButton({
} }
}); });
if (!!!jobUpdateResponse.errors) { if (!jobUpdateResponse.errors) {
notification.open({ notification.open({
type: "success", type: "success",
key: "jobsuccessexport", key: "jobsuccessexport",
@@ -213,13 +213,13 @@ export function JobsExportAllButton({
}) })
); );
if (!!completedCallback) completedCallback([]); if (completedCallback) completedCallback([]);
if (!!loadingCallback) loadingCallback(false); if (loadingCallback) loadingCallback(false);
setLoading(false); setLoading(false);
}; };
return ( return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled || jobIds?.length > 10}> <Button onClick={handleQbxml} loading={loading} disabled={disabled || jobIds?.length > 10} type="primary">
{t("jobs.actions.exportselected")} {t("jobs.actions.exportselected")}
</Button> </Button>
); );

View File

@@ -48,7 +48,7 @@ export function PayableExportAll({
let PartnerResponse; let PartnerResponse;
setLoading(true); setLoading(true);
if (!!loadingCallback) loadingCallback(true); if (loadingCallback) loadingCallback(true);
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
PartnerResponse = await axios.post(`/qbo/payables`, { PartnerResponse = await axios.post(`/qbo/payables`, {
bills: billids, bills: billids,
@@ -85,7 +85,7 @@ export function PayableExportAll({
notification["error"]({ notification["error"]({
message: t("bills.errors.exporting-partner") message: t("bills.errors.exporting-partner")
}); });
if (!!loadingCallback) loadingCallback(false); if (loadingCallback) loadingCallback(false);
setLoading(false); setLoading(false);
return; return;
} }
@@ -152,7 +152,7 @@ export function PayableExportAll({
} }
} }
}); });
if (!!!billUpdateResponse.errors) { if (!billUpdateResponse.errors) {
notification.open({ notification.open({
type: "success", type: "success",
key: "billsuccessexport", key: "billsuccessexport",
@@ -187,8 +187,8 @@ export function PayableExportAll({
}); });
await Promise.all(proms); await Promise.all(proms);
if (!!completedCallback) completedCallback([]); if (completedCallback) completedCallback([]);
if (!!loadingCallback) loadingCallback(false); if (loadingCallback) loadingCallback(false);
setLoading(false); setLoading(false);
}; };
@@ -200,7 +200,7 @@ export function PayableExportAll({
); );
return ( return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled || billids?.length > 10}> <Button onClick={handleQbxml} loading={loading} disabled={disabled || billids?.length > 10} type="primary">
{t("jobs.actions.exportselected")} {t("jobs.actions.exportselected")}
</Button> </Button>
); );

View File

@@ -46,7 +46,7 @@ export function PayableExportButton({
logImEXEvent("accounting_export_payable"); logImEXEvent("accounting_export_payable");
setLoading(true); setLoading(true);
if (!!loadingCallback) loadingCallback(true); if (loadingCallback) loadingCallback(true);
//Check if it's a QBO Setup. //Check if it's a QBO Setup.
let PartnerResponse; let PartnerResponse;
@@ -88,7 +88,7 @@ export function PayableExportButton({
notification["error"]({ notification["error"]({
message: t("bills.errors.exporting-partner") message: t("bills.errors.exporting-partner")
}); });
if (!!loadingCallback) loadingCallback(false); if (loadingCallback) loadingCallback(false);
setLoading(false); setLoading(false);
return; return;
} }
@@ -149,7 +149,7 @@ export function PayableExportButton({
} }
} }
}); });
if (!!!billUpdateResponse.errors) { if (!billUpdateResponse.errors) {
notification.open({ notification.open({
type: "success", type: "success",
key: "billsuccessexport", key: "billsuccessexport",
@@ -186,7 +186,7 @@ export function PayableExportButton({
} }
} }
if (!!loadingCallback) loadingCallback(false); if (loadingCallback) loadingCallback(false);
setLoading(false); setLoading(false);
}; };
@@ -198,7 +198,7 @@ export function PayableExportButton({
); );
return ( return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled}> <Button onClick={handleQbxml} loading={loading} disabled={disabled} type="primary">
{t("jobs.actions.export")} {t("jobs.actions.export")}
</Button> </Button>
); );

View File

@@ -55,7 +55,7 @@ export function PaymentExportButton({
} else { } else {
//Default is QBD //Default is QBD
if (!!loadingCallback) loadingCallback(true); if (loadingCallback) loadingCallback(true);
let QbXmlResponse; let QbXmlResponse;
try { try {
@@ -88,7 +88,7 @@ export function PaymentExportButton({
notification["error"]({ notification["error"]({
message: t("payments.errors.exporting-partner") message: t("payments.errors.exporting-partner")
}); });
if (!!loadingCallback) loadingCallback(false); if (loadingCallback) loadingCallback(false);
setLoading(false); setLoading(false);
return; return;
} }
@@ -148,7 +148,7 @@ export function PaymentExportButton({
} }
} }
}); });
if (!!!paymentUpdateResponse.errors) { if (!paymentUpdateResponse.errors) {
notification.open({ notification.open({
type: "success", type: "success",
key: "paymentsuccessexport", key: "paymentsuccessexport",
@@ -184,12 +184,12 @@ export function PaymentExportButton({
) )
]); ]);
} }
if (!!loadingCallback) loadingCallback(false); if (loadingCallback) loadingCallback(false);
setLoading(false); setLoading(false);
}; };
return ( return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled}> <Button onClick={handleQbxml} loading={loading} disabled={disabled} type="primary">
{t("jobs.actions.export")} {t("jobs.actions.export")}
</Button> </Button>
); );

View File

@@ -44,7 +44,7 @@ export function PaymentsExportAllButton({
const handleQbxml = async () => { const handleQbxml = async () => {
setLoading(true); setLoading(true);
if (!!loadingCallback) loadingCallback(true); if (loadingCallback) loadingCallback(true);
let PartnerResponse; let PartnerResponse;
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
PartnerResponse = await axios.post(`/qbo/payments`, { PartnerResponse = await axios.post(`/qbo/payments`, {
@@ -76,7 +76,7 @@ export function PaymentsExportAllButton({
notification["error"]({ notification["error"]({
message: t("payments.errors.exporting-partner") message: t("payments.errors.exporting-partner")
}); });
if (!!loadingCallback) loadingCallback(false); if (loadingCallback) loadingCallback(false);
setLoading(false); setLoading(false);
return; return;
} }
@@ -140,7 +140,7 @@ export function PaymentsExportAllButton({
} }
} }
}); });
if (!!!paymentUpdateResponse.errors) { if (!paymentUpdateResponse.errors) {
notification.open({ notification.open({
type: "success", type: "success",
key: "paymentsuccessexport", key: "paymentsuccessexport",
@@ -174,13 +174,13 @@ export function PaymentsExportAllButton({
); );
}); });
await Promise.all(proms); await Promise.all(proms);
if (!!completedCallback) completedCallback([]); if (completedCallback) completedCallback([]);
if (!!loadingCallback) loadingCallback(false); if (loadingCallback) loadingCallback(false);
setLoading(false); setLoading(false);
}; };
return ( return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled || paymentIds?.length > 10}> <Button onClick={handleQbxml} loading={loading} disabled={disabled || paymentIds?.length > 10} type="primary">
{t("jobs.actions.exportselected")} {t("jobs.actions.exportselected")}
</Button> </Button>
); );

View File

@@ -1261,7 +1261,7 @@ export function ShopInfoGeneral({ form, bodyshop }) {
key={`${index}prt_dsmk_p`} key={`${index}prt_dsmk_p`}
name={[field.name, "prt_dsmk_p"]} name={[field.name, "prt_dsmk_p"]}
> >
<InputNumber precision={0} min={0} max={100} /> <InputNumber precision={0} min={-100} max={100} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("joblines.fields.ah_detail_line")} label={t("joblines.fields.ah_detail_line")}

View File

@@ -73,6 +73,7 @@ export const QUERY_PAYMENTS_FOR_EXPORT = gql`
transactionid transactionid
paymentnum paymentnum
date date
type
exportlogs { exportlogs {
id id
successful successful

View File

@@ -636,7 +636,7 @@
"target_touchtime": "Target Touch Time", "target_touchtime": "Target Touch Time",
"timezone": "Timezone", "timezone": "Timezone",
"tt_allow_post_to_invoiced": "Allow Time Tickets to be posted to Invoiced & Exported Jobs", "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_fippa": "Conceal Customer Information on Generated Documents?",
"use_paint_scale_data": "Use Paint Scale Data for Job Costing?", "use_paint_scale_data": "Use Paint Scale Data for Job Costing?",
"uselocalmediaserver": "Use Local Media Server?", "uselocalmediaserver": "Use Local Media Server?",

View File

@@ -1444,7 +1444,8 @@ export const TemplateList = (type, context) => {
object: i18n.t("reportcenter.labels.objects.jobs"), object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_exported") field: i18n.t("jobs.fields.date_exported")
}, },
group: "sales" group: "sales",
featureNameRestricted: "export"
}, },
gsr_by_estimator: { gsr_by_estimator: {
title: i18n.t("reportcenter.templates.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"), object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_open") field: i18n.t("jobs.fields.date_open")
}, },
group: "jobs" group: "jobs",
featureNameRestricted: "bills"
}, },
psr_by_make: { psr_by_make: {
title: i18n.t("reportcenter.templates.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"), object: i18n.t("reportcenter.labels.objects.parts_orders"),
field: i18n.t("parts_orders.fields.order_date") field: i18n.t("parts_orders.fields.order_date")
}, },
group: "jobs" group: "jobs",
featureNameRestricted: "bills"
}, },
returns_grouped_by_vendor_detailed: { returns_grouped_by_vendor_detailed: {
title: i18n.t("reportcenter.templates.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"), object: i18n.t("reportcenter.labels.objects.parts_orders"),
field: i18n.t("parts_orders.fields.order_date") field: i18n.t("parts_orders.fields.order_date")
}, },
group: "jobs" group: "jobs",
featureNameRestricted: "bills"
}, },
scheduled_parts_list: { scheduled_parts_list: {
title: i18n.t("reportcenter.templates.scheduled_parts_list"), title: i18n.t("reportcenter.templates.scheduled_parts_list"),

View File

@@ -706,7 +706,7 @@
method: POST method: POST
query_params: {} query_params: {}
template_engine: Kriti template_engine: Kriti
url: '{{$base_url}}/job/events/handleBillsChange' url: '{{$base_url}}/notifications/events/handleBillsChange'
version: 2 version: 2
- name: os_bills - name: os_bills
definition: definition:
@@ -4477,7 +4477,7 @@
method: POST method: POST
query_params: {} query_params: {}
template_engine: Kriti template_engine: Kriti
url: '{{$base_url}}/job/events/handleJobsChange' url: '{{$base_url}}/notifications/events/handleJobsChange'
version: 2 version: 2
- name: os_jobs - name: os_jobs
definition: definition:
@@ -5137,7 +5137,7 @@
method: POST method: POST
query_params: {} query_params: {}
template_engine: Kriti template_engine: Kriti
url: '{{$base_url}}/job/events/handlePartsDispatchChange' url: '{{$base_url}}/notifications/events/handlePartsDispatchChange'
version: 2 version: 2
- table: - table:
name: parts_dispatch_lines name: parts_dispatch_lines
@@ -6109,7 +6109,7 @@
_eq: true _eq: true
check: null check: null
event_triggers: event_triggers:
- name: tasks_assigned_changed - name: notifications_tasks
definition: definition:
enable_manual: false enable_manual: false
insert: insert:
@@ -6119,6 +6119,34 @@
- assigned_to - assigned_to
- completed - completed
- description - 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: retry_conf:
interval_sec: 10 interval_sec: 10
num_retries: 3 num_retries: 3
@@ -6291,7 +6319,7 @@
method: POST method: POST
query_params: {} query_params: {}
template_engine: Kriti template_engine: Kriti
url: '{{$base_url}}/job/events/handleTimeTicketsChange' url: '{{$base_url}}/notifications/events/handleTimeTicketsChange'
version: 2 version: 2
- table: - table:
name: transitions name: transitions

View File

@@ -142,38 +142,27 @@ const sendMail = (type, to, subject, html, taskIds, successCallback, requestInst
* @returns {Promise<*>} * @returns {Promise<*>}
*/ */
const taskAssignedEmail = async (req, res) => { 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) { if (!req?.body?.event?.data?.new) {
return res.status(400).json({ message: "No data in the event body" }); 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 // This is not a new task, but a reassignment.
if (oldTask && oldTask.assigned_to === newTask.assigned_to) { const dirty = req.body.event.data?.old && req.body.event.data?.old?.assigned_to;
return res.status(200).json({ success: true, message: "assigned_to not changed" });
}
// 3. If we made it here, assigned_to changed (or oldTask is missing), //Query to get the employee assigned currently.
// so we continue with the rest of the logic.
// Query to get the task with bodyshop data, assigned employee data, etc.
const { tasks_by_pk } = await client.request(queries.QUERY_TASK_BY_ID, { const { tasks_by_pk } = await client.request(queries.QUERY_TASK_BY_ID, {
id: newTask.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"); 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( sendMail(
"assigned", "assigned",
tasks_by_pk.assigned_to_employee.user_email, tasks_by_pk.assigned_to_employee.user_email,
`A ${formatPriority(newTask.priority)} priority task has been ${ `A ${formatPriority(newTask.priority)} priority task has been ${dirty ? "reassigned to" : "created for"} you - ${newTask.title}`,
dirty ? "reassigned" : "created"
} for you - ${newTask.title}`,
generateEmailTemplate( generateEmailTemplate(
generateTemplateArgs( generateTemplateArgs(
newTask.title, newTask.title,
@@ -192,8 +181,8 @@ const taskAssignedEmail = async (req, res) => {
tasks_by_pk.bodyshop.convenient_company tasks_by_pk.bodyshop.convenient_company
); );
// Return success so we don't block the event trigger. // We return success regardless because we don't want to block the event trigger.
return res.status(200).json({ success: true }); res.status(200).json({ success: true });
}; };
/** /**