Merged in release/2022-01-14 (pull request #349)

release/2022-01-14

Approved-by: Patrick Fic
This commit is contained in:
Patrick Fic
2022-01-14 22:41:23 +00:00
14 changed files with 206 additions and 33 deletions

View File

@@ -38007,6 +38007,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>production_by_category</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>production_by_csr</name> <name>production_by_csr</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -18,6 +18,7 @@ import ProductionListLastContacted from "./production-list-columns.lastcontacted
import ProductionListColumnPaintPriority from "./production-list-columns.paintpriority.component"; import ProductionListColumnPaintPriority from "./production-list-columns.paintpriority.component";
import ProductionListColumnNote from "./production-list-columns.productionnote.component"; import ProductionListColumnNote from "./production-list-columns.productionnote.component";
import ProductionListColumnStatus from "./production-list-columns.status.component"; import ProductionListColumnStatus from "./production-list-columns.status.component";
import ProductionListColumnCategory from "./production-list-columns.status.category";
import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component"; import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component";
const r = ({ technician, state, activeStatuses, bodyshop }) => { const r = ({ technician, state, activeStatuses, bodyshop }) => {
@@ -251,6 +252,29 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
state.sortedInfo.columnKey === "status" && state.sortedInfo.order, state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
render: (text, record) => <ProductionListColumnStatus record={record} />, render: (text, record) => <ProductionListColumnStatus record={record} />,
}, },
{
title: i18n.t("jobs.fields.category"),
dataIndex: "category",
key: "category",
ellipsis: true,
filters:
(bodyshop &&
bodyshop.md_categories.map((s) => {
return {
text: s,
value: [s],
};
})) ||
[],
onFilter: (value, record) => value.includes(record.category),
sorter: (a, b) => alphaSort(a.category, b.category),
sortOrder:
state.sortedInfo.columnKey === "category" && state.sortedInfo.order,
render: (text, record) => (
<ProductionListColumnCategory record={record} />
),
},
{ {
title: i18n.t("production.labels.bodyhours"), title: i18n.t("production.labels.bodyhours"),
dataIndex: "labhrs", dataIndex: "labhrs",

View File

@@ -0,0 +1,63 @@
import { useMutation } from "@apollo/client";
import { Dropdown, Menu, Spin } from "antd";
import React, { useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export function ProductionListColumnCategory({ record, bodyshop }) {
const [updateJob] = useMutation(UPDATE_JOB);
const [loading, setLoading] = useState(false);
const handleSetStatus = async (e) => {
logImEXEvent("production_change_status");
setLoading(true);
const { key } = e;
await updateJob({
variables: {
jobId: record.id,
job: {
category: key,
},
},
});
setLoading(false);
};
return (
<Dropdown
overlay={
<Menu
style={{ maxHeight: "200px", overflowY: "auto" }}
onClick={handleSetStatus}
>
{bodyshop.md_categories.map((item) => (
<Menu.Item key={item}>{item}</Menu.Item>
))}
</Menu>
}
trigger={["click"]}
>
<div style={{ width: "100%", height: "19px", cursor: "pointer" }}>
{record.category}
{loading && <Spin />}
</div>
</Dropdown>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductionListColumnCategory);

View File

@@ -88,6 +88,12 @@ export function ProductionListTable({
); );
const handleTableChange = (pagination, filters, sorter) => { const handleTableChange = (pagination, filters, sorter) => {
console.log(
"🚀 ~ file: production-list-table.component.jsx ~ line 91 ~ pagination, filters, sorter",
pagination,
filters,
sorter
);
setState({ setState({
...state, ...state,
filteredInfo: filters, filteredInfo: filters,
@@ -265,6 +271,7 @@ export function ProductionListTable({
columns={columns.map((c, index) => { columns={columns.map((c, index) => {
return { return {
...c, ...c,
filteredValue: state.filteredInfo[c.key] || null,
sortOrder: sortOrder:
state.sortedInfo.columnKey === c.key && state.sortedInfo.order, state.sortedInfo.columnKey === c.key && state.sortedInfo.order,
title: headerItem(c), title: headerItem(c),

View File

@@ -25,15 +25,20 @@ export function ScheduleCalendarHeaderGraph({ bodyshop, loadData }) {
const { ssbuckets } = bodyshop; const { ssbuckets } = bodyshop;
const data = useMemo(() => { const data = useMemo(() => {
return Object.keys(loadData.expectedLoad).map((key) => { return (
const metadataBucket = ssbuckets.filter((b) => b.id === key)[0]; (loadData &&
loadData.expectedLoad &&
Object.keys(loadData.expectedLoad).map((key) => {
const metadataBucket = ssbuckets.filter((b) => b.id === key)[0];
return { return {
bucket: loadData.expectedLoad[key].label, bucket: loadData.expectedLoad[key].label,
current: loadData.expectedLoad[key].count, current: loadData.expectedLoad[key].count,
target: metadataBucket && metadataBucket.target, target: metadataBucket && metadataBucket.target,
}; };
}); })) ||
[]
);
}, [loadData, ssbuckets]); }, [loadData, ssbuckets]);
const popContent = ( const popContent = (

View File

@@ -75,9 +75,17 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<div> <div>
{(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && (
<> <>
<DataLabel label={t("bodyshop.labels.dms.cdk_dealerid")}> {bodyshop.cdk_dealerid && (
{form.getFieldValue("cdk_dealerid")} <DataLabel label={t("bodyshop.labels.dms.cdk_dealerid")}>
</DataLabel> {form.getFieldValue("cdk_dealerid")}
</DataLabel>
)}
{bodyshop.pbs_serialnumber && (
<DataLabel label={t("bodyshop.labels.dms.pbs_serialnumber")}>
{form.getFieldValue("pbs_serialnumber")}
</DataLabel>
)}
<LayoutFormRow> <LayoutFormRow>
<Form.Item <Form.Item
label={t("bodyshop.fields.dms.default_journal")} label={t("bodyshop.fields.dms.default_journal")}
@@ -158,11 +166,11 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
label={t("jobs.fields.dms.payer.control_type")} label={t("jobs.fields.dms.payer.control_type")}
key={`${index}control_type`} key={`${index}control_type`}
name={[field.name, "control_type"]} name={[field.name, "control_type"]}
rules={[ // rules={[
{ // {
required: true, // required: true,
}, // },
]} // ]}
> >
<Select showSearch> <Select showSearch>
<Select.Option value="ro_number"> <Select.Option value="ro_number">

View File

@@ -123,6 +123,7 @@ export const QUERY_EXACT_JOB_IN_PRODUCTION = gql`
ro_number ro_number
ownr_fn ownr_fn
ownr_ln ownr_ln
category
ownr_co_nm ownr_co_nm
v_model_yr v_model_yr
v_model_desc v_model_desc
@@ -193,6 +194,7 @@ export const QUERY_EXACT_JOBS_IN_PRODUCTION = gql`
status status
ro_number ro_number
ownr_fn ownr_fn
category
ownr_ln ownr_ln
ownr_co_nm ownr_co_nm
v_model_yr v_model_yr
@@ -263,6 +265,7 @@ export const QUERY_JOBS_IN_PRODUCTION = gql`
id id
updated_at updated_at
status status
category
ro_number ro_number
ownr_fn ownr_fn
ownr_ln ownr_ln

View File

@@ -2260,6 +2260,7 @@
"parts_not_recieved": "Parts Not Received", "parts_not_recieved": "Parts Not Received",
"payments_by_date": "Payments by Date", "payments_by_date": "Payments by Date",
"payments_by_date_type": "Payments by Date and Type", "payments_by_date_type": "Payments by Date and Type",
"production_by_category": "Production by Category",
"production_by_csr": "Production by CSR", "production_by_csr": "Production by CSR",
"production_by_last_name": "Production by Last Name", "production_by_last_name": "Production by Last Name",
"production_by_repair_status": "Production by Status", "production_by_repair_status": "Production by Status",

View File

@@ -2260,6 +2260,7 @@
"parts_not_recieved": "", "parts_not_recieved": "",
"payments_by_date": "", "payments_by_date": "",
"payments_by_date_type": "", "payments_by_date_type": "",
"production_by_category": "",
"production_by_csr": "", "production_by_csr": "",
"production_by_last_name": "", "production_by_last_name": "",
"production_by_repair_status": "", "production_by_repair_status": "",

View File

@@ -2260,6 +2260,7 @@
"parts_not_recieved": "", "parts_not_recieved": "",
"payments_by_date": "", "payments_by_date": "",
"payments_by_date_type": "", "payments_by_date_type": "",
"production_by_category": "",
"production_by_csr": "", "production_by_csr": "",
"production_by_last_name": "", "production_by_last_name": "",
"production_by_repair_status": "", "production_by_repair_status": "",

View File

@@ -1598,6 +1598,14 @@ export const TemplateList = (type, context) => {
//idtype: "vendor", //idtype: "vendor",
disabled: false, disabled: false,
}, },
production_by_category: {
title: i18n.t("reportcenter.templates.production_by_category"),
description: "",
subject: i18n.t("reportcenter.templates.production_by_category"),
key: "production_by_category",
//idtype: "vendor",
disabled: false,
},
} }
: {}), : {}),
...(!type || type === "special" ...(!type || type === "special"

View File

@@ -68,6 +68,10 @@ app.get("/test", async function (req, res) {
"git rev-parse --short HEAD" "git rev-parse --short HEAD"
); );
logger.log("test-api-status", "DEBUG", "api", { commit }); logger.log("test-api-status", "DEBUG", "api", { commit });
sendEmail.sendServerEmail({
subject: `API Check - ${process.env.NODE_ENV}`,
text: `Server API check has come in. `,
});
res.status(200).send(`OK - ${commit}`); res.status(200).send(`OK - ${commit}`);
}); });

View File

@@ -15,6 +15,7 @@ require("dotenv").config({
let Client = require("ssh2-sftp-client"); let Client = require("ssh2-sftp-client");
const client = require("../graphql-client/graphql-client").client; const client = require("../graphql-client/graphql-client").client;
const { sendServerEmail } = require("../email/sendemail");
const AHDineroFormat = "0.00"; const AHDineroFormat = "0.00";
const AhDateFormat = "MMDDYYYY"; const AhDateFormat = "MMDDYYYY";
@@ -112,16 +113,6 @@ exports.default = async (req, res) => {
}); });
} }
} }
//if (process.env.NODE_ENV !== "production") {
for (const xmlObj of allxmlsToUpload) {
fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml);
}
res.json(allxmlsToUpload);
return;
// }
let sftp = new Client(); let sftp = new Client();
sftp.on("error", (errors) => sftp.on("error", (errors) =>
logger.log("autohouse-sftp-error", "ERROR", "api", null, { logger.log("autohouse-sftp-error", "ERROR", "api", null, {
@@ -155,7 +146,17 @@ exports.default = async (req, res) => {
} finally { } finally {
sftp.end(); sftp.end();
} }
sendServerEmail({
subject: `Autohouse Report ${moment().format("MM-DD-YY")}`,
text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
Uploaded: ${JSON.stringify(
allxmlsToUpload.map((x) => x.filename),
null,
2
)}
`,
});
res.sendStatus(200); res.sendStatus(200);
} catch (error) { } catch (error) {
res.status(200).json(error); res.status(200).json(error);
@@ -682,13 +683,11 @@ const CreateCosts = (job) => {
ticketTotalsByCostCenter[defaultCosts.LAM] || Dinero(), ticketTotalsByCostCenter[defaultCosts.LAM] || Dinero(),
StructuralLaborTotalCost: StructuralLaborTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAS] || Dinero(), ticketTotalsByCostCenter[defaultCosts.LAS] || Dinero(),
ElectricalLaborTotalCost: ElectricalLaborTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAE] || Dinero(), ticketTotalsByCostCenter[defaultCosts.LAE] || Dinero(),
FrameLaborTotalCost: FrameLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAF] || Dinero(),
ticketTotalsByCostCenter[defaultCosts.LAF] || Dinero(), GlassLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAG] || Dinero(),
GlassLaborTotalCost: DetailLaborTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAG] || Dinero(),
DetailLaborTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAD] || Dinero(), ticketTotalsByCostCenter[defaultCosts.LAD] || Dinero(),
PMTotalCost: billTotalsByCostCenters[defaultCosts.MAPA] || Dinero(), PMTotalCost: billTotalsByCostCenters[defaultCosts.MAPA] || Dinero(),
BMTotalCost: billTotalsByCostCenters[defaultCosts.MASH] || Dinero(), BMTotalCost: billTotalsByCostCenters[defaultCosts.MASH] || Dinero(),

View File

@@ -18,6 +18,34 @@ let transporter = nodemailer.createTransport({
SES: { ses, aws }, SES: { ses, aws },
}); });
exports.sendServerEmail = async function ({ subject, text }) {
try {
transporter.sendMail(
{
from: `ImEX Online API - ${process.env.NODE_ENV} <noreply@imex.online>`,
to: ["patrick@snapt.ca"],
subject: subject,
text: text,
ses: {
// optional extra arguments for SendRawEmail
Tags: [
{
Name: "tag_name",
Value: "tag_value",
},
],
},
},
(err, info) => {
console.log(err || info);
}
);
} catch (error) {
console.log(error);
logger.log("server-email-failure", "error", null, null, error);
}
};
exports.sendEmail = async (req, res) => { exports.sendEmail = async (req, res) => {
logger.log("send-email", "DEBUG", req.user.email, null, { logger.log("send-email", "DEBUG", req.user.email, null, {
from: `${req.body.from.name} <${req.body.from.address}>`, from: `${req.body.from.name} <${req.body.from.address}>`,