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,
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"),
dataIndex: "memo",

View File

@@ -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 (
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
<Button onClick={handleQbxml} loading={loading} disabled={disabled} type="primary">
{t("jobs.actions.export")}
</Button>
);

View File

@@ -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 (
<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")}
</Button>
);

View File

@@ -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 (
<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")}
</Button>
);

View File

@@ -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 (
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
<Button onClick={handleQbxml} loading={loading} disabled={disabled} type="primary">
{t("jobs.actions.export")}
</Button>
);

View File

@@ -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 (
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
<Button onClick={handleQbxml} loading={loading} disabled={disabled} type="primary">
{t("jobs.actions.export")}
</Button>
);

View File

@@ -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 (
<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")}
</Button>
);

View File

@@ -1261,7 +1261,7 @@ export function ShopInfoGeneral({ form, bodyshop }) {
key={`${index}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
label={t("joblines.fields.ah_detail_line")}

View File

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

View File

@@ -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?",

View File

@@ -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"),

View File

@@ -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

View File

@@ -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 });
};
/**