Merge remote-tracking branch 'origin/release/2024-09-06' into feature/IO-2742-redis
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql
|
||||
VITE_APP_GA_CODE=231099835
|
||||
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
|
||||
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql
|
||||
VITE_APP_GA_CODE=231099835
|
||||
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
|
||||
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql
|
||||
VITE_APP_GA_CODE=231099835
|
||||
VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
|
||||
# VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
|
||||
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
|
||||
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
|
||||
VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
|
||||
VITE_APP_CLOUDINARY_API_KEY=957865933348715
|
||||
|
||||
4
client/package-lock.json
generated
4
client/package-lock.json
generated
@@ -109,7 +109,8 @@
|
||||
"vite-plugin-legacy": "^2.1.0",
|
||||
"vite-plugin-node-polyfills": "^0.22.0",
|
||||
"vite-plugin-pwa": "^0.20.1",
|
||||
"vite-plugin-style-import": "^2.0.0"
|
||||
"vite-plugin-style-import": "^2.0.0",
|
||||
"workbox-window": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.18.2"
|
||||
@@ -18429,6 +18430,7 @@
|
||||
"resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.1.0.tgz",
|
||||
"integrity": "sha512-ZHeROyqR+AS5UPzholQRDttLFqGMwP0Np8MKWAdyxsDETxq3qOAyXvqessc3GniohG6e0mAqSQyKOHmT8zPF7g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/trusted-types": "^2.0.2",
|
||||
"workbox-core": "7.1.0"
|
||||
|
||||
@@ -153,6 +153,7 @@
|
||||
"vite-plugin-legacy": "^2.1.0",
|
||||
"vite-plugin-node-polyfills": "^0.22.0",
|
||||
"vite-plugin-pwa": "^0.20.1",
|
||||
"vite-plugin-style-import": "^2.0.0"
|
||||
"vite-plugin-style-import": "^2.0.0",
|
||||
"workbox-window": "^7.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import PropTypes from "prop-types";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import dayjs from "../../utils/day";
|
||||
import { dateFormats, dateTimeFormats } from "./formats.js";
|
||||
import { fuzzyMatchDate } from "./formats.js";
|
||||
|
||||
const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, isDateOnly = false, ...restProps }) => {
|
||||
const [isManualInput, setIsManualInput] = useState(false);
|
||||
@@ -11,79 +11,34 @@ const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, is
|
||||
|
||||
const handleChange = useCallback(
|
||||
(newDate) => {
|
||||
if (newDate === null && onChange) {
|
||||
onChange(null);
|
||||
} else if (newDate && onChange) {
|
||||
onChange(newDate);
|
||||
if (onChange) {
|
||||
onChange(newDate || null);
|
||||
}
|
||||
setIsManualInput(false);
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const normalizeDateTimeString = (input) => {
|
||||
const upperV = input.toUpperCase().replaceAll(".", "/").replaceAll("-", "/");
|
||||
|
||||
const [datePart, ...timeParts] = upperV.split(" ");
|
||||
|
||||
if (timeParts.length === 0) {
|
||||
return datePart; // If there's no time part, just return the date part.
|
||||
}
|
||||
|
||||
const timePart = timeParts.join(" "); // In case there are multiple spaces, join them back
|
||||
|
||||
// Normalize the time part by ensuring there's a space before AM/PM if not already present
|
||||
const normalizedTime = timePart.replace(/(\d{1,2})(:\d{2})?\s?(AM|PM)/, "$1$2 $3");
|
||||
|
||||
// Combine the date part with the normalized time part
|
||||
return `${datePart} ${normalizedTime}`.trim();
|
||||
};
|
||||
|
||||
const handleBlur = useCallback(
|
||||
(e) => {
|
||||
// Bail if this is not a manual input
|
||||
if (!isManualInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset manual input flag
|
||||
setIsManualInput(false);
|
||||
|
||||
const v = e.target.value;
|
||||
const v = e?.target?.value;
|
||||
|
||||
if (!v) return;
|
||||
|
||||
const upperV = normalizeDateTimeString(v);
|
||||
let parsedDate;
|
||||
let parsedDate = isDateOnly ? fuzzyMatchDate(v)?.startOf("day") : fuzzyMatchDate(v);
|
||||
|
||||
for (const format of isDateOnly ? dateFormats : dateTimeFormats) {
|
||||
parsedDate = dayjs(upperV, format);
|
||||
if (parsedDate.isValid()) break;
|
||||
}
|
||||
|
||||
if (parsedDate && parsedDate.isValid()) {
|
||||
if (isDateOnly) {
|
||||
parsedDate = parsedDate.startOf("day");
|
||||
}
|
||||
|
||||
if (value && value.isValid && value.isValid()) {
|
||||
parsedDate = parsedDate.set({
|
||||
hours: value.hours(),
|
||||
minutes: value.minutes(),
|
||||
seconds: value.seconds(),
|
||||
milliseconds: value.milliseconds()
|
||||
});
|
||||
}
|
||||
|
||||
if (onlyFuture) {
|
||||
if (dayjs().subtract(1, "day").isBefore(parsedDate)) {
|
||||
onChange(parsedDate);
|
||||
} else {
|
||||
onChange(dayjs().startOf("day"));
|
||||
}
|
||||
} else {
|
||||
onChange(parsedDate);
|
||||
}
|
||||
if (parsedDate && onChange) {
|
||||
onChange(parsedDate);
|
||||
}
|
||||
},
|
||||
[isManualInput, isDateOnly, onlyFuture, onChange, value]
|
||||
[isManualInput, isDateOnly, onChange]
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
|
||||
@@ -1,96 +1,63 @@
|
||||
export const dateTimeFormats = [
|
||||
// Four-digit year with time
|
||||
"M/D/YYYY h:mm A", // Example: 1/5/2023 9:00 AM
|
||||
"M/D/YYYY h:mmA", // Example: 1/5/2023 9:00AM
|
||||
"M/D/YYYY h A", // Example: 1/5/2023 9 AM
|
||||
"M/D/YYYY hA", // Example: 1/5/2023 9AM
|
||||
"M/D/YYYY hh:mm A", // Example: 1/5/2023 02:25 PM
|
||||
"M/D/YYYY hh:mm:ss A", // Example: 1/5/2023 02:25:45 PM
|
||||
import dayjs from "../../utils/day";
|
||||
|
||||
"MM/D/YYYY h:mm A", // Example: 12/5/2023 9:00 AM
|
||||
"MM/D/YYYY h:mmA", // Example: 12/5/2023 9:00AM
|
||||
"MM/D/YYYY h A", // Example: 12/5/2023 9 AM
|
||||
"MM/D/YYYY hA", // Example: 12/5/2023 9AM
|
||||
"MM/D/YYYY hh:mm A", // Example: 12/5/2023 02:25 PM
|
||||
"MM/D/YYYY hh:mm:ss A", // Example: 12/5/2023 02:25:45 PM
|
||||
|
||||
"M/DD/YYYY h:mm A", // Example: 1/25/2023 9:00 AM
|
||||
"M/DD/YYYY h:mmA", // Example: 1/25/2023 9:00AM
|
||||
"M/DD/YYYY h A", // Example: 1/25/2023 9 AM
|
||||
"M/DD/YYYY hA", // Example: 1/25/2023 9AM
|
||||
"M/DD/YYYY hh:mm A", // Example: 1/25/2023 02:25 PM
|
||||
"M/DD/YYYY hh:mm:ss A", // Example: 1/25/2023 02:25:45 PM
|
||||
|
||||
"MM/DD/YYYY h:mm A", // Example: 12/25/2023 9:00 AM
|
||||
"MM/DD/YYYY h:mmA", // Example: 12/25/2023 9:00AM
|
||||
"MM/DD/YYYY h A", // Example: 12/25/2023 9 AM
|
||||
"MM/DD/YYYY hA", // Example: 12/25/2023 9AM
|
||||
"MM/DD/YYYY hh:mm A", // Example: 12/25/2023 02:25 PM
|
||||
"MM/DD/YYYY hh:mm:ss A", // Example: 12/25/2023 02:25:45 PM
|
||||
|
||||
// Two-digit year with time
|
||||
"M/D/YY h:mm A", // Example: 1/5/23 9:00 AM
|
||||
"M/D/YY h:mmA", // Example: 1/5/23 9:00AM
|
||||
"M/D/YY h A", // Example: 1/5/23 9 AM
|
||||
"M/D/YY hA", // Example: 1/5/23 9AM
|
||||
"M/D/YY hh:mm A", // Example: 1/5/23 02:25 PM
|
||||
"M/D/YY hh:mm:ss A", // Example: 1/5/23 02:25:45 PM
|
||||
|
||||
"MM/D/YY h:mm A", // Example: 12/5/23 9:00 AM
|
||||
"MM/D/YY h:mmA", // Example: 12/5/23 9:00AM
|
||||
"MM/D/YY h A", // Example: 12/5/23 9 AM
|
||||
"MM/D/YY hA", // Example: 12/5/23 9AM
|
||||
"MM/D/YY hh:mm A", // Example: 12/5/23 02:25 PM
|
||||
"MM/D/YY hh:mm:ss A", // Example: 12/5/23 02:25:45 PM
|
||||
|
||||
"M/DD/YY h:mm A", // Example: 1/25/23 9:00 AM
|
||||
"M/DD/YY h:mmA", // Example: 1/25/23 9:00AM
|
||||
"M/DD/YY h A", // Example: 1/25/23 9 AM
|
||||
"M/DD/YY hA", // Example: 1/25/23 9AM
|
||||
"M/DD/YY hh:mm A", // Example: 1/25/23 02:25 PM
|
||||
"M/DD/YY hh:mm:ss A", // Example: 1/25/23 02:25:45 PM
|
||||
|
||||
"MM/DD/YY h:mm A", // Example: 12/25/23 9:00 AM
|
||||
"MM/DD/YY h:mmA", // Example: 12/25/23 9:00AM
|
||||
"MM/DD/YY h A", // Example: 12/25/23 9 AM
|
||||
"MM/DD/YY hA", // Example: 12/25/23 9AM
|
||||
"MM/DD/YY hh:mm A", // Example: 12/25/23 02:25 PM
|
||||
"MM/DD/YY hh:mm:ss A", // Example: 12/25/23 02:25:45 PM
|
||||
|
||||
// Four-digit year without time
|
||||
"M/D/YYYY", // Example: 1/5/2023
|
||||
"MM/D/YYYY", // Example: 12/5/2023
|
||||
"M/DD/YYYY", // Example: 1/25/2023
|
||||
"MM/DD/YYYY", // Example: 12/25/2023
|
||||
|
||||
// Two-digit year without time
|
||||
"M/D/YY", // Example: 1/5/23
|
||||
"MM/D/YY", // Example: 12/5/23
|
||||
"M/DD/YY", // Example: 1/25/23
|
||||
"MM/DD/YY" // Example: 12/25/23
|
||||
];
|
||||
|
||||
// CONFIRMED
|
||||
export const dateFormats = [
|
||||
const dateFormats = [
|
||||
"MMDDYYYY",
|
||||
"MMDDYY",
|
||||
// Four-digit year
|
||||
"M/D/YYYY", // Example: 1/5/2023
|
||||
"MM/D/YYYY", // Example: 12/5/2023
|
||||
"M/DD/YYYY", // Example: 1/25/2023
|
||||
"MM/DD/YYYY", // Example: 12/25/2023
|
||||
|
||||
// Two-digit year
|
||||
"M/D/YY", // Example: 1/5/23
|
||||
"MM/D/YY", // Example: 12/5/23
|
||||
"M/DD/YY", // Example: 1/25/23
|
||||
"MM/DD/YY", // Example: 12/25/23
|
||||
|
||||
// Explicitly handle single-digit month and day
|
||||
"M/D/YY", // Example: 1/5/23
|
||||
"M/D/YYYY", // Example: 1/5/2023
|
||||
"M/DD/YY", // Example: 1/25/23
|
||||
"M/DD/YYYY", // Example: 1/25/2023
|
||||
"MM/D/YY", // Example: 12/5/23
|
||||
"MM/D/YYYY" // Example: 12/5/2023
|
||||
"M/D/YYYY",
|
||||
"MM/D/YYYY",
|
||||
"M/DD/YYYY",
|
||||
"MM/DD/YYYY",
|
||||
"M/D/YY",
|
||||
"MM/D/YY",
|
||||
"M/DD/YY",
|
||||
"MM/DD/YY"
|
||||
];
|
||||
|
||||
const timeFormats = ["h:mm A", "h:mmA", "h A", "hA", "hh:mm A", "hh:mm:ss A"];
|
||||
|
||||
const dateTimeFormats = [
|
||||
...["M/D/YYYY", "MM/D/YYYY", "M/DD/YYYY", "MM/DD/YYYY", "M/D/YY", "MM/D/YY", "M/DD/YY", "MM/DD/YY"].flatMap(
|
||||
(dateFormat) => timeFormats.map((timeFormat) => `${dateFormat} ${timeFormat}`)
|
||||
),
|
||||
|
||||
...["MMDDYYYY", "MMDDYY"].flatMap((dateFormat) => timeFormats.map((timeFormat) => `${dateFormat} ${timeFormat}`)),
|
||||
|
||||
"M/D/YYYY",
|
||||
"MM/D/YYYY",
|
||||
"M/DD/YYYY",
|
||||
"MM/DD/YYYY",
|
||||
"M/D/YY",
|
||||
"MM/D/YY",
|
||||
"M/DD/YY",
|
||||
"MM/DD/YY",
|
||||
"MMDDYYYY",
|
||||
"MMDDYY"
|
||||
];
|
||||
|
||||
const sanitizeInput = (input) =>
|
||||
input
|
||||
.trim()
|
||||
.toUpperCase()
|
||||
.replace(/\s*(am|pm)\s*/i, " $1")
|
||||
.replaceAll(".", "/")
|
||||
.replaceAll("-", "/");
|
||||
|
||||
export const fuzzyMatchDate = (dateString) => {
|
||||
const sanitizedInput = sanitizeInput(dateString);
|
||||
|
||||
for (const format of dateFormats) {
|
||||
const parsedDate = dayjs(sanitizedInput, format, true);
|
||||
if (parsedDate.isValid()) {
|
||||
return parsedDate;
|
||||
}
|
||||
}
|
||||
|
||||
for (const format of dateTimeFormats) {
|
||||
const parsedDateTime = dayjs(sanitizedInput, format, true);
|
||||
if (parsedDateTime.isValid()) {
|
||||
return parsedDateTime; // Return the dayjs object
|
||||
}
|
||||
}
|
||||
|
||||
return null; // If no matching format is found
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Card, Statistic } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import PropTypes from "prop-types";
|
||||
import { defaultKanbanSettings, statisticsItems } from "./settings/defaultKanbanSettings.js";
|
||||
import Dinero from "dinero.js";
|
||||
|
||||
export const StatisticType = {
|
||||
HOURS: "hours",
|
||||
@@ -32,7 +33,21 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
||||
};
|
||||
|
||||
const calculateTotalAmount = (items, key) => {
|
||||
return items.reduce((acc, item) => acc + (item[key]?.totals?.subtotal?.amount || 0), 0);
|
||||
return items.reduce((acc, item) => acc.add(Dinero(item[key]?.totals?.subtotal ?? Dinero())), Dinero({ amount: 0 }));
|
||||
};
|
||||
|
||||
const calculateReducerTotalAmount = (lanes, key) => {
|
||||
return lanes.reduce(
|
||||
(acc, lane) => {
|
||||
return acc.add(
|
||||
lane.cards.reduce(
|
||||
(laneAcc, card) => laneAcc.add(Dinero(card.metadata[key]?.totals?.subtotal ?? Dinero())),
|
||||
Dinero({ amount: 0 })
|
||||
)
|
||||
);
|
||||
},
|
||||
Dinero({ amount: 0 })
|
||||
);
|
||||
};
|
||||
|
||||
const calculateReducerTotal = (lanes, key, subKey) => {
|
||||
@@ -43,14 +58,6 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
||||
}, 0);
|
||||
};
|
||||
|
||||
const calculateReducerTotalAmount = (lanes, key) => {
|
||||
return lanes.reduce((acc, lane) => {
|
||||
return (
|
||||
acc + lane.cards.reduce((laneAcc, card) => laneAcc + (card.metadata[key]?.totals?.subtotal?.amount || 0), 0)
|
||||
);
|
||||
}, 0);
|
||||
};
|
||||
|
||||
const formatValue = (value, type) => {
|
||||
if (type === StatisticType.JOBS) {
|
||||
return value.toFixed(0);
|
||||
@@ -87,9 +94,15 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
||||
const totalAmountInProduction = useMemo(() => {
|
||||
if (!cardSettings.totalAmountInProduction) return null;
|
||||
const total = calculateTotalAmount(data, "job_totals");
|
||||
return parseFloat(total.toFixed(2));
|
||||
return total.toFormat("$0,0.00");
|
||||
}, [data, cardSettings.totalAmountInProduction]);
|
||||
|
||||
const totalAmountOnBoard = useMemo(() => {
|
||||
if (!reducerData || !cardSettings.totalAmountOnBoard) return null;
|
||||
const total = calculateReducerTotalAmount(reducerData.lanes, "job_totals");
|
||||
return total.toFormat("$0,0.00");
|
||||
}, [reducerData, cardSettings.totalAmountOnBoard]);
|
||||
|
||||
const totalHrsOnBoard = useMemo(() => {
|
||||
if (!reducerData || !cardSettings.totalHrsOnBoard) return null;
|
||||
const total =
|
||||
@@ -118,12 +131,6 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
||||
[reducerData, cardSettings.jobsOnBoard]
|
||||
);
|
||||
|
||||
const totalAmountOnBoard = useMemo(() => {
|
||||
if (!reducerData || !cardSettings.totalAmountOnBoard) return null;
|
||||
const total = calculateReducerTotalAmount(reducerData.lanes, "job_totals");
|
||||
return parseFloat(total.toFixed(2));
|
||||
}, [reducerData, cardSettings.totalAmountOnBoard]);
|
||||
|
||||
const tasksInProduction = useMemo(() => {
|
||||
if (!data || !cardSettings.tasksInProduction) return null;
|
||||
return data.reduce((acc, item) => acc + (item.tasks_aggregate?.aggregate?.count || 0), 0);
|
||||
@@ -191,7 +198,6 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
||||
<Statistic
|
||||
title={t(`production.statistics.${stat.label}`)}
|
||||
value={formatValue(stat.value, stat.type)}
|
||||
prefix={stat.type === StatisticType.AMOUNT ? t("production.statistics.currency_symbol") : undefined}
|
||||
suffix={
|
||||
stat.type === StatisticType.HOURS
|
||||
? t("production.statistics.hours")
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Button, Dropdown, Input, Space, Statistic, Table } from "antd";
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import { PageHeader } from "@ant-design/pro-layout";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Button, Dropdown, Input, Space, Statistic, Table } from "antd";
|
||||
import _ from "lodash";
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import ReactDragListView from "react-drag-listview";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import Prompt from "../../utils/prompt.js";
|
||||
import AlertComponent from "../alert/alert.component.jsx";
|
||||
import ProductionListColumnsAdd from "../production-list-columns/production-list-columns.add.component";
|
||||
import ProductionListColumns from "../production-list-columns/production-list-columns.data";
|
||||
import ProductionListDetail from "../production-list-detail/production-list-detail.component";
|
||||
import { ProductionListConfigManager } from "./production-list-config-manager.component.jsx";
|
||||
import ProductionListPrint from "./production-list-print.component";
|
||||
import ResizeableTitle from "./production-list-table.resizeable.component";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import Prompt from "../../utils/prompt.js";
|
||||
import _ from "lodash";
|
||||
import AlertComponent from "../alert/alert.component.jsx";
|
||||
import { ProductionListConfigManager } from "./production-list-config-manager.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -43,7 +43,7 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
const initialStateRef = useRef(
|
||||
(bodyshop.production_config &&
|
||||
bodyshop.production_config.find((p) => p.name === defaultView)?.columns.tableState) ||
|
||||
bodyshop.production_config[0]?.columns.tableState || {
|
||||
(bodyshop.production_config && bodyshop.production_config[0]?.columns.tableState) || {
|
||||
sortedInfo: {},
|
||||
filteredInfo: { text: "" }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Button, Card, DatePicker, Form, Popover, Radio, Space } from "antd";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -7,10 +6,12 @@ import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import DatePIckerRanges from "../../utils/DatePickerRanges";
|
||||
import dayjs from "../../utils/day";
|
||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectTechnician,
|
||||
technician: selectTechnician
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
@@ -18,7 +19,7 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TechJobPrintTickets);
|
||||
|
||||
export function TechJobPrintTickets({ technician, event, attendacePrint }) {
|
||||
export function TechJobPrintTickets({ bodyshop, technician, event, attendacePrint }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -57,7 +58,8 @@ export function TechJobPrintTickets({ technician, event, attendacePrint }) {
|
||||
subject:
|
||||
attendacePrint === true ? Templates.attendance_employee.subject : Templates.timetickets_employee.subject
|
||||
},
|
||||
values.sendby // === "email" ? "e" : "p"
|
||||
values.sendby,
|
||||
bodyshop
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EditFilled, SyncOutlined } from "@ant-design/icons";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Button, Card, Checkbox, Space, Table } from "antd";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -10,10 +10,10 @@ import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectAuthLevel, selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { onlyUnique } from "../../utils/arrayHelper";
|
||||
import dayjs from "../../utils/day";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import RbacWrapper, { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
||||
import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -165,7 +165,7 @@ export function TimeTicketList({
|
||||
key: "memo",
|
||||
sorter: (a, b) => alphaSort(a.memo, b.memo),
|
||||
sortOrder: state.sortedInfo.columnKey === "memo" && state.sortedInfo.order,
|
||||
render: (text, record) => (record.clockon || record.clockoff ? t(record.memo) : record.memo)
|
||||
render: (text, record) => (record.memo?.startsWith("timetickets.labels") ? t(record.memo) : record.memo)
|
||||
},
|
||||
...(Enhanced_Payroll.treatment === "on"
|
||||
? [
|
||||
@@ -206,76 +206,98 @@ export function TimeTicketList({
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
]),
|
||||
{
|
||||
title: t("timetickets.fields.created_by"),
|
||||
dataIndex: "created_by",
|
||||
key: "created_by",
|
||||
sorter: (a, b) => alphaSort(a.created_by, b.created_by),
|
||||
sortOrder: state.sortedInfo.columnKey === "created_by" && state.sortedInfo.order,
|
||||
render: (text, record) => record.created_by
|
||||
},
|
||||
// {
|
||||
// title: "Pay",
|
||||
// dataIndex: "pay",
|
||||
// key: "pay",
|
||||
// render: (text, record) =>
|
||||
// Dinero({ amount: Math.round(record.rate * 100) })
|
||||
// .multiply(record.flat_rate ? record.productivehrs : record.actualhrs)
|
||||
// .toFormat("$0.00"),
|
||||
// },
|
||||
{
|
||||
title: t("general.labels.actions"),
|
||||
dataIndex: "actions",
|
||||
key: "actions",
|
||||
render: (text, record) => (
|
||||
<Space wrap>
|
||||
{techConsole && (
|
||||
<TimeTicketEnterButton
|
||||
actions={{ refetch }}
|
||||
context={{ id: record.id, timeticket: record }}
|
||||
disabled={!record.job || disabled}
|
||||
>
|
||||
<EditFilled />
|
||||
</TimeTicketEnterButton>
|
||||
)}
|
||||
{!techConsole && (
|
||||
<RbacWrapper
|
||||
action="timetickets:edit"
|
||||
noauth={() => {
|
||||
return <div />;
|
||||
}}
|
||||
>
|
||||
<TimeTicketEnterButton
|
||||
actions={{ refetch }}
|
||||
context={{
|
||||
id: record.id,
|
||||
timeticket: record
|
||||
}}
|
||||
disabled={
|
||||
HasRbacAccess({
|
||||
bodyshop,
|
||||
authLevel: authLevel,
|
||||
action: "timetickets:editcommitted"
|
||||
}) &&
|
||||
HasRbacAccess({
|
||||
bodyshop,
|
||||
authLevel: authLevel,
|
||||
action: "timetickets:shiftedit"
|
||||
})
|
||||
{
|
||||
title: t("timetickets.fields.created_by"),
|
||||
dataIndex: "created_by",
|
||||
key: "created_by",
|
||||
sorter: (a, b) => alphaSort(a.created_by, b.created_by),
|
||||
sortOrder: state.sortedInfo.columnKey === "created_by" && state.sortedInfo.order,
|
||||
render: (text, record) => record.created_by
|
||||
},
|
||||
// {
|
||||
// title: "Pay",
|
||||
// dataIndex: "pay",
|
||||
// key: "pay",
|
||||
// render: (text, record) =>
|
||||
// Dinero({ amount: Math.round(record.rate * 100) })
|
||||
// .multiply(record.flat_rate ? record.productivehrs : record.actualhrs)
|
||||
// .toFormat("$0.00"),
|
||||
// },
|
||||
{
|
||||
title: t("general.labels.actions"),
|
||||
dataIndex: "actions",
|
||||
key: "actions",
|
||||
render: (text, record) => (
|
||||
<Space wrap>
|
||||
{techConsole && (
|
||||
<TimeTicketEnterButton
|
||||
actions={{ refetch }}
|
||||
context={{ id: record.id, timeticket: record }}
|
||||
disabled={!record.job || disabled}
|
||||
>
|
||||
<EditFilled />
|
||||
</TimeTicketEnterButton>
|
||||
)}
|
||||
{!techConsole && (
|
||||
<RbacWrapper
|
||||
action="timetickets:edit"
|
||||
noauth={() => {
|
||||
return <div />;
|
||||
}}
|
||||
>
|
||||
<TimeTicketEnterButton
|
||||
actions={{ refetch }}
|
||||
context={{
|
||||
id: record.id,
|
||||
timeticket: record
|
||||
}}
|
||||
disabled={
|
||||
record.ciecacode
|
||||
? record.committed_at
|
||||
? HasRbacAccess({
|
||||
bodyshop,
|
||||
authLevel: authLevel,
|
||||
action: "timetickets:editcommitted"
|
||||
}) &&
|
||||
HasRbacAccess({
|
||||
bodyshop,
|
||||
authLevel: authLevel,
|
||||
action: "timetickets:edit"
|
||||
})
|
||||
: HasRbacAccess({
|
||||
bodyshop,
|
||||
authLevel: authLevel,
|
||||
action: "timetickets:edit"
|
||||
})
|
||||
: record.committed_at
|
||||
? HasRbacAccess({
|
||||
bodyshop,
|
||||
authLevel: authLevel,
|
||||
action: "timetickets:editcommitted"
|
||||
}) &&
|
||||
HasRbacAccess({
|
||||
bodyshop,
|
||||
authLevel: authLevel,
|
||||
action: "timetickets:shiftedit"
|
||||
})
|
||||
: HasRbacAccess({
|
||||
bodyshop,
|
||||
authLevel: authLevel,
|
||||
action: "timetickets:shiftedit"
|
||||
})
|
||||
? disabled
|
||||
: !record.jobid
|
||||
}
|
||||
>
|
||||
<EditFilled />
|
||||
</TimeTicketEnterButton>
|
||||
</RbacWrapper>
|
||||
)}
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
}
|
||||
>
|
||||
<EditFilled />
|
||||
</TimeTicketEnterButton>
|
||||
</RbacWrapper>
|
||||
)}
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useLazyQuery } from "@apollo/client";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Form, Input, InputNumber, Select, Switch } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -7,8 +8,10 @@ import { createStructuredSelector } from "reselect";
|
||||
import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries";
|
||||
import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component";
|
||||
import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import {
|
||||
default as DateTimePicker,
|
||||
default as FormDateTimePicker
|
||||
} from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
||||
import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component";
|
||||
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
|
||||
@@ -16,7 +19,6 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
||||
import TimeTicketList from "../time-ticket-list/time-ticket-list.component";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -69,13 +71,7 @@ export function TimeTicketModalComponent({
|
||||
};
|
||||
|
||||
const MemoInput = ({ value, ...props }) => {
|
||||
return (
|
||||
<Input
|
||||
value={value?.startsWith("timetickets.") ? t(value) : value}
|
||||
{...props}
|
||||
disabled={value?.startsWith("timetickets.") || disabled}
|
||||
/>
|
||||
);
|
||||
return <Input value={value?.startsWith("timetickets.labels") ? t(value) : value} {...props} />;
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -333,7 +329,9 @@ export function LaborAllocationContainer({ jobid, loading, lineTicketData, hideT
|
||||
timetickets={lineTicketData.timetickets}
|
||||
adjustments={lineTicketData.jobs_by_pk.lbr_adjustments}
|
||||
/>
|
||||
{!hideTimeTickets && <TimeTicketList loading={loading} timetickets={lineTicketData.timetickets} techConsole />}
|
||||
{!hideTimeTickets && (
|
||||
<TimeTicketList loading={loading} timetickets={jobid ? lineTicketData.timetickets : []} techConsole />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export default function TimeTicketShiftActive({ timetickets, refetch, isTechCons
|
||||
renderItem={(ticket) => (
|
||||
<List.Item>
|
||||
<Card
|
||||
title={t(ticket.memo)}
|
||||
title={ticket.memo?.startsWith("timetickets.labels") ? t(ticket.memo) : ticket.memo}
|
||||
actions={[
|
||||
<TechClockOffButton
|
||||
jobId={ticket.jobid}
|
||||
|
||||
@@ -31,7 +31,7 @@ export function UpdateAlert({ updateAvailable }) {
|
||||
() => {
|
||||
r.update();
|
||||
},
|
||||
10 * 60 * 1000
|
||||
30 * 60 * 1000
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
|
||||
import RbacWrapperComponent from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||
import TechLookupJobsList from "../../components/tech-lookup-jobs-list/tech-lookup-jobs-list.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import TechLookupJobsDrawer from "../../components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component";
|
||||
|
||||
export default function TechLookupContainer() {
|
||||
const { t } = useTranslation();
|
||||
@@ -20,6 +21,7 @@ export default function TechLookupContainer() {
|
||||
return (
|
||||
<div>
|
||||
<RbacWrapperComponent action="jobs:list-active">
|
||||
<TechLookupJobsDrawer />
|
||||
<TechLookupJobsList />
|
||||
</RbacWrapperComponent>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,6 @@ import ErrorBoundary from "../../components/error-boundary/error-boundary.compon
|
||||
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
|
||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||
import TechHeader from "../../components/tech-header/tech-header.component";
|
||||
import TechLookupJobsDrawer from "../../components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component";
|
||||
import TechSider from "../../components/tech-sider/tech-sider.component";
|
||||
import UpdateAlert from "../../components/update-alert/update-alert.component";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
@@ -68,7 +67,7 @@ export function TechPage({ technician }) {
|
||||
<Layout>
|
||||
<UpdateAlert />
|
||||
<TechHeader />
|
||||
<TechLookupJobsDrawer />
|
||||
|
||||
<TaskUpsertModalContainer />
|
||||
<Content className="tech-content-container">
|
||||
<ErrorBoundary>
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
signInWithEmailAndPassword,
|
||||
signOut
|
||||
} from "firebase/auth";
|
||||
import { doc, getDoc, setDoc } from "firebase/firestore";
|
||||
import { arrayUnion, doc, getDoc, setDoc, updateDoc } from "firebase/firestore";
|
||||
import { getToken } from "firebase/messaging";
|
||||
import i18next from "i18next";
|
||||
import LogRocket from "logrocket";
|
||||
@@ -48,6 +48,7 @@ import {
|
||||
validatePasswordResetSuccess
|
||||
} from "./user.actions";
|
||||
import UserActionTypes from "./user.types";
|
||||
import cleanAxios from "../../utils/CleanAxios";
|
||||
|
||||
const fpPromise = FingerprintJS.load();
|
||||
|
||||
@@ -177,10 +178,24 @@ export function* setInstanceIdSaga({ payload: uid }) {
|
||||
// Get the visitor identifier when you need it.
|
||||
const fp = yield fpPromise;
|
||||
const result = yield fp.get();
|
||||
yield setDoc(userInstanceRef, {
|
||||
timestamp: new Date(),
|
||||
fingerprint: result.visitorId
|
||||
});
|
||||
const res = yield cleanAxios.get("https://api.ipify.org/?format=json");
|
||||
const udoc = yield getDoc(userInstanceRef);
|
||||
|
||||
if (!udoc.data()) {
|
||||
yield setDoc(userInstanceRef, {
|
||||
timestamp: new Date(),
|
||||
fingerprint: result.visitorId,
|
||||
//totalFingerprint: result,
|
||||
ip: [res.data.ip]
|
||||
});
|
||||
} else {
|
||||
yield updateDoc(userInstanceRef, {
|
||||
timestamp: new Date(),
|
||||
fingerprint: result.visitorId,
|
||||
//totalFingerprint: result,
|
||||
ip: arrayUnion(res.data.ip)
|
||||
});
|
||||
}
|
||||
|
||||
yield put(setLocalFingerprint(result.visitorId));
|
||||
yield delay(5 * 60 * 1000);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
version: 2
|
||||
endpoint: https://db.dev.bodyshop.app
|
||||
endpoint: https://db.dev.imex.online
|
||||
admin_secret: Dev-BodyShopApp!
|
||||
metadata_directory: metadata
|
||||
actions:
|
||||
|
||||
@@ -1,3 +1,27 @@
|
||||
- name: AutoHouse Data Pump
|
||||
webhook: '{{HASURA_API_URL}}/data/ah'
|
||||
schedule: 0 6 * * *
|
||||
include_in_metadata: true
|
||||
payload: {}
|
||||
headers:
|
||||
- name: x-imex-auth
|
||||
value_from_env: DATAPUMP_AUTH
|
||||
- name: Claimscorp Data Pump
|
||||
webhook: '{{HASURA_API_URL}}/data/cc'
|
||||
schedule: 30 6 * * *
|
||||
include_in_metadata: true
|
||||
payload: {}
|
||||
headers:
|
||||
- name: x-imex-auth
|
||||
value_from_env: DATAPUMP_AUTH
|
||||
- name: Kaizen Data Pump
|
||||
webhook: '{{HASURA_API_URL}}/data/kaizen'
|
||||
schedule: 30 5 * * *
|
||||
include_in_metadata: true
|
||||
payload: {}
|
||||
headers:
|
||||
- name: x-imex-auth
|
||||
value_from_env: DATAPUMP_AUTH
|
||||
- name: Task Reminders
|
||||
webhook: '{{HASURA_API_URL}}/tasks-remind-handler'
|
||||
schedule: '*/15 * * * *'
|
||||
|
||||
@@ -31,6 +31,12 @@ const ftpSetup = {
|
||||
};
|
||||
|
||||
exports.default = async (req, res) => {
|
||||
// Only process if in production environment.
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
//Query for the List of Bodyshop Clients.
|
||||
logger.log("autohouse-start", "DEBUG", "api", null, null);
|
||||
const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS);
|
||||
|
||||
@@ -31,6 +31,12 @@ const ftpSetup = {
|
||||
};
|
||||
|
||||
exports.default = async (req, res) => {
|
||||
// Only process if in production environment.
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
//Query for the List of Bodyshop Clients.
|
||||
logger.log("claimscorp-start", "DEBUG", "api", null, null);
|
||||
const { bodyshops } = await client.request(queries.GET_CLAIMSCORP_SHOPS);
|
||||
|
||||
@@ -31,6 +31,12 @@ const ftpSetup = {
|
||||
};
|
||||
|
||||
exports.default = async (req, res) => {
|
||||
// Only process if in production environment.
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
//Query for the List of Bodyshop Clients.
|
||||
logger.log("kaizen-start", "DEBUG", "api", null, null);
|
||||
const kaizenShopsIDs = ["SUMMIT", "STRATHMORE", "SUNRIDGE", "SHAW"];
|
||||
@@ -56,8 +62,8 @@ exports.default = async (req, res) => {
|
||||
try {
|
||||
const { jobs, bodyshops_by_pk } = await client.request(queries.KAIZEN_QUERY, {
|
||||
bodyshopid: bodyshop.id,
|
||||
start: start ? moment(start).startOf("hours") : moment().subtract(2, "hours").startOf("hour"),
|
||||
...(end && { end: moment(end).endOf("hours") })
|
||||
start: start ? moment(start).startOf("day") : moment().subtract(5, "days").startOf("day"),
|
||||
...(end && { end: moment(end).endOf("day") })
|
||||
});
|
||||
|
||||
const kaizenObject = {
|
||||
@@ -176,24 +182,19 @@ exports.default = async (req, res) => {
|
||||
} finally {
|
||||
sftp.end();
|
||||
}
|
||||
// sendServerEmail({
|
||||
// subject: `Kaizen Report ${moment().format("MM-DD-YY")}`,
|
||||
// text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
|
||||
// Uploaded: ${JSON.stringify(
|
||||
// allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
|
||||
// null,
|
||||
// 2
|
||||
// )}
|
||||
// `,
|
||||
// });
|
||||
sendServerEmail({
|
||||
subject: `Kaizen Report ${moment().format("MM-DD-YY")}`,
|
||||
text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
|
||||
Uploaded: ${JSON.stringify(
|
||||
allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
|
||||
null,
|
||||
2
|
||||
)}
|
||||
`
|
||||
});
|
||||
res.sendStatus(200);
|
||||
} catch (error) {
|
||||
res.status(200).json(error);
|
||||
sendServerEmail({
|
||||
subject: `Kaizen Report ${moment().format("MM-DD-YY @ HH:mm:ss")}`,
|
||||
text: `Errors: JSON.stringify(error)}
|
||||
All Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}`
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -965,22 +965,17 @@ function CalculateTaxesTotals(job, otherTotals) {
|
||||
}
|
||||
});
|
||||
|
||||
if (job.adjustment_bottom_line) {
|
||||
const subtotal_before_adjustment = subtotal.add(Dinero({ amount: Math.round(job.adjustment_bottom_line * -100) }));
|
||||
const percent_of_adjustment =
|
||||
Math.round(
|
||||
subtotal_before_adjustment.toUnit() /
|
||||
(job.adjustment_bottom_line > 0 ? job.adjustment_bottom_line : job.adjustment_bottom_line * -1)
|
||||
) / 100;
|
||||
|
||||
Object.keys(taxableAmountsByTier).forEach((taxTierKey) => {
|
||||
taxable_adjustment = taxableAmountsByTier[taxTierKey].multiply(percent_of_adjustment);
|
||||
if (job.adjustment_bottom_line > 0) {
|
||||
taxableAmountsByTier[taxTierKey] = taxableAmountsByTier[taxTierKey].add(taxable_adjustment);
|
||||
} else {
|
||||
taxableAmountsByTier[taxTierKey] = taxableAmountsByTier[taxTierKey].subtract(taxable_adjustment);
|
||||
if (job.adjustment_bottom_line && job.adjustment_bottom_line !== 0) {
|
||||
for (let tyCounter = 1; tyCounter <= 5; tyCounter++) {
|
||||
if (IsTrueOrYes(pfp["PAN"][`prt_tx_in${tyCounter}`])) {
|
||||
//This amount is taxable for this type.
|
||||
taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[`ty${tyCounter}Tax`].add(
|
||||
Dinero({
|
||||
amount: Math.round(job.adjustment_bottom_line * 100)
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const remainingTaxableAmounts = taxableAmountsByTier;
|
||||
|
||||
Reference in New Issue
Block a user