Merged in release/2023-05-05 (pull request #749)

Release/2023 05 05
This commit is contained in:
Patrick Fic
2023-05-04 19:34:56 +00:00
15 changed files with 187 additions and 37 deletions

View File

@@ -1,3 +1,4 @@
GENERATE_SOURCEMAP=false
REACT_APP_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql REACT_APP_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.imex.online/v1/graphql REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.imex.online/v1/graphql
REACT_APP_GA_CODE=231103507 REACT_APP_GA_CODE=231103507

View File

@@ -0,0 +1,39 @@
import { Col, List, Space, Typography } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
const CardColorLegend = ({ bodyshop, cardSettings }) => {
const { t } = useTranslation();
const data = bodyshop.ssbuckets.map((size) => ({
label: size.label,
color: size.color?.hex ?? "white",
}));
return (
<Col>
<Typography>{t("production.labels.legend")}</Typography>
<List
grid={{
gutter: 16,
}}
dataSource={data}
renderItem={(item) => (
<List.Item>
<Space>
<div
style={{
width: "1.5rem",
aspectRatio: "1/1",
backgroundColor: item.color,
}}
></div>
<div>{item.label}</div>
</Space>
</List.Item>
)}
/>
</Col>
);
};
export default CardColorLegend;

View File

@@ -18,6 +18,28 @@ import moment from "moment";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component"; import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
const cardColor = (ssbuckets, totalHrs) => {
const bucket = ssbuckets.filter(
(bucket) =>
bucket.gte <= totalHrs && (!!bucket.lt ? bucket.lt > totalHrs : true)
)[0];
if (bucket.color) {
return bucket.color.hex;
}
return "";
};
function getContrastYIQ(hexColor) {
const r = parseInt(hexColor.substr(1, 2), 16);
const g = parseInt(hexColor.substr(3, 2), 16);
const b = parseInt(hexColor.substr(5, 2), 16);
const yiq = (r * 299 + g * 587 + b * 114) / 1000;
return yiq >= 128 ? "black" : "white";
}
export default function ProductionBoardCard( export default function ProductionBoardCard(
technician, technician,
card, card,
@@ -54,10 +76,19 @@ export default function ProductionBoardCard(
.isSame(moment(card.scheduled_completion), "day") && .isSame(moment(card.scheduled_completion), "day") &&
"production-completion-soon")); "production-completion-soon"));
const totalHrs =
card.labhrs.aggregate.sum.mod_lb_hrs + card.larhrs.aggregate.sum.mod_lb_hrs;
const bgColor = cardColor(bodyshop.ssbuckets, totalHrs);
return ( return (
<Card <Card
className="react-kanban-card imex-kanban-card" className="react-kanban-card imex-kanban-card"
size="small" size="small"
style={{
backgroundColor: cardSettings && cardSettings.cardcolor && bgColor,
color: getContrastYIQ(bgColor),
}}
title={ title={
<Space> <Space>
<ProductionAlert record={card} key="alert" /> <ProductionAlert record={card} key="alert" />

View File

@@ -104,6 +104,13 @@ export default function ProductionBoardKanbanCardSettings({
> >
<Switch /> <Switch />
</Form.Item> </Form.Item>
<Form.Item
valuePropName="checked"
label={t("production.labels.cardcolor")}
name="cardcolor"
>
<Switch />
</Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item <Form.Item

View File

@@ -22,6 +22,7 @@ import ProductionBoardKanbanCardSettings from "./production-board-kanban.card-se
//import "@asseinfo/react-kanban/dist/styles.css"; //import "@asseinfo/react-kanban/dist/styles.css";
import "./production-board-kanban.styles.scss"; import "./production-board-kanban.styles.scss";
import { createBoardData } from "./production-board-kanban.utils.js"; import { createBoardData } from "./production-board-kanban.utils.js";
import CardColorLegend from "../production-board-kanban-card/production-board-kanban-card-color-legend.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
technician: selectTechnician, technician: selectTechnician,
@@ -221,6 +222,7 @@ export function ProductionBoardKanbanComponent({
employeeassignments: true, employeeassignments: true,
scheduled_completion: true, scheduled_completion: true,
stickyheader: false, stickyheader: false,
cardcolor: false,
}; };
return ( return (
@@ -256,6 +258,11 @@ export function ProductionBoardKanbanComponent({
</Space> </Space>
} }
/> />
{cardSettings.cardcolor && (
<CardColorLegend cardSettings={cardSettings} bodyshop={bodyshop} />
)}
<ProductionListDetailComponent jobs={data} /> <ProductionListDetailComponent jobs={data} />
<StickyContainer> <StickyContainer>
<Board <Board

View File

@@ -277,6 +277,15 @@ export default function ShopInfoSchedulingComponent({ form }) {
> >
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item
label={t("bodyshop.fields.ssbuckets.color")}
key={`${index}color`}
name={[field.name, "color"]}
>
<ColorpickerFormItemComponent />
</Form.Item>
<Space wrap> <Space wrap>
<DeleteFilled <DeleteFilled
onClick={() => { onClick={() => {

View File

@@ -508,7 +508,8 @@
"id": "ID", "id": "ID",
"label": "Label", "label": "Label",
"lt": "Less than (hrs)", "lt": "Less than (hrs)",
"target": "Target (count)" "target": "Target (count)",
"color": "Job Color"
}, },
"state": "Province/State", "state": "Province/State",
"state_tax_id": "Provincial/State Tax ID (PST, QST)", "state_tax_id": "Provincial/State Tax ID (PST, QST)",
@@ -2385,7 +2386,9 @@
"sublets": "Sublets", "sublets": "Sublets",
"totalhours": "Total Hrs ", "totalhours": "Total Hrs ",
"touchtime": "T/T", "touchtime": "T/T",
"viewname": "View Name" "viewname": "View Name",
"legend": "Legend:",
"cardcolor": "Card Colors"
}, },
"successes": { "successes": {
"removed": "Job removed from production." "removed": "Job removed from production."

View File

@@ -1,15 +1,15 @@
import { gql } from "@apollo/client"; import { gql } from "@apollo/client";
import { notification } from "antd"; import { notification } from "antd";
import axios from "axios";
import jsreport from "@jsreport/browser-client"; import jsreport from "@jsreport/browser-client";
import _ from "lodash"; import _ from "lodash";
import moment from "moment"; import moment from "moment";
import { auth } from "../firebase/firebase.utils"; //import { auth } from "../firebase/firebase.utils";
import { setEmailOptions } from "../redux/email/email.actions"; import { setEmailOptions } from "../redux/email/email.actions";
import { store } from "../redux/store"; import { store } from "../redux/store";
import client from "../utils/GraphQLClient"; import client from "../utils/GraphQLClient";
import { TemplateList } from "./TemplateConstants"; import { TemplateList } from "./TemplateConstants";
import cleanAxios from "./CleanAxios";
import axios from "axios";
const server = process.env.REACT_APP_REPORTS_SERVER_URL; const server = process.env.REACT_APP_REPORTS_SERVER_URL;
jsreport.serverUrl = server; jsreport.serverUrl = server;
@@ -26,10 +26,14 @@ export default async function RenderTemplate(
if (window.jsr3) { if (window.jsr3) {
jsreport.serverUrl = "https://reports3.test.imex.online/"; jsreport.serverUrl = "https://reports3.test.imex.online/";
} }
const jsrAuth = (await axios.post("/utils/jsr")).data;
console.log("🚀 ~ file: RenderTemplate.js:30 ~ jsrAuth:", jsrAuth);
jsreport.headers["Authorization"] = jsrAuth;
//Query assets that match the template name. Must be in format <<templateName>>.query //Query assets that match the template name. Must be in format <<templateName>>.query
let { contextData, useShopSpecificTemplate } = await fetchContextData( let { contextData, useShopSpecificTemplate } = await fetchContextData(
templateObject templateObject,
jsrAuth
); );
const { ignoreCustomMargins } = Templates[templateObject.name]; const { ignoreCustomMargins } = Templates[templateObject.name];
@@ -137,11 +141,15 @@ export async function RenderTemplates(
//Query assets that match the template name. Must be in format <<templateName>>.query //Query assets that match the template name. Must be in format <<templateName>>.query
let unsortedTemplatesAndData = []; let unsortedTemplatesAndData = [];
let proms = []; let proms = [];
const jsrAuth = (await axios.post("/utils/jsr")).data;
jsreport.headers["Authorization"] = jsrAuth;
templateObjects.forEach((template) => { templateObjects.forEach((template) => {
proms.push( proms.push(
(async () => { (async () => {
let { contextData, useShopSpecificTemplate } = await fetchContextData( let { contextData, useShopSpecificTemplate } = await fetchContextData(
template template,
jsrAuth
); );
unsortedTemplatesAndData.push({ unsortedTemplatesAndData.push({
templateObject: template, templateObject: template,
@@ -298,19 +306,22 @@ export const GenerateDocuments = async (templates) => {
await RenderTemplates(templates, bodyshop); await RenderTemplates(templates, bodyshop);
}; };
const fetchContextData = async (templateObject) => { const fetchContextData = async (templateObject, jsrAuth) => {
const bodyshop = store.getState().user.bodyshop; const bodyshop = store.getState().user.bodyshop;
jsreport.headers["Authorization"] = // jsreport.headers["Authorization"] =
"Bearer " + (await auth.currentUser.getIdToken()); // "Bearer " + (await auth.currentUser.getIdToken());
const folders = await axios.get(`${server}/odata/folders`); const folders = await cleanAxios.get(`${server}/odata/folders`, {
headers: { Authorization: jsrAuth },
});
const shopSpecificFolder = folders.data.value.find( const shopSpecificFolder = folders.data.value.find(
(f) => f.name === bodyshop.imexshopid (f) => f.name === bodyshop.imexshopid
); );
const jsReportQueries = await axios.get( const jsReportQueries = await cleanAxios.get(
`${server}/odata/assets?$filter=name eq '${templateObject.name}.query'` `${server}/odata/assets?$filter=name eq '${templateObject.name}.query'`,
{ headers: { Authorization: jsrAuth } }
); );
let templateQueryToExecute; let templateQueryToExecute;

View File

@@ -694,6 +694,9 @@
num_retries: 3 num_retries: 3
timeout_sec: 60 timeout_sec: 60
webhook_from_env: HASURA_API_URL webhook_from_env: HASURA_API_URL
headers:
- name: event-secret
value_from_env: EVENT_SECRET
request_transform: request_transform:
method: POST method: POST
query_params: {} query_params: {}
@@ -4112,6 +4115,9 @@
num_retries: 3 num_retries: 3
timeout_sec: 60 timeout_sec: 60
webhook_from_env: HASURA_API_URL webhook_from_env: HASURA_API_URL
headers:
- name: event-secret
value_from_env: EVENT_SECRET
request_transform: request_transform:
method: POST method: POST
query_params: {} query_params: {}
@@ -4562,6 +4568,9 @@
num_retries: 3 num_retries: 3
timeout_sec: 60 timeout_sec: 60
webhook_from_env: HASURA_API_URL webhook_from_env: HASURA_API_URL
headers:
- name: event-secret
value_from_env: EVENT_SECRET
request_transform: request_transform:
method: POST method: POST
query_params: {} query_params: {}
@@ -5015,6 +5024,9 @@
num_retries: 3 num_retries: 3
timeout_sec: 60 timeout_sec: 60
webhook_from_env: HASURA_API_URL webhook_from_env: HASURA_API_URL
headers:
- name: event-secret
value_from_env: EVENT_SECRET
request_transform: request_transform:
method: POST method: POST
query_params: {} query_params: {}
@@ -5957,6 +5969,9 @@
num_retries: 3 num_retries: 3
timeout_sec: 60 timeout_sec: 60
webhook_from_env: HASURA_API_URL webhook_from_env: HASURA_API_URL
headers:
- name: event-secret
value_from_env: EVENT_SECRET
request_transform: request_transform:
method: POST method: POST
query_params: {} query_params: {}

View File

@@ -123,7 +123,11 @@ app.post(
twilio.webhook({ validate: process.env.NODE_ENV === "PRODUCTION" }), twilio.webhook({ validate: process.env.NODE_ENV === "PRODUCTION" }),
smsStatus.status smsStatus.status
); );
app.post("/sms/markConversationRead", smsStatus.markConversationRead); app.post(
"/sms/markConversationRead",
fb.validateFirebaseIdToken,
smsStatus.markConversationRead
);
var job = require("./server/job/job"); var job = require("./server/job/job");
app.post("/job/totals", fb.validateFirebaseIdToken, job.totals); app.post("/job/totals", fb.validateFirebaseIdToken, job.totals);
@@ -147,11 +151,11 @@ app.post("/scheduling/job", fb.validateFirebaseIdToken, scheduling.job);
var inlineCss = require("./server/render/inlinecss"); var inlineCss = require("./server/render/inlinecss");
app.post("/render/inlinecss", fb.validateFirebaseIdToken, inlineCss.inlinecss); app.post("/render/inlinecss", fb.validateFirebaseIdToken, inlineCss.inlinecss);
app.post( // app.post(
"/notifications/send", // "/notifications/send",
fb.sendNotification // fb.sendNotification
); // );
app.post("/notifications/subscribe", fb.validateFirebaseIdToken, fb.subscribe); app.post("/notifications/subscribe", fb.validateFirebaseIdToken, fb.subscribe);
app.post( app.post(
"/notifications/unsubscribe", "/notifications/unsubscribe",
@@ -188,13 +192,13 @@ app.post(
); );
//Stripe Processing //Stripe Processing
var stripe = require("./server/stripe/payment"); // var stripe = require("./server/stripe/payment");
app.post("/stripe/payment", fb.validateFirebaseIdToken, stripe.payment); // app.post("/stripe/payment", fb.validateFirebaseIdToken, stripe.payment);
app.post( // app.post(
"/stripe/mobilepayment", // "/stripe/mobilepayment",
fb.validateFirebaseIdToken, // fb.validateFirebaseIdToken,
stripe.mobile_payment // stripe.mobile_payment
); // );
//Tech Console //Tech Console
var tech = require("./server/tech/tech"); var tech = require("./server/tech/tech");
@@ -202,7 +206,7 @@ app.post("/tech/login", fb.validateFirebaseIdToken, tech.techLogin);
var utils = require("./server/utils/utils"); var utils = require("./server/utils/utils");
app.post("/utils/time", utils.servertime); app.post("/utils/time", utils.servertime);
app.post("/utils/jsr", fb.validateFirebaseIdToken, utils.jsrAuth);
var qbo = require("./server/accounting/qbo/qbo"); var qbo = require("./server/accounting/qbo/qbo");
app.post("/qbo/authorize", fb.validateFirebaseIdToken, qbo.authorize); app.post("/qbo/authorize", fb.validateFirebaseIdToken, qbo.authorize);
app.get("/qbo/callback", qbo.callback); app.get("/qbo/callback", qbo.callback);
@@ -215,7 +219,7 @@ app.post("/data/ah", data.autohouse);
app.post("/record-handler/arms", data.arms); app.post("/record-handler/arms", data.arms);
var taskHandler = require("./server/tasks/tasks"); var taskHandler = require("./server/tasks/tasks");
app.post("/taskHandler", taskHandler.taskHandler); app.post("/taskHandler", fb.validateFirebaseIdToken, taskHandler.taskHandler);
var mixdataUpload = require("./server/mixdata/mixdata"); var mixdataUpload = require("./server/mixdata/mixdata");
@@ -228,10 +232,10 @@ app.post(
var ioevent = require("./server/ioevent/ioevent"); var ioevent = require("./server/ioevent/ioevent");
app.post("/ioevent", ioevent.default); app.post("/ioevent", ioevent.default);
app.post("/newlog", (req, res) => { // app.post("/newlog", (req, res) => {
const { message, type, user, record, object } = req.body; // const { message, type, user, record, object } = req.body;
logger.log(message, type, user, record, object); // logger.log(message, type, user, record, object);
}); // });
var os = require("./server/opensearch/os-handler"); var os = require("./server/opensearch/os-handler");
app.post( app.post(
@@ -243,9 +247,9 @@ app.post("/search", fb.validateFirebaseIdToken, os.search);
var cdkGetMake = require("./server/cdk/cdk-get-makes"); var cdkGetMake = require("./server/cdk/cdk-get-makes");
app.post("/cdk/getvehicles", fb.validateFirebaseIdToken, cdkGetMake.default); app.post("/cdk/getvehicles", fb.validateFirebaseIdToken, cdkGetMake.default);
app.get("/", async function (req, res) { // app.get("/", async function (req, res) {
res.status(200).send("Access Forbidden."); // res.status(200).send("Access Forbidden.");
}); // });
server.listen(port, (error) => { server.listen(port, (error) => {
if (error) throw error; if (error) throw error;

View File

@@ -50,7 +50,7 @@ async function getEntegralShopData() {
} }
exports.default = async (req, res) => { exports.default = async (req, res) => {
res.sendStatus(200); res.sendStatus(401);
return; return;
//Query for the List of Bodyshop Clients. //Query for the List of Bodyshop Clients.
const job = req.body.event.data.new; const job = req.body.event.data.new;

View File

@@ -40,6 +40,14 @@ exports.default = async (req, res) => {
const specificShopIds = req.body.bodyshopIds; // ['uuid] const specificShopIds = req.body.bodyshopIds; // ['uuid]
const { start, end, skipUpload } = req.body; //YYYY-MM-DD const { start, end, skipUpload } = req.body; //YYYY-MM-DD
if (
!start ||
!moment(start).isValid ||
req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN
) {
res.sendStatus(401);
return;
}
const allxmlsToUpload = []; const allxmlsToUpload = [];
const allErrors = []; const allErrors = [];
try { try {
@@ -772,7 +780,9 @@ const CreateCosts = (job) => {
billTotalsByCostCenters[ billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
] = Dinero({ ] = Dinero({
amount: Math.round((job.mixdata[0] && job.mixdata[0].totalliquidcost || 0) * 100) amount: Math.round(
((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) * 100
),
}); });
} else { } else {
billTotalsByCostCenters[ billTotalsByCostCenters[

View File

@@ -17,7 +17,7 @@ require("dotenv").config({
}); });
async function StatusTransition(req, res) { async function StatusTransition(req, res) {
if (req.headers["event-secret"] !== process.env.EVENT_SECRET) { if (req.headers["event-secret"] !== process.env.EVENT_SECRET) {
res.status(403).send("Unauthorized"); res.status(401).send("Unauthorized");
return; return;
} }
res.sendStatus(200); res.sendStatus(200);

View File

@@ -45,6 +45,10 @@ const getClient = async () => {
}; };
async function OpenSearchUpdateHandler(req, res) { async function OpenSearchUpdateHandler(req, res) {
if (req.headers["event-secret"] !== process.env.EVENT_SECRET) {
res.status(401).send("Unauthorized");
return;
}
try { try {
var osClient = await getClient(); var osClient = await getClient();
// const osClient = new Client({ // const osClient = new Client({

View File

@@ -1,3 +1,12 @@
exports.servertime = (req, res) => { exports.servertime = (req, res) => {
res.status(200).send(new Date()); res.status(200).send(new Date());
}; };
exports.jsrAuth = async (req, res) => {
res.send(
"Basic " +
Buffer.from(
`${process.env.JSR_USER}:${process.env.JSR_PASSWORD}`
).toString("base64")
);
};