Compare commits

...

13 Commits

Author SHA1 Message Date
Patrick Fic
002301c792 Enable autohouse upload again. 2022-01-13 13:42:54 -08:00
Patrick Fic
7ee544b013 Minor PBS Updates. 2022-01-13 10:55:16 -08:00
Patrick Fic
3bc1785351 IO-1627 Add production category report. 2022-01-13 09:19:41 -08:00
Patrick Fic
5ec1e84671 IO-1627 Add category to production. 2022-01-12 12:49:38 -08:00
Patrick Fic
d9fa00e633 IO-1636 Resolve scheduling error 2022-01-12 12:21:30 -08:00
Patrick Fic
5fe8a15a8c IO-1622 Resolve shift clock. 2022-01-12 08:15:16 -08:00
Patrick Fic
70cfbf2f5b IO-1552 Autohouse schema updates. 2022-01-11 13:13:54 -08:00
Patrick Fic
2aa6fdfac5 IO-1622 Add Rates to tech console. 2022-01-11 12:28:15 -08:00
Patrick Fic
e1d5558dac Add RBAC to employees page. 2022-01-11 12:03:09 -08:00
Patrick Fic
061212f915 Merge branch 'master' into release/2022-01-14 2022-01-11 12:01:03 -08:00
Patrick Fic
9714371a36 IO-1632 Resolve missing cieca code on clock out. 2022-01-11 11:39:56 -08:00
Patrick Fic
1c2a64cf50 IO-1632 Update tech console for CDK posting. 2022-01-11 10:12:10 -08:00
Patrick Fic
7d4d97ce4e Merged in release/2022-01-07 (pull request #339)
Release/2022 01 07
2022-01-08 00:43:51 +00:00
18 changed files with 290 additions and 45 deletions

View File

@@ -38007,6 +38007,27 @@
</translation>
</translations>
</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>
<name>production_by_csr</name>
<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 ProductionListColumnNote from "./production-list-columns.productionnote.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";
const r = ({ technician, state, activeStatuses, bodyshop }) => {
@@ -251,6 +252,29 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
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"),
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) => {
console.log(
"🚀 ~ file: production-list-table.component.jsx ~ line 91 ~ pagination, filters, sorter",
pagination,
filters,
sorter
);
setState({
...state,
filteredInfo: filters,
@@ -265,6 +271,7 @@ export function ProductionListTable({
columns={columns.map((c, index) => {
return {
...c,
filteredValue: state.filteredInfo[c.key] || null,
sortOrder:
state.sortedInfo.columnKey === c.key && state.sortedInfo.order,
title: headerItem(c),

View File

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

View File

@@ -7,6 +7,8 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import ShopEmployeesFormComponent from "./shop-employees-form.component";
import ShopEmployeesListComponent from "./shop-employees-list.component";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
@@ -21,11 +23,13 @@ function ShopEmployeesContainer({ bodyshop }) {
return (
<div>
<ShopEmployeesListComponent
employees={data ? data.employees : []}
loading={loading}
/>
<ShopEmployeesFormComponent />
<RbacWrapper action="employees:page">
<ShopEmployeesListComponent
employees={data ? data.employees : []}
loading={loading}
/>
<ShopEmployeesFormComponent />
</RbacWrapper>
</div>
);
}

View File

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

View File

@@ -49,9 +49,13 @@ export function TechClockInComponent({ form, bodyshop, technician }) {
<Select>
{emps &&
emps.rates.map((item) => (
<Select.Option key={item.cost_center}>
<Select.Option key={item.cost_center} value={item.cost_center}>
{item.cost_center === "timetickets.labels.shift"
? t(item.cost_center)
: bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
? t(
`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`
)
: item.cost_center}
</Select.Option>
))}

View File

@@ -36,14 +36,17 @@ export function TechClockInContainer({ technician, bodyshop }) {
clockon: theTime,
jobid: values.jobid,
cost_center: values.cost_center,
ciecacode: Object.keys(
bodyshop.md_responsibility_centers.defaults.costs
).find((key) => {
return (
bodyshop.md_responsibility_centers.defaults.costs[key] ===
values.cost_center
);
}),
ciecacode:
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
? values.cost_center
: Object.keys(
bodyshop.md_responsibility_centers.defaults.costs
).find((key) => {
return (
bodyshop.md_responsibility_centers.defaults.costs[key] ===
values.cost_center
);
}),
},
],
},

View File

@@ -55,6 +55,20 @@ export function TechClockOffButton({
timeticket: {
clockoff: (await axios.post("/utils/time")).data,
...values,
rate: emps && emps.rates.filter(
(r) => r.cost_center === values.cost_center
)[0]?.rate,
ciecacode:
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
? values.cost_center
: Object.keys(
bodyshop.md_responsibility_centers.defaults.costs
).find((key) => {
return (
bodyshop.md_responsibility_centers.defaults.costs[key] ===
values.cost_center
);
}),
},
},
});
@@ -141,6 +155,10 @@ export function TechClockOffButton({
<Select.Option key={item.cost_center}>
{item.cost_center === "timetickets.labels.shift"
? t(item.cost_center)
: bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
? t(
`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`
)
: item.cost_center}
</Select.Option>
))

View File

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

View File

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

View File

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

View File

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

View File

@@ -1598,6 +1598,14 @@ export const TemplateList = (type, context) => {
//idtype: "vendor",
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"

View File

@@ -68,6 +68,10 @@ app.get("/test", async function (req, res) {
"git rev-parse --short HEAD"
);
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}`);
});

View File

@@ -15,6 +15,7 @@ require("dotenv").config({
let Client = require("ssh2-sftp-client");
const client = require("../graphql-client/graphql-client").client;
const { sendServerEmail } = require("../email/sendemail");
const AHDineroFormat = "0.00";
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();
sftp.on("error", (errors) =>
logger.log("autohouse-sftp-error", "ERROR", "api", null, {
@@ -155,7 +146,17 @@ exports.default = async (req, res) => {
} finally {
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);
} catch (error) {
res.status(200).json(error);
@@ -278,10 +279,10 @@ const CreateRepairOrderTag = (job, errorCallback) => {
AssntoMech: null,
AssntoPaint: null,
AssntoDetail: null,
PromiseDate:
(job.scheduled_completion &&
moment(job.scheduled_completion).format(AhDateFormat)) ||
"",
// PromiseDate:
// (job.scheduled_completion &&
// moment(job.scheduled_completion).format(AhDateFormat)) ||
// "",
//InsuranceTargetOut: null,
CarComplete:
(job.actual_completion &&
@@ -312,6 +313,11 @@ const CreateRepairOrderTag = (job, errorCallback) => {
RefinishRate: job.rate_lar || 0,
MechanicalRate: job.rate_lam || 0,
StructuralRate: job.rate_las || 0,
ElectricalRate: job.rate_lae || 0,
FrameRate: job.rate_laf || 0,
GlassRate: job.rate_lag || 0,
DetailRate: job.rate_lad || 0,
LaborMiscRate: 0,
PMRate: job.rate_mapa || 0,
BMRate: job.rate_mash || 0,
TaxRate:
@@ -399,6 +405,13 @@ const CreateRepairOrderTag = (job, errorCallback) => {
RefinishHours: job.job_totals.rates.lar.hours,
MechanicalHours: job.job_totals.rates.lam.hours,
StructuralHours: job.job_totals.rates.las.hours,
ElectricalHours: job.job_totals.rates.lae.hours,
FrameHours: job.job_totals.rates.laf.hours,
GlassHours: job.job_totals.rates.lag.hours,
DetailHours: job.job_totals.rates.lad.hours,
LaborMiscHours: 0,
PartsTotal: Dinero(job.job_totals.parts.parts.total).toFormat(
AHDineroFormat
),
@@ -459,6 +472,29 @@ const CreateRepairOrderTag = (job, errorCallback) => {
),
StructuralLaborTotalCost:
repairCosts.StructuralLaborTotalCost.toFormat(AHDineroFormat),
ElectricalLaborTotal: Dinero(job.job_totals.rates.lae.total).toFormat(
AHDineroFormat
),
ElectricalLaborTotalCost:
repairCosts.ElectricalLaborTotalCost.toFormat(AHDineroFormat),
FrameLaborTotal: Dinero(job.job_totals.rates.laf.total).toFormat(
AHDineroFormat
),
FrameLaborTotalCost:
repairCosts.FrameLaborTotalCost.toFormat(AHDineroFormat),
GlassLaborTotal: Dinero(job.job_totals.rates.lag.total).toFormat(
AHDineroFormat
),
GlassLaborTotalCost:
repairCosts.GlassLaborTotalCost.toFormat(AHDineroFormat),
DetailLaborTotal: Dinero(job.job_totals.rates.lag.total).toFormat(
AHDineroFormat
),
DetailLaborTotalCost:
repairCosts.DetailLaborTotalCost.toFormat(AHDineroFormat),
LaborMiscTotal: 0,
LaborMiscTotalCost: 0,
MiscellaneousChargeTotal: 0,
MiscellaneousChargeTotalCost: 0,
PMTotal: Dinero(job.job_totals.rates.mapa.total).toFormat(
@@ -647,6 +683,12 @@ const CreateCosts = (job) => {
ticketTotalsByCostCenter[defaultCosts.LAM] || Dinero(),
StructuralLaborTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAS] || Dinero(),
ElectricalLaborTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAE] || Dinero(),
FrameLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAF] || Dinero(),
GlassLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAG] || Dinero(),
DetailLaborTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAD] || Dinero(),
PMTotalCost: billTotalsByCostCenters[defaultCosts.MAPA] || Dinero(),
BMTotalCost: billTotalsByCostCenters[defaultCosts.MASH] || Dinero(),
MiscTotalCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(),

View File

@@ -18,6 +18,34 @@ let transporter = nodemailer.createTransport({
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) => {
logger.log("send-email", "DEBUG", req.user.email, null, {
from: `${req.body.from.name} <${req.body.from.address}>`,