Merged in release/2022-06-17 (pull request #518)
release/2022-06-17 Approved-by: Patrick Fic
This commit is contained in:
@@ -15374,6 +15374,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>sizelimit</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
</children>
|
</children>
|
||||||
</folder_node>
|
</folder_node>
|
||||||
<folder_node>
|
<folder_node>
|
||||||
@@ -44929,6 +44950,27 @@
|
|||||||
<folder_node>
|
<folder_node>
|
||||||
<name>signinerror</name>
|
<name>signinerror</name>
|
||||||
<children>
|
<children>
|
||||||
|
<concept_node>
|
||||||
|
<name>auth/user-disabled</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>auth/user-not-found</name>
|
<name>auth/user-not-found</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export const uploadToCloudinary = async (
|
|||||||
//Set variables for getting the signed URL.
|
//Set variables for getting the signed URL.
|
||||||
let timestamp = Math.floor(Date.now() / 1000);
|
let timestamp = Math.floor(Date.now() / 1000);
|
||||||
let public_id = key;
|
let public_id = key;
|
||||||
let tags = `${bodyshop.textid},${
|
let tags = `${bodyshop.imexshopid},${
|
||||||
tagsArray ? tagsArray.map((tag) => `${tag},`) : ""
|
tagsArray ? tagsArray.map((tag) => `${tag},`) : ""
|
||||||
}`;
|
}`;
|
||||||
// let eager = process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS;
|
// let eager = process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS;
|
||||||
|
|||||||
@@ -38,6 +38,12 @@ export function EmailDocumentsComponent({
|
|||||||
nextFetchPolicy: "network-only",
|
nextFetchPolicy: "network-only",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
selectedMedia &&
|
||||||
|
selectedMedia
|
||||||
|
.filter((s) => s.isSelected)
|
||||||
|
.reduce((acc, val) => (acc = acc + val.size), 0)
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{loading && <LoadingSpinner />}
|
{loading && <LoadingSpinner />}
|
||||||
@@ -45,6 +51,12 @@ export function EmailDocumentsComponent({
|
|||||||
{selectedMedia.filter((s) => s.isSelected).length >= 10 ? (
|
{selectedMedia.filter((s) => s.isSelected).length >= 10 ? (
|
||||||
<div style={{ color: "red" }}>{t("messaging.labels.maxtenimages")}</div>
|
<div style={{ color: "red" }}>{t("messaging.labels.maxtenimages")}</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
{selectedMedia &&
|
||||||
|
selectedMedia
|
||||||
|
.filter((s) => s.isSelected)
|
||||||
|
.reduce((acc, val) => (acc = acc + val.size), 0) >= 9961472 ? (
|
||||||
|
<div style={{ color: "red" }}>{t("general.errors.sizelimit")}</div>
|
||||||
|
) : null}
|
||||||
{data && (
|
{data && (
|
||||||
<JobDocumentsGalleryExternal
|
<JobDocumentsGalleryExternal
|
||||||
data={data ? data.documents : []}
|
data={data ? data.documents : []}
|
||||||
|
|||||||
@@ -180,6 +180,23 @@ export function EmailOverlayComponent({
|
|||||||
}
|
}
|
||||||
return e && e.fileList;
|
return e && e.fileList;
|
||||||
}}
|
}}
|
||||||
|
rules={[
|
||||||
|
({ getFieldValue }) => ({
|
||||||
|
validator(rule, value) {
|
||||||
|
const totalSize = value.reduce(
|
||||||
|
(acc, val) => (acc = acc + val.size),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
const limit = 9961472;
|
||||||
|
|
||||||
|
if (totalSize > limit) {
|
||||||
|
return Promise.reject(t("general.errors.sizelimit"));
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Upload.Dragger
|
<Upload.Dragger
|
||||||
beforeUpload={Upload.LIST_IGNORE}
|
beforeUpload={Upload.LIST_IGNORE}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ class ErrorBoundary extends React.Component {
|
|||||||
|
|
||||||
static getDerivedStateFromError(error) {
|
static getDerivedStateFromError(error) {
|
||||||
console.log("ErrorBoundary -> getDerivedStateFromError -> error", error);
|
console.log("ErrorBoundary -> getDerivedStateFromError -> error", error);
|
||||||
|
|
||||||
return { hasErrored: true, error: error };
|
return { hasErrored: true, error: error };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ function JobsDocumentGalleryExternal({
|
|||||||
id: value.id,
|
id: value.id,
|
||||||
type: value.type,
|
type: value.type,
|
||||||
tags: [{ value: value.type, title: value.type }],
|
tags: [{ value: value.type, title: value.type }],
|
||||||
|
size: value.size,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export default function OwnerFindModalContainer({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (modalProps.visible && owner) {
|
if (modalProps.visible && owner) {
|
||||||
const s = OwnerNameDisplayFunction(owner);
|
const s = OwnerNameDisplayFunction(owner, true);
|
||||||
|
|
||||||
setSearchText(s.trim());
|
setSearchText(s.trim());
|
||||||
callSearchowners({ variables: { search: s.trim() } });
|
callSearchowners({ variables: { search: s.trim() } });
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export function OwnerNameDisplay({ bodyshop, ownerObject }) {
|
|||||||
}`.trim();
|
}`.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function OwnerNameDisplayFunction(ownerObject) {
|
export function OwnerNameDisplayFunction(ownerObject, forceFirstLast = false) {
|
||||||
const emptyTest =
|
const emptyTest =
|
||||||
ownerObject.ownr_fn + ownerObject.ownr_ln + ownerObject.ownr_co_nm;
|
ownerObject.ownr_fn + ownerObject.ownr_ln + ownerObject.ownr_co_nm;
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ export function OwnerNameDisplayFunction(ownerObject) {
|
|||||||
|
|
||||||
const rdxStore = store.getState();
|
const rdxStore = store.getState();
|
||||||
|
|
||||||
if (rdxStore.user.bodyshop.last_name_first)
|
if (rdxStore.user.bodyshop.last_name_first && !forceFirstLast)
|
||||||
return `${ownerObject.ownr_ln || ""}, ${ownerObject.ownr_fn || ""} ${
|
return `${ownerObject.ownr_ln || ""}, ${ownerObject.ownr_fn || ""} ${
|
||||||
ownerObject.ownr_co_nm || ""
|
ownerObject.ownr_co_nm || ""
|
||||||
}`.trim();
|
}`.trim();
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import { Card } from "antd";
|
import { Card } from "antd";
|
||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import {
|
import {
|
||||||
Bar,
|
Bar,
|
||||||
CartesianGrid,
|
CartesianGrid,
|
||||||
ComposedChart,
|
ComposedChart,
|
||||||
LabelList,
|
|
||||||
Legend,
|
Legend,
|
||||||
ReferenceLine,
|
ReferenceLine,
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
@@ -69,7 +67,7 @@ export function ScoreboardTicketsBar({ data, bodyshop }) {
|
|||||||
// barSize={20}
|
// barSize={20}
|
||||||
fill={data.colors[idx]}
|
fill={data.colors[idx]}
|
||||||
>
|
>
|
||||||
<LabelList position="top" />
|
{/* <LabelList position="top" /> */}
|
||||||
</Bar>
|
</Bar>
|
||||||
))}
|
))}
|
||||||
</ComposedChart>
|
</ComposedChart>
|
||||||
|
|||||||
@@ -216,7 +216,10 @@ export default function ScoreboardTimeTickets() {
|
|||||||
|
|
||||||
ret2.push(r);
|
ret2.push(r);
|
||||||
});
|
});
|
||||||
|
roundObject(ret);
|
||||||
|
roundObject(totals);
|
||||||
|
roundObject(ret2);
|
||||||
|
console.log(ret);
|
||||||
return {
|
return {
|
||||||
fixed: ret,
|
fixed: ret,
|
||||||
timeperiod: {
|
timeperiod: {
|
||||||
@@ -299,3 +302,18 @@ function getColorArray(num) {
|
|||||||
// }
|
// }
|
||||||
// return result;
|
// return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function roundObject(inputObj) {
|
||||||
|
for (var key of Object.keys(inputObj)) {
|
||||||
|
if (typeof inputObj[key] === "number" && inputObj[key] !== 0) {
|
||||||
|
inputObj[key] =
|
||||||
|
inputObj[key] && inputObj[key].toFixed
|
||||||
|
? inputObj[key].toFixed(1)
|
||||||
|
: inputObj[key]; //Math.round(inputObj[key] * 100) / 100;
|
||||||
|
} else if (Array.isArray(inputObj[key])) {
|
||||||
|
inputObj[key].forEach((item) => roundObject(item));
|
||||||
|
} else if (typeof inputObj[key] === "object") {
|
||||||
|
roundObject(inputObj[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -73,13 +73,13 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
|
|||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={t("scoreboard.labels.lastweek")}
|
title={t("scoreboard.labels.lastweek")}
|
||||||
value={data.totalLastWeek.toFixed(1)}
|
value={data.totalLastWeek}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={t("scoreboard.labels.lastmonth")}
|
title={t("scoreboard.labels.lastmonth")}
|
||||||
value={data.totalLastMonth.toFixed(1)}
|
value={data.totalLastMonth}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -87,13 +87,13 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
|
|||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={t("scoreboard.labels.thisweek")}
|
title={t("scoreboard.labels.thisweek")}
|
||||||
value={data.totalThisWeek.toFixed(1)}
|
value={data.totalThisWeek}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={t("scoreboard.labels.thismonth")}
|
title={t("scoreboard.labels.thismonth")}
|
||||||
value={data.totalThisMonth.toFixed(1)}
|
value={data.totalThisMonth}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -101,7 +101,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
|
|||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={t("scoreboard.labels.totaloverperiod")}
|
title={t("scoreboard.labels.totaloverperiod")}
|
||||||
value={data.totalOverPeriod.toFixed(1)}
|
value={data.totalOverPeriod}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
@@ -146,7 +146,8 @@ const JobRelatedTicketsTable = ({
|
|||||||
title: t("employees.labels.name"),
|
title: t("employees.labels.name"),
|
||||||
dataIndex: "empname",
|
dataIndex: "empname",
|
||||||
key: "empname",
|
key: "empname",
|
||||||
sorter: (a, b) => alphaSort(a.empname, b.empname),
|
sorter: (a, b) =>
|
||||||
|
alphaSort(a.item.employee.last_name, b.item.employee.last_name),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "empname" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "empname" && state.sortedInfo.order,
|
||||||
render: (text, record) =>
|
render: (text, record) =>
|
||||||
@@ -172,7 +173,9 @@ const JobRelatedTicketsTable = ({
|
|||||||
title: t("timetickets.fields.efficiency"),
|
title: t("timetickets.fields.efficiency"),
|
||||||
dataIndex: "total",
|
dataIndex: "total",
|
||||||
key: "total",
|
key: "total",
|
||||||
sorter: (a, b) => a.total - b.total,
|
sorter: (a, b) =>
|
||||||
|
(a.actHrs === 0 || !a.actHrs ? 0 : (a.prodHrs / a.actHrs) * 100) -
|
||||||
|
(b.actHrs === 0 || !b.actHrs ? 0 : (b.prodHrs / b.actHrs) * 100),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "total" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "total" && state.sortedInfo.order,
|
||||||
render: (text, record) =>
|
render: (text, record) =>
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import {
|
|||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
selectInstanceConflict,
|
selectInstanceConflict,
|
||||||
} from "../../redux/user/user.selectors";
|
} from "../../redux/user/user.selectors";
|
||||||
|
import * as Sentry from "@sentry/react";
|
||||||
|
|
||||||
import "./manage.page.styles.scss";
|
import "./manage.page.styles.scss";
|
||||||
|
|
||||||
const ManageRootPage = lazy(() =>
|
const ManageRootPage = lazy(() =>
|
||||||
@@ -407,7 +409,10 @@ export function Manage({ match, conflict, bodyshop }) {
|
|||||||
|
|
||||||
<Content className="content-container">
|
<Content className="content-container">
|
||||||
<PartnerPingComponent />
|
<PartnerPingComponent />
|
||||||
<ErrorBoundary>{PageContent}</ErrorBoundary>
|
<Sentry.ErrorBoundary fallback={<ErrorBoundary />} showDialog>
|
||||||
|
{PageContent}
|
||||||
|
</Sentry.ErrorBoundary>
|
||||||
|
|
||||||
<BackTop />
|
<BackTop />
|
||||||
<Footer>
|
<Footer>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -961,7 +961,8 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"fcm": "You must allow notification permissions to have real time messaging. Click to try again.",
|
"fcm": "You must allow notification permissions to have real time messaging. Click to try again.",
|
||||||
"notfound": "No record was found."
|
"notfound": "No record was found.",
|
||||||
|
"sizelimit": "The selected items exceed the size limit."
|
||||||
},
|
},
|
||||||
"itemtypes": {
|
"itemtypes": {
|
||||||
"contract": "CC Contract",
|
"contract": "CC Contract",
|
||||||
@@ -2668,6 +2669,7 @@
|
|||||||
"users": {
|
"users": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"signinerror": {
|
"signinerror": {
|
||||||
|
"auth/user-disabled": "User account disabled. ",
|
||||||
"auth/user-not-found": "A user with this email does not exist.",
|
"auth/user-not-found": "A user with this email does not exist.",
|
||||||
"auth/wrong-password": "The email and password combination you provided is incorrect."
|
"auth/wrong-password": "The email and password combination you provided is incorrect."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -961,7 +961,8 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"fcm": "",
|
"fcm": "",
|
||||||
"notfound": ""
|
"notfound": "",
|
||||||
|
"sizelimit": ""
|
||||||
},
|
},
|
||||||
"itemtypes": {
|
"itemtypes": {
|
||||||
"contract": "",
|
"contract": "",
|
||||||
@@ -2668,6 +2669,7 @@
|
|||||||
"users": {
|
"users": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"signinerror": {
|
"signinerror": {
|
||||||
|
"auth/user-disabled": "",
|
||||||
"auth/user-not-found": "",
|
"auth/user-not-found": "",
|
||||||
"auth/wrong-password": ""
|
"auth/wrong-password": ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -961,7 +961,8 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"fcm": "",
|
"fcm": "",
|
||||||
"notfound": ""
|
"notfound": "",
|
||||||
|
"sizelimit": ""
|
||||||
},
|
},
|
||||||
"itemtypes": {
|
"itemtypes": {
|
||||||
"contract": "",
|
"contract": "",
|
||||||
@@ -2668,6 +2669,7 @@
|
|||||||
"users": {
|
"users": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"signinerror": {
|
"signinerror": {
|
||||||
|
"auth/user-disabled": "",
|
||||||
"auth/user-not-found": "",
|
"auth/user-not-found": "",
|
||||||
"auth/wrong-password": ""
|
"auth/wrong-password": ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ export default async function RenderTemplate(
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
console.log("PDFREQ", pdfRequest);
|
|
||||||
const pdfRender = await jsreport.renderAsync(pdfRequest);
|
const pdfRender = await jsreport.renderAsync(pdfRequest);
|
||||||
pdf = pdfRender.toDataURI();
|
pdf = pdfRender.toDataURI();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
Must set the environment variables using:
|
Must set the environment variables using:
|
||||||
|
|
||||||
firebase functions:config:set auth.graphql_endpoint="https://bodyshop-dev-db.herokuapp.com/v1/graphql" auth.hasura_secret_admin_key="Dev-BodyShopApp!"
|
firebase functions:config:set auth.graphql_endpoint="https://db.development.bodyshop.app/v1/graphql" auth.hasura_secret_admin_key="Dev-BodyShopApp!"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
version: 2
|
version: 2
|
||||||
endpoint: https://bodyshop-dev-db.herokuapp.com
|
endpoint: https://db.development.bodyshop.app
|
||||||
admin_secret: Dev-BodyShopApp!
|
admin_secret: Dev-BodyShopApp!
|
||||||
metadata_directory: metadata
|
metadata_directory: metadata
|
||||||
actions:
|
actions:
|
||||||
|
|||||||
4940
package-lock.json
generated
4940
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
server.js
16
server.js
@@ -157,7 +157,21 @@ app.post(
|
|||||||
fb.unsubscribe
|
fb.unsubscribe
|
||||||
);
|
);
|
||||||
app.post("/adm/updateuser", fb.validateFirebaseIdToken, fb.updateUser);
|
app.post("/adm/updateuser", fb.validateFirebaseIdToken, fb.updateUser);
|
||||||
|
app.post("/adm/getuser", fb.validateFirebaseIdToken, fb.getUser);
|
||||||
app.post("/adm/createuser", fb.validateFirebaseIdToken, fb.createUser);
|
app.post("/adm/createuser", fb.validateFirebaseIdToken, fb.createUser);
|
||||||
|
const adm = require("./server/admin/adminops");
|
||||||
|
app.post(
|
||||||
|
"/adm/createassociation",
|
||||||
|
fb.validateFirebaseIdToken,
|
||||||
|
fb.validateAdmin,
|
||||||
|
adm.createAssociation
|
||||||
|
);
|
||||||
|
app.post(
|
||||||
|
"/adm/createshop",
|
||||||
|
fb.validateFirebaseIdToken,
|
||||||
|
fb.validateAdmin,
|
||||||
|
adm.createShop
|
||||||
|
);
|
||||||
|
|
||||||
//Stripe Processing
|
//Stripe Processing
|
||||||
var stripe = require("./server/stripe/payment");
|
var stripe = require("./server/stripe/payment");
|
||||||
@@ -216,7 +230,7 @@ server.listen(port, (error) => {
|
|||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
logger.log(
|
logger.log(
|
||||||
`[${process.env.NODE_ENV || "DEVELOPMENT"}] Server running on port ${port}`,
|
`[${process.env.NODE_ENV || "DEVELOPMENT"}] Server running on port ${port}`,
|
||||||
"DEBUG",
|
"INFO",
|
||||||
"api"
|
"api"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
68
server/admin/adminops.js
Normal file
68
server/admin/adminops.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
const _ = require("lodash");
|
||||||
|
const logger = require("../utils/logger");
|
||||||
|
require("dotenv").config({
|
||||||
|
path: path.resolve(
|
||||||
|
process.cwd(),
|
||||||
|
`.env.${process.env.NODE_ENV || "development"}`
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const client = require("../graphql-client/graphql-client").client;
|
||||||
|
|
||||||
|
exports.createAssociation = async (req, res) => {
|
||||||
|
logger.log("admin-create-association", "ADMIN", req.user.email, null, {
|
||||||
|
request: req.body,
|
||||||
|
ioadmin: true,
|
||||||
|
});
|
||||||
|
const { shopid, authlevel, useremail } = req.body;
|
||||||
|
|
||||||
|
const result = await client.request(
|
||||||
|
`mutation INSERT_ASSOCIATION($assoc: associations_insert_input!){
|
||||||
|
insert_associations_one(object:$assoc){
|
||||||
|
id
|
||||||
|
authlevel
|
||||||
|
useremail
|
||||||
|
active
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
{
|
||||||
|
assoc: { shopid, authlevel, useremail, active: false },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
res.json(result);
|
||||||
|
};
|
||||||
|
exports.createShop = async (req, res) => {
|
||||||
|
logger.log("admin-create-shop", "ADMIN", req.user.email, null, {
|
||||||
|
request: req.body,
|
||||||
|
ioadmin: true,
|
||||||
|
});
|
||||||
|
const { bodyshop, ronum } = req.body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await client.request(
|
||||||
|
`mutation INSERT_BODYSHOPS($bs: bodyshops_insert_input!){
|
||||||
|
insert_bodyshops_one(object:$bs){
|
||||||
|
id
|
||||||
|
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
{
|
||||||
|
bs: {
|
||||||
|
...bodyshop,
|
||||||
|
counters: {
|
||||||
|
data: [
|
||||||
|
{ countertype: "ronum", count: ronum },
|
||||||
|
{ countertype: "ihbnum", count: 1 },
|
||||||
|
{ countertype: "paymentnum", count: 1 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
res.json(result);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -665,6 +665,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
|
|||||||
const CreateCosts = (job) => {
|
const CreateCosts = (job) => {
|
||||||
//Create a mapping based on AH Requirements
|
//Create a mapping based on AH Requirements
|
||||||
|
|
||||||
|
//For DMS, the keys in the object below are the CIECA part types.
|
||||||
const billTotalsByCostCenters = job.bills.reduce((bill_acc, bill_val) => {
|
const billTotalsByCostCenters = job.bills.reduce((bill_acc, bill_val) => {
|
||||||
//At the bill level.
|
//At the bill level.
|
||||||
bill_val.billlines.map((line_val) => {
|
bill_val.billlines.map((line_val) => {
|
||||||
@@ -731,7 +732,7 @@ const CreateCosts = (job) => {
|
|||||||
}).multiply(job.job_totals.rates.mash.hours)
|
}).multiply(job.job_totals.rates.mash.hours)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
//Uses CIECA Labor types.
|
||||||
const ticketTotalsByCostCenter = job.timetickets.reduce(
|
const ticketTotalsByCostCenter = job.timetickets.reduce(
|
||||||
(ticket_acc, ticket_val) => {
|
(ticket_acc, ticket_val) => {
|
||||||
//At the invoice level.
|
//At the invoice level.
|
||||||
@@ -750,7 +751,43 @@ const CreateCosts = (job) => {
|
|||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
const defaultCosts = job.bodyshop.md_responsibility_centers.defaults.costs;
|
//CIECA STANDARD MAPPING OBJECT.
|
||||||
|
|
||||||
|
const ciecaObj = {
|
||||||
|
ATS: "ATS",
|
||||||
|
LA1: "LA1",
|
||||||
|
LA2: "LA2",
|
||||||
|
LA3: "LA3",
|
||||||
|
LA4: "LA4",
|
||||||
|
LAA: "LAA",
|
||||||
|
LAB: "LAB",
|
||||||
|
LAD: "LAD",
|
||||||
|
LAE: "LAE",
|
||||||
|
LAF: "LAF",
|
||||||
|
LAG: "LAG",
|
||||||
|
LAM: "LAM",
|
||||||
|
LAR: "LAR",
|
||||||
|
LAS: "LAS",
|
||||||
|
LAU: "LAU",
|
||||||
|
PAA: "PAA",
|
||||||
|
PAC: "PAC",
|
||||||
|
PAG: "PAG",
|
||||||
|
PAL: "PAL",
|
||||||
|
PAM: "PAM",
|
||||||
|
PAN: "PAN",
|
||||||
|
PAO: "PAO",
|
||||||
|
PAP: "PAP",
|
||||||
|
PAR: "PAR",
|
||||||
|
PAS: "PAS",
|
||||||
|
TOW: "TOW",
|
||||||
|
MAPA: "MAPA",
|
||||||
|
MASH: "MASH",
|
||||||
|
PASL: "PASL",
|
||||||
|
};
|
||||||
|
const defaultCosts =
|
||||||
|
job.bodyshop.cdk_dealerid || job.bodyshop.pbs_serialnumber
|
||||||
|
? ciecaObj
|
||||||
|
: job.bodyshop.md_responsibility_centers.defaults.costs;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
PartsTotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => {
|
PartsTotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => {
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ let nodemailer = require("nodemailer");
|
|||||||
let aws = require("aws-sdk");
|
let aws = require("aws-sdk");
|
||||||
const logger = require("../utils/logger");
|
const logger = require("../utils/logger");
|
||||||
const ses = new aws.SES({
|
const ses = new aws.SES({
|
||||||
apiVersion: "2010-12-01",
|
apiVersion: "latest",
|
||||||
|
|
||||||
region: "ca-central-1",
|
region: "ca-central-1",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -43,6 +44,7 @@ exports.sendServerEmail = async function ({ subject, text }) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
logger.log("server-email-failure", "error", null, null, error);
|
logger.log("server-email-failure", "error", null, null, error);
|
||||||
|
res.status(500).json(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
exports.sendTaskEmail = async function ({ to, subject, text, attachments }) {
|
exports.sendTaskEmail = async function ({ to, subject, text, attachments }) {
|
||||||
@@ -153,7 +155,7 @@ exports.sendEmail = async (req, res) => {
|
|||||||
error: err,
|
error: err,
|
||||||
});
|
});
|
||||||
|
|
||||||
res.json({ success: false, error: err });
|
res.status(500).json({ success: false, error: err });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
var admin = require("firebase-admin");
|
var admin = require("firebase-admin");
|
||||||
const logger = require("../utils/logger");
|
const logger = require("../utils/logger");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
const { auth } = require("firebase-admin");
|
||||||
require("dotenv").config({
|
require("dotenv").config({
|
||||||
path: path.resolve(
|
path: path.resolve(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
`.env.${process.env.NODE_ENV || "development"}`
|
`.env.${process.env.NODE_ENV || "development"}`
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
const client = require("../graphql-client/graphql-client").client;
|
||||||
var serviceAccount = require(process.env.FIREBASE_ADMINSDK_JSON);
|
var serviceAccount = require(process.env.FIREBASE_ADMINSDK_JSON);
|
||||||
|
|
||||||
admin.initializeApp({
|
admin.initializeApp({
|
||||||
@@ -19,54 +20,61 @@ exports.admin = admin;
|
|||||||
|
|
||||||
const adminEmail = [
|
const adminEmail = [
|
||||||
"patrick@imex.dev",
|
"patrick@imex.dev",
|
||||||
"patrick@imex.text",
|
//"patrick@imex.test",
|
||||||
"patrick@imex.prod",
|
"patrick@imex.prod",
|
||||||
"patrick@imexsystems.ca",
|
"patrick@imexsystems.ca",
|
||||||
"patrick@thinkimex.com",
|
"patrick@thinkimex.com",
|
||||||
];
|
];
|
||||||
|
|
||||||
exports.createUser = (req, res) => {
|
exports.createUser = async (req, res) => {
|
||||||
logger.log("admin-create-user", "WARN", req.user.email, null, {
|
logger.log("admin-create-user", "ADMIN", req.user.email, null, {
|
||||||
request: req.body,
|
request: req.body,
|
||||||
|
ioadmin: true,
|
||||||
});
|
});
|
||||||
if (!adminEmail.includes(req.user.email)) {
|
|
||||||
logger.log(
|
const { email, displayName, password, shopid, authlevel } = req.body;
|
||||||
"admin-create-user-unauthorized",
|
try {
|
||||||
"ERROR",
|
const userRecord = await admin
|
||||||
req.user.email,
|
.auth()
|
||||||
null,
|
.createUser({ email, displayName, password });
|
||||||
|
|
||||||
|
// See the UserRecord reference doc for the contents of userRecord.
|
||||||
|
|
||||||
|
const result = await client.request(
|
||||||
|
`
|
||||||
|
mutation INSERT_USER($user: users_insert_input!) {
|
||||||
|
insert_users_one(object: $user) {
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
{
|
{
|
||||||
request: req.body,
|
user: {
|
||||||
user: req.user,
|
email,
|
||||||
|
authid: userRecord.uid,
|
||||||
|
associations: {
|
||||||
|
data: [{ shopid, authlevel, active: true }],
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
res.sendStatus(404);
|
|
||||||
}
|
|
||||||
const { email, displayName, password } = req.body;
|
|
||||||
admin
|
|
||||||
.auth()
|
|
||||||
.createUser({ email, displayName, password })
|
|
||||||
.then((userRecord) => {
|
|
||||||
// See the UserRecord reference doc for the contents of userRecord.
|
|
||||||
|
|
||||||
logger.log("admin-update-user-success", "DEBUG", req.user.email, null, {
|
res.json({ userRecord, result });
|
||||||
userRecord,
|
} catch (error) {
|
||||||
});
|
logger.log("admin-update-user-error", "ERROR", req.user.email, null, {
|
||||||
res.json(userRecord);
|
error,
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
logger.log("admin-update-user-error", "ERROR", req.user.email, null, {
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
res.status(500).json(error);
|
|
||||||
});
|
});
|
||||||
|
res.status(500).json(error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.updateUser = (req, res) => {
|
exports.updateUser = (req, res) => {
|
||||||
logger.log("admin-update-user", "WARN", req.user.email, null, {
|
logger.log("admin-update-user", "ADMIN", req.user.email, null, {
|
||||||
request: req.body,
|
request: req.body,
|
||||||
|
ioadmin: true,
|
||||||
});
|
});
|
||||||
if (!adminEmail.includes(req.user.email)) {
|
|
||||||
|
if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) {
|
||||||
logger.log(
|
logger.log(
|
||||||
"admin-update-user-unauthorized",
|
"admin-update-user-unauthorized",
|
||||||
"ERROR",
|
"ERROR",
|
||||||
@@ -78,6 +86,7 @@ exports.updateUser = (req, res) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
res.sendStatus(404);
|
res.sendStatus(404);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
admin
|
admin
|
||||||
@@ -98,8 +107,9 @@ exports.updateUser = (req, res) => {
|
|||||||
.then((userRecord) => {
|
.then((userRecord) => {
|
||||||
// See the UserRecord reference doc for the contents of userRecord.
|
// See the UserRecord reference doc for the contents of userRecord.
|
||||||
|
|
||||||
logger.log("admin-update-user-success", "DEBUG", req.user.email, null, {
|
logger.log("admin-update-user-success", "ADMIN", req.user.email, null, {
|
||||||
userRecord,
|
userRecord,
|
||||||
|
ioadmin: true,
|
||||||
});
|
});
|
||||||
res.json(userRecord);
|
res.json(userRecord);
|
||||||
})
|
})
|
||||||
@@ -111,6 +121,41 @@ exports.updateUser = (req, res) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.getUser = (req, res) => {
|
||||||
|
logger.log("admin-get-user", "ADMIN", req.user.email, null, {
|
||||||
|
request: req.body,
|
||||||
|
ioadmin: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) {
|
||||||
|
logger.log(
|
||||||
|
"admin-update-user-unauthorized",
|
||||||
|
"ERROR",
|
||||||
|
req.user.email,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
request: req.body,
|
||||||
|
user: req.user,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
res.sendStatus(404);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
admin
|
||||||
|
.auth()
|
||||||
|
.getUser(req.body.uid)
|
||||||
|
.then((userRecord) => {
|
||||||
|
res.json(userRecord);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.log("admin-get-user-error", "ERROR", req.user.email, null, {
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
res.status(500).json(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
exports.sendNotification = async (req, res) => {
|
exports.sendNotification = async (req, res) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Send a message to the device corresponding to the provided
|
// Send a message to the device corresponding to the provided
|
||||||
@@ -221,3 +266,35 @@ exports.validateFirebaseIdToken = async (req, res, next) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.validateAdmin = async (req, res, next) => {
|
||||||
|
if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) {
|
||||||
|
logger.log("admin-validation-failed", "ERROR", req.user.email, null, {
|
||||||
|
request: req.body,
|
||||||
|
user: req.user,
|
||||||
|
});
|
||||||
|
res.sendStatus(404);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//Admin claims code.
|
||||||
|
// const uid = "JEqqYlsadwPEXIiyRBR55fflfko1";
|
||||||
|
|
||||||
|
// admin
|
||||||
|
// .auth()
|
||||||
|
// .getUser(uid)
|
||||||
|
// .then((user) => {
|
||||||
|
// console.log(user);
|
||||||
|
// admin.auth().setCustomUserClaims(uid, {
|
||||||
|
// ioadmin: true,
|
||||||
|
// "https://hasura.io/jwt/claims": {
|
||||||
|
// "x-hasura-default-role": "admin",
|
||||||
|
// "x-hasura-allowed-roles": ["admin"],
|
||||||
|
// "x-hasura-user-id": uid,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|||||||
@@ -611,6 +611,8 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshop
|
|||||||
autohouseid
|
autohouseid
|
||||||
md_responsibility_centers
|
md_responsibility_centers
|
||||||
jc_hourly_rates
|
jc_hourly_rates
|
||||||
|
cdk_dealerid
|
||||||
|
pbs_serialnumber
|
||||||
timezone
|
timezone
|
||||||
}
|
}
|
||||||
jobs(where: {_and: [{converted: {_eq: true}}, {updated_at: {_gt: $start}}, {updated_at: {_lte: $end}}, {shopid: {_eq: $bodyshopid}}]}) {
|
jobs(where: {_and: [{converted: {_eq: true}}, {updated_at: {_gt: $start}}, {updated_at: {_lte: $end}}, {shopid: {_eq: $bodyshopid}}]}) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const logger = new graylog2.graylog({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function log(message, type, user, record, object) {
|
function log(message, type, user, record, object) {
|
||||||
if (type !== "ioevent")
|
if (type !== "ioevent" && type !== "DEBUG")
|
||||||
console.log(message, {
|
console.log(message, {
|
||||||
type,
|
type,
|
||||||
env: process.env.NODE_ENV || "development",
|
env: process.env.NODE_ENV || "development",
|
||||||
@@ -13,7 +13,7 @@ function log(message, type, user, record, object) {
|
|||||||
record,
|
record,
|
||||||
...object,
|
...object,
|
||||||
});
|
});
|
||||||
logger.log(message, {
|
logger.log(message, message, {
|
||||||
type,
|
type,
|
||||||
env: process.env.NODE_ENV || "development",
|
env: process.env.NODE_ENV || "development",
|
||||||
user,
|
user,
|
||||||
|
|||||||
Reference in New Issue
Block a user