Compare commits
3 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca1a456312 | ||
|
|
c010665ea9 | ||
|
|
d6fba12cd9 |
@@ -609,7 +609,7 @@ export function JobsDetailHeaderActions({
|
|||||||
<FormDateTimePickerComponent
|
<FormDateTimePickerComponent
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
const start = form.getFieldValue("start");
|
const start = form.getFieldValue("start");
|
||||||
form.setFieldsValue({ end: start?.add(30, "minutes") });
|
form.setFieldsValue({ end: start.add(30, "minutes") });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ export function ProductionListEmpAssignment({ insertAuditTrail, bodyshop, record
|
|||||||
<Spin spinning={loading}>
|
<Spin spinning={loading}>
|
||||||
{record[type] ? (
|
{record[type] ? (
|
||||||
<div>
|
<div>
|
||||||
<span>{`${theEmployee?.first_name || ""} ${theEmployee?.last_name || ""}`}</span>
|
<span>{`${theEmployee.first_name || ""} ${theEmployee.last_name || ""}`}</span>
|
||||||
<DeleteFilled style={iconStyle} onClick={() => handleRemove(type)} />
|
<DeleteFilled style={iconStyle} onClick={() => handleRemove(type)} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export default function ShopInfoNotificationsAutoadd({ bodyshop }) {
|
|||||||
<Text type="secondary">{t("bodyshop.labels.notifications.followers")}</Text>
|
<Text type="secondary">{t("bodyshop.labels.notifications.followers")}</Text>
|
||||||
{employeeOptions.length > 0 ? (
|
{employeeOptions.length > 0 ? (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
normalize={(value) => (value || []).filter((id) => typeof id === "string" && id.trim() !== "")}
|
||||||
name="notification_followers"
|
name="notification_followers"
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
@@ -42,11 +43,6 @@ export default function ShopInfoNotificationsAutoadd({ bodyshop }) {
|
|||||||
options={employeeOptions}
|
options={employeeOptions}
|
||||||
placeholder={t("bodyshop.fields.notifications.placeholder")}
|
placeholder={t("bodyshop.fields.notifications.placeholder")}
|
||||||
showEmail={true}
|
showEmail={true}
|
||||||
onChange={(value) => {
|
|
||||||
// Filter out null or invalid values before passing to Form
|
|
||||||
const cleanedValue = value?.filter((id) => id != null && typeof id === "string" && id.trim() !== "");
|
|
||||||
return cleanedValue;
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { getFirestore } from "@firebase/firestore";
|
|||||||
import { getMessaging, getToken, onMessage } from "@firebase/messaging";
|
import { getMessaging, getToken, onMessage } from "@firebase/messaging";
|
||||||
import { store } from "../redux/store";
|
import { store } from "../redux/store";
|
||||||
//import * as amplitude from '@amplitude/analytics-browser';
|
//import * as amplitude from '@amplitude/analytics-browser';
|
||||||
// import posthog from 'posthog-js'
|
import posthog from 'posthog-js'
|
||||||
|
|
||||||
const config = JSON.parse(import.meta.env.VITE_APP_FIREBASE_CONFIG);
|
const config = JSON.parse(import.meta.env.VITE_APP_FIREBASE_CONFIG);
|
||||||
initializeApp(config);
|
initializeApp(config);
|
||||||
@@ -74,6 +74,7 @@ onMessage(messaging, (payload) => {
|
|||||||
|
|
||||||
export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
|
export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const state = stateProp || store.getState();
|
const state = stateProp || store.getState();
|
||||||
|
|
||||||
const eventParams = {
|
const eventParams = {
|
||||||
@@ -98,7 +99,8 @@ export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
|
|||||||
// );
|
// );
|
||||||
logEvent(analytics, eventName, eventParams);
|
logEvent(analytics, eventName, eventParams);
|
||||||
//amplitude.track(eventName, eventParams);
|
//amplitude.track(eventName, eventParams);
|
||||||
//posthog.capture(eventName, eventParams);
|
posthog.capture(eventName, eventParams);
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
//If it fails, just keep going.
|
//If it fails, just keep going.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,8 +31,7 @@ if (!import.meta.env.DEV) {
|
|||||||
"Module specifier, 'fs' does not start",
|
"Module specifier, 'fs' does not start",
|
||||||
"Module specifier, 'zlib' does not start with",
|
"Module specifier, 'zlib' does not start with",
|
||||||
"Messaging: This browser doesn't support the API's required to use the Firebase SDK.",
|
"Messaging: This browser doesn't support the API's required to use the Firebase SDK.",
|
||||||
"Failed to update a ServiceWorker for scope",
|
"Failed to update a ServiceWorker for scope"
|
||||||
"Network Error"
|
|
||||||
],
|
],
|
||||||
integrations: [
|
integrations: [
|
||||||
// See docs for support of different versions of variation of react router
|
// See docs for support of different versions of variation of react router
|
||||||
|
|||||||
@@ -24,13 +24,11 @@ const lightningCssTargets = browserslistToTargets(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const pstFormatter = new Intl.DateTimeFormat("en-CA", {
|
const currentDatePST = new Date()
|
||||||
timeZone: "America/Los_Angeles",
|
.toLocaleDateString("en-US", { timeZone: "America/Los_Angeles", year: "numeric", month: "2-digit", day: "2-digit" })
|
||||||
year: "numeric",
|
.split("/")
|
||||||
month: "2-digit",
|
.reverse()
|
||||||
day: "2-digit"
|
.join("-");
|
||||||
});
|
|
||||||
const currentDatePST = pstFormatter.format(new Date());
|
|
||||||
|
|
||||||
const getFormattedTimestamp = () =>
|
const getFormattedTimestamp = () =>
|
||||||
new Date().toLocaleTimeString("en-US", { hour12: true }).replace("AM", "a.m.").replace("PM", "p.m.");
|
new Date().toLocaleTimeString("en-US", { hour12: true }).replace("AM", "a.m.").replace("PM", "p.m.");
|
||||||
|
|||||||
@@ -1156,7 +1156,11 @@
|
|||||||
enable_manual: false
|
enable_manual: false
|
||||||
update:
|
update:
|
||||||
columns:
|
columns:
|
||||||
|
- imexshopid
|
||||||
|
- timezone
|
||||||
- shopname
|
- shopname
|
||||||
|
- notification_followers
|
||||||
|
- state
|
||||||
- md_order_statuses
|
- md_order_statuses
|
||||||
retry_conf:
|
retry_conf:
|
||||||
interval_sec: 10
|
interval_sec: 10
|
||||||
@@ -3698,6 +3702,7 @@
|
|||||||
- deliverchecklist
|
- deliverchecklist
|
||||||
- depreciation_taxes
|
- depreciation_taxes
|
||||||
- dms_allocation
|
- dms_allocation
|
||||||
|
- dms_id
|
||||||
- driveable
|
- driveable
|
||||||
- employee_body
|
- employee_body
|
||||||
- employee_csr
|
- employee_csr
|
||||||
@@ -3975,6 +3980,7 @@
|
|||||||
- deliverchecklist
|
- deliverchecklist
|
||||||
- depreciation_taxes
|
- depreciation_taxes
|
||||||
- dms_allocation
|
- dms_allocation
|
||||||
|
- dms_id
|
||||||
- driveable
|
- driveable
|
||||||
- employee_body
|
- employee_body
|
||||||
- employee_csr
|
- employee_csr
|
||||||
@@ -4264,6 +4270,7 @@
|
|||||||
- deliverchecklist
|
- deliverchecklist
|
||||||
- depreciation_taxes
|
- depreciation_taxes
|
||||||
- dms_allocation
|
- dms_allocation
|
||||||
|
- dms_id
|
||||||
- driveable
|
- driveable
|
||||||
- employee_body
|
- employee_body
|
||||||
- employee_csr
|
- employee_csr
|
||||||
|
|||||||
@@ -2926,6 +2926,15 @@ exports.GET_BODYSHOP_BY_ID = `
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports.GET_BODYSHOP_WATCHERS_BY_ID = `
|
||||||
|
query GET_BODYSHOP_BY_ID($id: uuid!) {
|
||||||
|
bodyshops_by_pk(id: $id) {
|
||||||
|
id
|
||||||
|
notification_followers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
exports.GET_DOCUMENTS_BY_JOB = `
|
exports.GET_DOCUMENTS_BY_JOB = `
|
||||||
query GET_DOCUMENTS_BY_JOB($jobId: uuid!) {
|
query GET_DOCUMENTS_BY_JOB($jobId: uuid!) {
|
||||||
jobs_by_pk(id: $jobId) {
|
jobs_by_pk(id: $jobId) {
|
||||||
|
|||||||
@@ -241,6 +241,8 @@ const partsManagementProvisioning = async (req, res) => {
|
|||||||
"phone",
|
"phone",
|
||||||
"userEmail"
|
"userEmail"
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// TODO add in check for early access
|
||||||
await ensureExternalIdUnique(body.external_shop_id);
|
await ensureExternalIdUnique(body.external_shop_id);
|
||||||
|
|
||||||
logger.log("admin-create-shop-user", "debug", body.userEmail, null, {
|
logger.log("admin-create-shop-user", "debug", body.userEmail, null, {
|
||||||
|
|||||||
@@ -4,11 +4,14 @@
|
|||||||
* This module handles automatically adding watchers to new jobs based on the notifications_autoadd
|
* This module handles automatically adding watchers to new jobs based on the notifications_autoadd
|
||||||
* boolean field in the associations table and the notification_followers JSON field in the bodyshops table.
|
* boolean field in the associations table and the notification_followers JSON field in the bodyshops table.
|
||||||
* It ensures users are not added twice and logs the process.
|
* It ensures users are not added twice and logs the process.
|
||||||
|
*
|
||||||
|
* NOTE: Bodyshop notification_followers is fetched directly from the DB (Hasura) to avoid stale Redis cache.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { client: gqlClient } = require("../graphql-client/graphql-client");
|
const { client: gqlClient } = require("../graphql-client/graphql-client");
|
||||||
const { isEmpty } = require("lodash");
|
const { isEmpty } = require("lodash");
|
||||||
const {
|
const {
|
||||||
|
GET_BODYSHOP_WATCHERS_BY_ID,
|
||||||
GET_JOB_WATCHERS_MINIMAL,
|
GET_JOB_WATCHERS_MINIMAL,
|
||||||
GET_NOTIFICATION_WATCHERS,
|
GET_NOTIFICATION_WATCHERS,
|
||||||
INSERT_JOB_WATCHERS
|
INSERT_JOB_WATCHERS
|
||||||
@@ -26,10 +29,7 @@ const FILTER_SELF_FROM_WATCHERS = process.env?.FILTER_SELF_FROM_WATCHERS !== "fa
|
|||||||
*/
|
*/
|
||||||
const autoAddWatchers = async (req) => {
|
const autoAddWatchers = async (req) => {
|
||||||
const { event, trigger } = req.body;
|
const { event, trigger } = req.body;
|
||||||
const {
|
const { logger } = req;
|
||||||
logger,
|
|
||||||
sessionUtils: { getBodyshopFromRedis }
|
|
||||||
} = req;
|
|
||||||
|
|
||||||
// Validate that this is an INSERT event, bail
|
// Validate that this is an INSERT event, bail
|
||||||
if (trigger?.name !== "notifications_jobs_autoadd" || event.op !== "INSERT" || event.data.old) {
|
if (trigger?.name !== "notifications_jobs_autoadd" || event.op !== "INSERT" || event.data.old) {
|
||||||
@@ -48,20 +48,20 @@ const autoAddWatchers = async (req) => {
|
|||||||
const hasuraUserId = event?.session_variables?.["x-hasura-user-id"];
|
const hasuraUserId = event?.session_variables?.["x-hasura-user-id"];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch bodyshop data from Redis
|
// Fetch bodyshop data directly from DB (avoid Redis staleness)
|
||||||
const bodyshopData = await getBodyshopFromRedis(shopId);
|
const bodyshopResponse = await gqlClient.request(GET_BODYSHOP_WATCHERS_BY_ID, { id: shopId });
|
||||||
let notificationFollowers = bodyshopData?.notification_followers;
|
const bodyshopData = bodyshopResponse?.bodyshops_by_pk;
|
||||||
|
|
||||||
// Bail if notification_followers is missing or not an array
|
const notificationFollowersRaw = bodyshopData?.notification_followers;
|
||||||
if (!notificationFollowers || !Array.isArray(notificationFollowers)) {
|
const notificationFollowers = Array.isArray(notificationFollowersRaw)
|
||||||
return;
|
? [...new Set(notificationFollowersRaw.filter((id) => id))] // de-dupe + remove falsy
|
||||||
}
|
: [];
|
||||||
|
|
||||||
// Execute queries in parallel
|
// Execute queries in parallel
|
||||||
const [notificationData, existingWatchersData] = await Promise.all([
|
const [notificationData, existingWatchersData] = await Promise.all([
|
||||||
gqlClient.request(GET_NOTIFICATION_WATCHERS, {
|
gqlClient.request(GET_NOTIFICATION_WATCHERS, {
|
||||||
shopId,
|
shopId,
|
||||||
employeeIds: notificationFollowers.filter((id) => id)
|
employeeIds: notificationFollowers
|
||||||
}),
|
}),
|
||||||
gqlClient.request(GET_JOB_WATCHERS_MINIMAL, { jobid: jobId })
|
gqlClient.request(GET_JOB_WATCHERS_MINIMAL, { jobid: jobId })
|
||||||
]);
|
]);
|
||||||
@@ -73,7 +73,7 @@ const autoAddWatchers = async (req) => {
|
|||||||
associationId: assoc.id
|
associationId: assoc.id
|
||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
// Get users from notification_followers
|
// Get users from notification_followers (employee IDs -> employee emails)
|
||||||
const followerEmails =
|
const followerEmails =
|
||||||
notificationData?.employees
|
notificationData?.employees
|
||||||
?.filter((e) => e.user_email)
|
?.filter((e) => e.user_email)
|
||||||
@@ -84,7 +84,7 @@ const autoAddWatchers = async (req) => {
|
|||||||
|
|
||||||
// Combine and deduplicate emails (use email as the unique key)
|
// Combine and deduplicate emails (use email as the unique key)
|
||||||
const usersToAdd = [...autoAddUsers, ...followerEmails].reduce((acc, user) => {
|
const usersToAdd = [...autoAddUsers, ...followerEmails].reduce((acc, user) => {
|
||||||
if (!acc.some((u) => u.email === user.email)) {
|
if (user?.email && !acc.some((u) => u.email === user.email)) {
|
||||||
acc.push(user);
|
acc.push(user);
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
@@ -123,6 +123,7 @@ const autoAddWatchers = async (req) => {
|
|||||||
message: error?.message,
|
message: error?.message,
|
||||||
stack: error?.stack,
|
stack: error?.stack,
|
||||||
jobId,
|
jobId,
|
||||||
|
shopId,
|
||||||
roNumber
|
roNumber
|
||||||
});
|
});
|
||||||
throw error; // Re-throw to ensure the error is logged in the handler
|
throw error; // Re-throw to ensure the error is logged in the handler
|
||||||
|
|||||||
Reference in New Issue
Block a user