@@ -14,7 +14,6 @@ import {
|
|||||||
Typography
|
Typography
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import Dinero from "dinero.js";
|
import Dinero from "dinero.js";
|
||||||
import dayjs from "../../utils/day";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -22,6 +21,7 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { determineDmsType } from "../../pages/dms/dms.container";
|
import { determineDmsType } from "../../pages/dms/dms.container";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import i18n from "../../translations/i18n";
|
import i18n from "../../translations/i18n";
|
||||||
|
import dayjs from "../../utils/day";
|
||||||
import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component";
|
import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component";
|
||||||
import DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.component";
|
import DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.component";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||||
@@ -89,7 +89,7 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
|
|||||||
job.area_of_damage && job.area_of_damage.impact1
|
job.area_of_damage && job.area_of_damage.impact1
|
||||||
? " " +
|
? " " +
|
||||||
t("jobs.labels.dms.damageto", {
|
t("jobs.labels.dms.damageto", {
|
||||||
area_of_damage: (job.area_of_damage && job.area_of_damage.impact1) || "UNKNOWN"
|
area_of_damage: (job.area_of_damage && job.area_of_damage.impact1.padStart(2, "0")) || "UNKNOWN"
|
||||||
})
|
})
|
||||||
: ""
|
: ""
|
||||||
}`.slice(0, 239),
|
}`.slice(0, 239),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useCallback, useState } from "react";
|
|
||||||
import { LockOutlined } from "@ant-design/icons";
|
import { LockOutlined } from "@ant-design/icons";
|
||||||
import { Badge, Card, Col, Collapse, Form, Input, Row, Space, Tooltip } from "antd";
|
import { Badge, Card, Col, Collapse, Form, Input, Row, Space, Tooltip } from "antd";
|
||||||
|
import React, { useCallback, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -12,8 +12,8 @@ import JobCloseRoGuardBills from "./job-close-ro-guard.bills";
|
|||||||
import JobCloseRoGuardPpd from "./job-close-ro-guard.ppd";
|
import JobCloseRoGuardPpd from "./job-close-ro-guard.ppd";
|
||||||
import JobCloseRoGuardProfit from "./job-close-ro-guard.profit";
|
import JobCloseRoGuardProfit from "./job-close-ro-guard.profit";
|
||||||
import "./job-close-ro-guard.styles.scss";
|
import "./job-close-ro-guard.styles.scss";
|
||||||
import JobCloseRoGuardTtLifecycle from "./job-close-ro-guard.tt-lifecycle";
|
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
|
import JobCloseRoGuardTtLifecycle from "./job-close-ro-guard.tt-lifecycle";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import CardTemplate from "./job-detail-cards.template.component";
|
|
||||||
import Car from "../job-damage-visual/job-damage-visual.component";
|
import Car from "../job-damage-visual/job-damage-visual.component";
|
||||||
|
import CardTemplate from "./job-detail-cards.template.component";
|
||||||
|
|
||||||
export default function JobDetailCardsDamageComponent({ loading, data }) {
|
export default function JobDetailCardsDamageComponent({ loading, data }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { area_of_damage } = data;
|
const { area_of_damage } = data;
|
||||||
return (
|
return (
|
||||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.damage")}>
|
<CardTemplate loading={loading} title={t("jobs.labels.cards.damage")}>
|
||||||
{area_of_damage ? <Car dmg1={area_of_damage.impact1} dmg2={area_of_damage.impact2} /> : t("jobs.errors.nodamage")}
|
{area_of_damage ? (
|
||||||
|
<Car
|
||||||
|
dmg1={area_of_damage.impact1 && area_of_damage.impact1.padStart(2, "0")}
|
||||||
|
dmg2={area_of_damage.impact2 && area_of_damage.impact2.padStart(2, "0")}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
t("jobs.errors.nodamage")
|
||||||
|
)}
|
||||||
</CardTemplate>
|
</CardTemplate>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,16 @@ export function JobDetailCardsPartsComponent({ loading, data, jobRO }) {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { joblines_status } = data;
|
const { joblines_status } = data;
|
||||||
|
|
||||||
|
const filteredJobLines = data.joblines.filter(
|
||||||
|
(j) =>
|
||||||
|
j.part_type !== null &&
|
||||||
|
j.part_type !== "PAE" &&
|
||||||
|
j.part_type !== "PAS" &&
|
||||||
|
j.part_type !== "PASL" &&
|
||||||
|
j.part_qty !== 0 &&
|
||||||
|
j.act_price !== 0
|
||||||
|
);
|
||||||
|
//TODO: Correct jobline_statuses view by including the part_qty !== 0 and act_price !== 0
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: t("joblines.fields.line_desc"),
|
title: t("joblines.fields.line_desc"),
|
||||||
@@ -95,7 +105,7 @@ export function JobDetailCardsPartsComponent({ loading, data, jobRO }) {
|
|||||||
<div>
|
<div>
|
||||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.parts")}>
|
<CardTemplate loading={loading} title={t("jobs.labels.cards.parts")}>
|
||||||
<PartsStatusPie joblines_status={joblines_status} />
|
<PartsStatusPie joblines_status={joblines_status} />
|
||||||
<Table key="id" columns={columns} dataSource={data ? data.joblines : []} />
|
<Table key="id" columns={columns} dataSource={filteredJobLines ? filteredJobLines : []} />
|
||||||
</CardTemplate>
|
</CardTemplate>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -189,7 +189,10 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col {...lossColDamage}>
|
<Col {...lossColDamage}>
|
||||||
{job.area_of_damage ? (
|
{job.area_of_damage ? (
|
||||||
<Car dmg1={job.area_of_damage.impact1} dmg2={job.area_of_damage.impact2} />
|
<Car
|
||||||
|
dmg1={job.area_of_damage.impact1 && job.area_of_damage.impact1.padStart(2, "0")}
|
||||||
|
dmg2={job.area_of_damage.impact2 && job.area_of_damage.impact2.padStart(2, "0")}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
t("jobs.errors.nodamage")
|
t("jobs.errors.nodamage")
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import { DeleteFilled } from "@ant-design/icons";
|
import { DeleteFilled } from "@ant-design/icons";
|
||||||
import { Button, Form, Input, InputNumber, Select, Space, Switch, Typography } from "antd";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
|
import { Button, Form, Input, InputNumber, Select, Space, Switch } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import styled from "styled-components";
|
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
|
||||||
import DataLabel from "../data-label/data-label.component";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import styled from "styled-components";
|
||||||
import ShopInfoResponsibilitycentersTaxesComponent from "./shop-info.responsibilitycenters.taxes.component";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
|
import DataLabel from "../data-label/data-label.component";
|
||||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
||||||
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
|
import ShopInfoResponsibilitycentersTaxesComponent from "./shop-info.responsibilitycenters.taxes.component";
|
||||||
|
|
||||||
const SelectorDiv = styled.div`
|
const SelectorDiv = styled.div`
|
||||||
.ant-form-item .ant-select {
|
.ant-form-item .ant-select {
|
||||||
@@ -214,7 +214,6 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
</Select.Option>
|
</Select.Option>
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Space align="center">
|
<Space align="center">
|
||||||
<DeleteFilled
|
<DeleteFilled
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -274,7 +273,6 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
>
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<DeleteFilled
|
<DeleteFilled
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
remove(field.name);
|
remove(field.name);
|
||||||
@@ -386,7 +384,6 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
>
|
>
|
||||||
<Input onBlur={handleBlur} />
|
<Input onBlur={handleBlur} />
|
||||||
</Form.Item> */}
|
</Form.Item> */}
|
||||||
|
|
||||||
{(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && (
|
{(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.dms.dms_acctnumber")}
|
label={t("bodyshop.fields.dms.dms_acctnumber")}
|
||||||
@@ -451,7 +448,6 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
}}
|
}}
|
||||||
</Form.List>
|
</Form.List>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
|
|
||||||
<LayoutFormRow header={t("bodyshop.labels.responsibilitycenters.profits")} id="profits">
|
<LayoutFormRow header={t("bodyshop.labels.responsibilitycenters.profits")} id="profits">
|
||||||
<Form.List name={["md_responsibility_centers", "profits"]}>
|
<Form.List name={["md_responsibility_centers", "profits"]}>
|
||||||
{(fields, { add, remove, move }) => {
|
{(fields, { add, remove, move }) => {
|
||||||
@@ -579,10 +575,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
}}
|
}}
|
||||||
</Form.List>
|
</Form.List>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
|
|
||||||
<SelectorDiv>
|
<SelectorDiv>
|
||||||
{(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && (
|
{(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && (
|
||||||
<>
|
<>
|
||||||
|
<LayoutFormRow id="mappingname" header={t("bodyshop.labels.dms.dms_allocations")}>
|
||||||
<Form.List name={["md_responsibility_centers", "dms_defaults"]}>
|
<Form.List name={["md_responsibility_centers", "dms_defaults"]}>
|
||||||
{(fields, { add, remove, move }) => {
|
{(fields, { add, remove, move }) => {
|
||||||
return (
|
return (
|
||||||
@@ -590,7 +586,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
<Form.Item key={field.key}>
|
<Form.Item key={field.key}>
|
||||||
<div>
|
<div>
|
||||||
<LayoutFormRow id="mappingname">
|
<LayoutFormRow id="mappingname" noDivider={index > 0 ? false : true}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.dms.mappingname")}
|
label={t("bodyshop.fields.dms.mappingname")}
|
||||||
key={`${index}name`}
|
key={`${index}name`}
|
||||||
@@ -616,7 +612,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<LayoutFormRow header={t("bodyshop.labels.defaultcostsmapping")} id="defaultcostsmapping">
|
<LayoutFormRow header={t("bodyshop.labels.dms.costsmapping")} id="costsmapping">
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.responsibilitycenters.ats")}
|
label={t("bodyshop.fields.responsibilitycenters.ats")}
|
||||||
rules={[
|
rules={[
|
||||||
@@ -1401,7 +1397,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<LayoutFormRow header={t("bodyshop.labels.defaultprofitsmapping")}>
|
<LayoutFormRow header={t("bodyshop.labels.dms.profitsmapping")} id="profitsmapping">
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.responsibilitycenters.ats")}
|
label={t("bodyshop.fields.responsibilitycenters.ats")}
|
||||||
rules={[
|
rules={[
|
||||||
@@ -2204,6 +2200,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</Form.List>
|
</Form.List>
|
||||||
|
</LayoutFormRow>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -3720,7 +3717,6 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
</SelectorDiv>
|
</SelectorDiv>
|
||||||
|
|
||||||
<LayoutFormRow header={t("bodyshop.labels.responsibilitycenters.tax_accounts")} id="tax_accounts">
|
<LayoutFormRow header={t("bodyshop.labels.responsibilitycenters.tax_accounts")} id="tax_accounts">
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.responsibilitycenters.federal_tax")}
|
label={t("bodyshop.fields.responsibilitycenters.federal_tax")}
|
||||||
@@ -4354,14 +4350,14 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
)}
|
)}
|
||||||
<Typography.Title level={4}>{t("bodyshop.labels.responsibilitycenters.sales_tax_codes")}</Typography.Title>
|
<LayoutFormRow id="mappingname" header={t("bodyshop.labels.responsibilitycenters.sales_tax_codes")}>
|
||||||
<Form.List name={["md_responsibility_centers", "sales_tax_codes"]}>
|
<Form.List name={["md_responsibility_centers", "sales_tax_codes"]}>
|
||||||
{(fields, { add, remove }) => {
|
{(fields, { add, remove }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
<Form.Item key={field.key}>
|
<Form.Item key={field.key}>
|
||||||
<LayoutFormRow id="sales_tax_codes">
|
<LayoutFormRow id="sales_tax_codes" noDivider={index > 0 ? false : true}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.responsibilitycenters.sales_tax_codes.description")}
|
label={t("bodyshop.fields.responsibilitycenters.sales_tax_codes.description")}
|
||||||
key={`${index}description`}
|
key={`${index}description`}
|
||||||
@@ -4435,6 +4431,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</Form.List>
|
</Form.List>
|
||||||
|
</LayoutFormRow>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -905,7 +905,7 @@ export const QUERY_JOB_CARD_DETAILS = gql`
|
|||||||
}
|
}
|
||||||
|
|
||||||
joblines(
|
joblines(
|
||||||
where: { removed: { _eq: false }, part_type: { _is_null: false, _nin: ["PAE", "PAS", "PASL"] } }
|
where: { removed: { _eq: false } }
|
||||||
order_by: { line_no: asc }
|
order_by: { line_no: asc }
|
||||||
) {
|
) {
|
||||||
id
|
id
|
||||||
|
|||||||
@@ -647,7 +647,10 @@
|
|||||||
"payers": "Payers"
|
"payers": "Payers"
|
||||||
},
|
},
|
||||||
"cdk_dealerid": "CDK Dealer ID",
|
"cdk_dealerid": "CDK Dealer ID",
|
||||||
|
"costsmapping": "Costs Mapping",
|
||||||
|
"dms_allocations": "DMS Allocations",
|
||||||
"pbs_serialnumber": "PBS Serial Number",
|
"pbs_serialnumber": "PBS Serial Number",
|
||||||
|
"profitsmapping": "Profits Mapping",
|
||||||
"title": "DMS"
|
"title": "DMS"
|
||||||
},
|
},
|
||||||
"emaillater": "Email Later",
|
"emaillater": "Email Later",
|
||||||
@@ -2947,6 +2950,8 @@
|
|||||||
"purchases_by_cost_center_summary": "Purchases by Cost Center (Summary)",
|
"purchases_by_cost_center_summary": "Purchases by Cost Center (Summary)",
|
||||||
"purchases_by_date_range_detail": "Purchases by Date - Detail",
|
"purchases_by_date_range_detail": "Purchases by Date - Detail",
|
||||||
"purchases_by_date_range_summary": "Purchases by Date - Summary",
|
"purchases_by_date_range_summary": "Purchases by Date - Summary",
|
||||||
|
"purchases_by_ro_detail_date": "Purchases by RO - Detail",
|
||||||
|
"purchases_by_ro_summary_date": "Purchases by RO - Summary",
|
||||||
"purchases_by_vendor_detailed_date_range": "Purchases By Vendor - Detailed",
|
"purchases_by_vendor_detailed_date_range": "Purchases By Vendor - Detailed",
|
||||||
"purchases_by_vendor_summary_date_range": "Purchases by Vendor - Summary",
|
"purchases_by_vendor_summary_date_range": "Purchases by Vendor - Summary",
|
||||||
"purchases_grouped_by_vendor_detailed": "Purchases Grouped by Vendor - Detailed",
|
"purchases_grouped_by_vendor_detailed": "Purchases Grouped by Vendor - Detailed",
|
||||||
|
|||||||
@@ -647,7 +647,10 @@
|
|||||||
"payers": ""
|
"payers": ""
|
||||||
},
|
},
|
||||||
"cdk_dealerid": "",
|
"cdk_dealerid": "",
|
||||||
|
"costsmapping": "",
|
||||||
|
"dms_allocations": "",
|
||||||
"pbs_serialnumber": "",
|
"pbs_serialnumber": "",
|
||||||
|
"profitsmapping": "",
|
||||||
"title": ""
|
"title": ""
|
||||||
},
|
},
|
||||||
"emaillater": "",
|
"emaillater": "",
|
||||||
@@ -2937,6 +2940,8 @@
|
|||||||
"purchases_by_cost_center_summary": "",
|
"purchases_by_cost_center_summary": "",
|
||||||
"purchases_by_date_range_detail": "",
|
"purchases_by_date_range_detail": "",
|
||||||
"purchases_by_date_range_summary": "",
|
"purchases_by_date_range_summary": "",
|
||||||
|
"purchases_by_ro_detail_date": "",
|
||||||
|
"purchases_by_ro_summary_date": "",
|
||||||
"purchases_by_vendor_detailed_date_range": "",
|
"purchases_by_vendor_detailed_date_range": "",
|
||||||
"purchases_by_vendor_summary_date_range": "",
|
"purchases_by_vendor_summary_date_range": "",
|
||||||
"purchases_grouped_by_vendor_detailed": "",
|
"purchases_grouped_by_vendor_detailed": "",
|
||||||
|
|||||||
@@ -647,7 +647,10 @@
|
|||||||
"payers": ""
|
"payers": ""
|
||||||
},
|
},
|
||||||
"cdk_dealerid": "",
|
"cdk_dealerid": "",
|
||||||
|
"costsmapping": "",
|
||||||
|
"dms_allocations": "",
|
||||||
"pbs_serialnumber": "",
|
"pbs_serialnumber": "",
|
||||||
|
"profitsmapping": "",
|
||||||
"title": ""
|
"title": ""
|
||||||
},
|
},
|
||||||
"emaillater": "",
|
"emaillater": "",
|
||||||
@@ -2937,6 +2940,8 @@
|
|||||||
"purchases_by_cost_center_summary": "",
|
"purchases_by_cost_center_summary": "",
|
||||||
"purchases_by_date_range_detail": "",
|
"purchases_by_date_range_detail": "",
|
||||||
"purchases_by_date_range_summary": "",
|
"purchases_by_date_range_summary": "",
|
||||||
|
"purchases_by_ro_detail_date": "",
|
||||||
|
"purchases_by_ro_summary_date": "",
|
||||||
"purchases_by_vendor_detailed_date_range": "",
|
"purchases_by_vendor_detailed_date_range": "",
|
||||||
"purchases_by_vendor_summary_date_range": "",
|
"purchases_by_vendor_summary_date_range": "",
|
||||||
"purchases_grouped_by_vendor_detailed": "",
|
"purchases_grouped_by_vendor_detailed": "",
|
||||||
|
|||||||
@@ -1081,6 +1081,32 @@ export const TemplateList = (type, context) => {
|
|||||||
},
|
},
|
||||||
group: "purchases"
|
group: "purchases"
|
||||||
},
|
},
|
||||||
|
purchases_by_ro_detail_date: {
|
||||||
|
title: i18n.t("reportcenter.templates.purchases_by_ro_detail_date"),
|
||||||
|
description: "",
|
||||||
|
subject: i18n.t("reportcenter.templates.purchases_by_ro_detail_date"),
|
||||||
|
key: "purchases_by_ro_detail_date",
|
||||||
|
//idtype: "vendor",
|
||||||
|
disabled: false,
|
||||||
|
rangeFilter: {
|
||||||
|
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||||
|
field: i18n.t("jobs.fields.date_invoiced")
|
||||||
|
},
|
||||||
|
group: "purchases"
|
||||||
|
},
|
||||||
|
purchases_by_ro_summary_date: {
|
||||||
|
title: i18n.t("reportcenter.templates.purchases_by_ro_summary_date"),
|
||||||
|
description: "",
|
||||||
|
subject: i18n.t("reportcenter.templates.purchases_by_ro_summary_date"),
|
||||||
|
key: "purchases_by_ro_summary_date",
|
||||||
|
//idtype: "vendor",
|
||||||
|
disabled: false,
|
||||||
|
rangeFilter: {
|
||||||
|
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||||
|
field: i18n.t("jobs.fields.date_invoiced")
|
||||||
|
},
|
||||||
|
group: "purchases"
|
||||||
|
},
|
||||||
job_costing_ro_date_summary: {
|
job_costing_ro_date_summary: {
|
||||||
title: i18n.t("reportcenter.templates.job_costing_ro_date_summary"),
|
title: i18n.t("reportcenter.templates.job_costing_ro_date_summary"),
|
||||||
description: "",
|
description: "",
|
||||||
|
|||||||
10
hasura/migrations/1720712957502_run_sql_migration/down.sql
Normal file
10
hasura/migrations/1720712957502_run_sql_migration/down.sql
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- CREATE OR REPLACE VIEW "public"."joblines_status" AS
|
||||||
|
-- SELECT j.jobid,
|
||||||
|
-- j.status,
|
||||||
|
-- count(1) AS count,
|
||||||
|
-- j.part_type
|
||||||
|
-- FROM joblines j
|
||||||
|
-- WHERE ((j.part_type IS NOT NULL) AND (j.part_type <> 'PAE'::text) AND (j.part_type <> 'PAS'::text) AND (j.part_type <> 'PASL'::text) AND (j.part_qty <> 0) AND (j.act_price <> 0) AND (j.removed IS FALSE))
|
||||||
|
-- GROUP BY j.jobid, j.status, j.part_type;
|
||||||
8
hasura/migrations/1720712957502_run_sql_migration/up.sql
Normal file
8
hasura/migrations/1720712957502_run_sql_migration/up.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
CREATE OR REPLACE VIEW "public"."joblines_status" AS
|
||||||
|
SELECT j.jobid,
|
||||||
|
j.status,
|
||||||
|
count(1) AS count,
|
||||||
|
j.part_type
|
||||||
|
FROM joblines j
|
||||||
|
WHERE ((j.part_type IS NOT NULL) AND (j.part_type <> 'PAE'::text) AND (j.part_type <> 'PAS'::text) AND (j.part_type <> 'PASL'::text) AND (j.part_qty <> 0) AND (j.act_price <> 0) AND (j.removed IS FALSE))
|
||||||
|
GROUP BY j.jobid, j.status, j.part_type;
|
||||||
10
hasura/migrations/1720713022389_run_sql_migration/down.sql
Normal file
10
hasura/migrations/1720713022389_run_sql_migration/down.sql
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- CREATE OR REPLACE VIEW "public"."joblines_status" AS
|
||||||
|
-- SELECT j.jobid,
|
||||||
|
-- j.status,
|
||||||
|
-- count(1) AS count,
|
||||||
|
-- j.part_type
|
||||||
|
-- FROM joblines j
|
||||||
|
-- WHERE ((j.part_type IS NOT NULL) AND (j.part_type <> 'PAE'::text) AND (j.part_type <> 'PAS'::text) AND (j.part_type <> 'PASL'::text) AND (j.part_qty <> (0)::numeric) AND (j.act_price <> (0)::numeric) AND (j.removed IS FALSE))
|
||||||
|
-- GROUP BY j.jobid, j.status, j.part_type;
|
||||||
8
hasura/migrations/1720713022389_run_sql_migration/up.sql
Normal file
8
hasura/migrations/1720713022389_run_sql_migration/up.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
CREATE OR REPLACE VIEW "public"."joblines_status" AS
|
||||||
|
SELECT j.jobid,
|
||||||
|
j.status,
|
||||||
|
count(1) AS count,
|
||||||
|
j.part_type
|
||||||
|
FROM joblines j
|
||||||
|
WHERE ((j.part_type IS NOT NULL) AND (j.part_type <> 'PAE'::text) AND (j.part_type <> 'PAS'::text) AND (j.part_type <> 'PASL'::text) AND (j.part_qty <> (0)::numeric) AND (j.act_price <> (0)::numeric) AND (j.removed IS FALSE))
|
||||||
|
GROUP BY j.jobid, j.status, j.part_type;
|
||||||
@@ -28,6 +28,8 @@
|
|||||||
"better-queue": "^3.8.12",
|
"better-queue": "^3.8.12",
|
||||||
"bluebird": "^3.7.2",
|
"bluebird": "^3.7.2",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
|
"canvas": "^2.11.2",
|
||||||
|
"chart.js": "^4.4.3",
|
||||||
"cloudinary": "^2.2.0",
|
"cloudinary": "^2.2.0",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
|
|||||||
@@ -16,10 +16,37 @@ const { DiscountNotAlreadyCounted } = InstanceManager({
|
|||||||
promanager: "USE_ROME"
|
promanager: "USE_ROME"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
exports.defaultRoute = async function (req, res) {
|
||||||
|
try {
|
||||||
|
CdkBase.createLogEvent(req, "DEBUG", `Received request to calculate allocations for ${req.body.jobid}`);
|
||||||
|
const jobData = await QueryJobData(req, req.BearerToken, req.body.jobid);
|
||||||
|
return res.status(200).json({ data: calculateAllocations(req, jobData) });
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
CdkBase.createLogEvent(req, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`);
|
||||||
|
res.status(500).json({ error: `Error encountered in CdkCalculateAllocations. ${error}` });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
exports.default = async function (socket, jobid) {
|
exports.default = async function (socket, jobid) {
|
||||||
try {
|
try {
|
||||||
CdkBase.createLogEvent(socket, "DEBUG", `Received request to calculate allocations for ${jobid}`);
|
const jobData = await QueryJobData(socket, "Bearer " + socket.handshake.auth.token, jobid);
|
||||||
const job = await QueryJobData(socket, jobid);
|
return calculateAllocations(socket, jobData);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
CdkBase.createLogEvent(socket, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function QueryJobData(connectionData, token, jobid) {
|
||||||
|
CdkBase.createLogEvent(connectionData, "DEBUG", `Querying job data for id ${jobid}`);
|
||||||
|
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
|
||||||
|
const result = await client.setHeaders({ Authorization: token }).request(queries.GET_CDK_ALLOCATIONS, { id: jobid });
|
||||||
|
CdkBase.createLogEvent(connectionData, "TRACE", `Job data query result ${JSON.stringify(result, null, 2)}`);
|
||||||
|
return result.jobs_by_pk;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateAllocations(connectionData, job) {
|
||||||
const { bodyshop } = job;
|
const { bodyshop } = job;
|
||||||
|
|
||||||
const taxAllocations = InstanceManager({
|
const taxAllocations = InstanceManager({
|
||||||
@@ -145,7 +172,7 @@ exports.default = async function (socket, jobid) {
|
|||||||
(d) => d.name === job.dms_allocation
|
(d) => d.name === job.dms_allocation
|
||||||
);
|
);
|
||||||
CdkBase.createLogEvent(
|
CdkBase.createLogEvent(
|
||||||
socket,
|
connectionData,
|
||||||
"DEBUG",
|
"DEBUG",
|
||||||
`Using DMS Allocation ${selectedDmsAllocationConfig && selectedDmsAllocationConfig.name} for cost export.`
|
`Using DMS Allocation ${selectedDmsAllocationConfig && selectedDmsAllocationConfig.name} for cost export.`
|
||||||
);
|
);
|
||||||
@@ -225,10 +252,10 @@ exports.default = async function (socket, jobid) {
|
|||||||
// console.log("NO MASH ACCOUNT FOUND!!");
|
// console.log("NO MASH ACCOUNT FOUND!!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(
|
// console.log(
|
||||||
Number.isInteger(bodyshop?.cdk_configuration?.sendmaterialscosting),
|
// Number.isInteger(bodyshop?.cdk_configuration?.sendmaterialscosting),
|
||||||
typeof Number.isInteger(bodyshop?.cdk_configuration?.sendmaterialscosting)
|
// typeof Number.isInteger(bodyshop?.cdk_configuration?.sendmaterialscosting)
|
||||||
);
|
// );
|
||||||
if (!!bodyshop?.cdk_configuration?.sendmaterialscosting) {
|
if (!!bodyshop?.cdk_configuration?.sendmaterialscosting) {
|
||||||
//Manually send the percentage of the costing.
|
//Manually send the percentage of the costing.
|
||||||
|
|
||||||
@@ -236,34 +263,22 @@ exports.default = async function (socket, jobid) {
|
|||||||
const mapaAccountName = selectedDmsAllocationConfig.costs.MAPA;
|
const mapaAccountName = selectedDmsAllocationConfig.costs.MAPA;
|
||||||
const mapaAccount = bodyshop.md_responsibility_centers.costs.find((c) => c.name === mapaAccountName);
|
const mapaAccount = bodyshop.md_responsibility_centers.costs.find((c) => c.name === mapaAccountName);
|
||||||
if (mapaAccount) {
|
if (mapaAccount) {
|
||||||
if (!costCenterHash[mapaAccountName])
|
if (!costCenterHash[mapaAccountName]) costCenterHash[mapaAccountName] = Dinero();
|
||||||
costCenterHash[mapaAccountName] = Dinero();
|
|
||||||
if (job.bodyshop.use_paint_scale_data === true) {
|
if (job.bodyshop.use_paint_scale_data === true) {
|
||||||
if (job.mixdata.length > 0) {
|
if (job.mixdata.length > 0) {
|
||||||
costCenterHash[mapaAccountName] = costCenterHash[
|
costCenterHash[mapaAccountName] = costCenterHash[mapaAccountName].add(
|
||||||
mapaAccountName
|
|
||||||
].add(
|
|
||||||
Dinero({
|
Dinero({
|
||||||
amount: Math.round(
|
amount: Math.round(((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) * 100)
|
||||||
((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) *
|
|
||||||
100
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
costCenterHash[mapaAccountName] = costCenterHash[
|
costCenterHash[mapaAccountName] = costCenterHash[mapaAccountName].add(
|
||||||
mapaAccountName
|
Dinero(job.job_totals.rates.mapa.total).percentage(bodyshop?.cdk_configuration?.sendmaterialscosting)
|
||||||
].add(
|
|
||||||
Dinero(job.job_totals.rates.mapa.total).percentage(
|
|
||||||
bodyshop?.cdk_configuration?.sendmaterialscosting
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
costCenterHash[mapaAccountName] = costCenterHash[mapaAccountName].add(
|
costCenterHash[mapaAccountName] = costCenterHash[mapaAccountName].add(
|
||||||
Dinero(job.job_totals.rates.mapa.total).percentage(
|
Dinero(job.job_totals.rates.mapa.total).percentage(bodyshop?.cdk_configuration?.sendmaterialscosting)
|
||||||
bodyshop?.cdk_configuration?.sendmaterialscosting
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -289,9 +304,7 @@ exports.default = async function (socket, jobid) {
|
|||||||
// (c) => c.name === mashAccountName
|
// (c) => c.name === mashAccountName
|
||||||
// );
|
// );
|
||||||
|
|
||||||
taxAllocations.state.sale = taxAllocations.state.sale.add(
|
taxAllocations.state.sale = taxAllocations.state.sale.add(Dinero({ amount: Math.round((ca_bc_pvrt || 0) * 100) }));
|
||||||
Dinero({ amount: Math.round((ca_bc_pvrt || 0) * 100) })
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (job.towing_payable && job.towing_payable !== 0) {
|
if (job.towing_payable && job.towing_payable !== 0) {
|
||||||
@@ -361,7 +374,7 @@ exports.default = async function (socket, jobid) {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
CdkBase.createLogEvent(
|
CdkBase.createLogEvent(
|
||||||
socket,
|
connectionData,
|
||||||
"ERROR",
|
"ERROR",
|
||||||
`Error encountered in CdkCalculateAllocations. Unable to find adjustment account. ${error}`
|
`Error encountered in CdkCalculateAllocations. Unable to find adjustment account. ${error}`
|
||||||
);
|
);
|
||||||
@@ -401,18 +414,4 @@ exports.default = async function (socket, jobid) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
CdkBase.createLogEvent(socket, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
async function QueryJobData(socket, jobid) {
|
|
||||||
CdkBase.createLogEvent(socket, "DEBUG", `Querying job data for id ${jobid}`);
|
|
||||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
|
|
||||||
const result = await client
|
|
||||||
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
|
|
||||||
.request(queries.GET_CDK_ALLOCATIONS, { id: jobid });
|
|
||||||
CdkBase.createLogEvent(socket, "TRACE", `Job data query result ${JSON.stringify(result, null, 2)}`);
|
|
||||||
return result.jobs_by_pk;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -244,6 +244,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
|
|||||||
},
|
},
|
||||||
InsuranceCompany: job.ins_co_nm || "",
|
InsuranceCompany: job.ins_co_nm || "",
|
||||||
Claim: job.clm_no || "",
|
Claim: job.clm_no || "",
|
||||||
|
DMSAllocation: job.dms_allocation || "",
|
||||||
Contacts: {
|
Contacts: {
|
||||||
CSR: job.employee_csr_rel
|
CSR: job.employee_csr_rel
|
||||||
? `${
|
? `${
|
||||||
@@ -643,7 +644,8 @@ const CreateJobLines = (joblines) => {
|
|||||||
part_qty: jobline.part_qty,
|
part_qty: jobline.part_qty,
|
||||||
part_price: jobline.act_price,
|
part_price: jobline.act_price,
|
||||||
labor_type: jobline.mod_lbr_ty,
|
labor_type: jobline.mod_lbr_ty,
|
||||||
labor_hours: jobline.mod_lb_hrs
|
labor_hours: jobline.mod_lb_hrs,
|
||||||
|
labor_sale: jobline.lbr_amt
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return repairLines;
|
return repairLines;
|
||||||
@@ -660,7 +662,10 @@ const CreateTimeTickets = (timetickets) => {
|
|||||||
.trim(),
|
.trim(),
|
||||||
productive_hrs: ticket.productivehrs,
|
productive_hrs: ticket.productivehrs,
|
||||||
actual_hrs: ticket.actualhrs,
|
actual_hrs: ticket.actualhrs,
|
||||||
cost_center: ticket.cost_center
|
cost_center: ticket.cost_center,
|
||||||
|
flat_rate: ticket.flat_rate,
|
||||||
|
rate: ticket.rate,
|
||||||
|
ticket_cost: ticket.flat_rate ? ticket.rate * ticket.productive_hrs : ticket.rate * ticket.actual_hrs
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return timeTickets;
|
return timeTickets;
|
||||||
|
|||||||
@@ -1139,6 +1139,7 @@ exports.KAIZEN_QUERY = `query KAIZEN_EXPORT($start: timestamptz, $bodyshopid: uu
|
|||||||
date_open
|
date_open
|
||||||
date_repairstarted
|
date_repairstarted
|
||||||
date_void
|
date_void
|
||||||
|
dms_allocation
|
||||||
employee_body_rel {
|
employee_body_rel {
|
||||||
first_name
|
first_name
|
||||||
last_name
|
last_name
|
||||||
@@ -1184,6 +1185,7 @@ exports.KAIZEN_QUERY = `query KAIZEN_EXPORT($start: timestamptz, $bodyshopid: uu
|
|||||||
}
|
}
|
||||||
db_price
|
db_price
|
||||||
id
|
id
|
||||||
|
lbr_amt
|
||||||
lbr_op
|
lbr_op
|
||||||
line_desc
|
line_desc
|
||||||
line_ind
|
line_ind
|
||||||
|
|||||||
76
server/render/canvas-colors.js
Normal file
76
server/render/canvas-colors.js
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
const borderColors = [
|
||||||
|
"rgba(193, 0, 50, 1)",
|
||||||
|
"rgba(0, 101, 68, 1)",
|
||||||
|
"rgba(0, 45, 98, 1)",
|
||||||
|
"rgba(253, 184, 0, 1)",
|
||||||
|
"rgba(200, 112, 126, 1)",
|
||||||
|
"rgba(228, 142, 88, 1)",
|
||||||
|
"rgba(90, 160, 141, 1)",
|
||||||
|
"rgba(103, 143, 174, 1)",
|
||||||
|
"rgba(192, 136, 99, 1)",
|
||||||
|
"rgba(234, 3, 55, 1)",
|
||||||
|
"rgba(0, 149, 67, 1)",
|
||||||
|
"rgba(0, 81, 155, 1)",
|
||||||
|
"rgba(226, 143, 173, 1)",
|
||||||
|
"rgba(237, 170, 125, 1)",
|
||||||
|
"rgba(76, 146, 177, 1)",
|
||||||
|
"rgba(172, 153, 193, 1)",
|
||||||
|
"rgba(173, 167, 89, 1)",
|
||||||
|
"rgba(254, 197, 222, 1)",
|
||||||
|
"rgba(177, 231, 223, 1)",
|
||||||
|
"rgba(120, 199, 235, 1)",
|
||||||
|
"rgba(239, 180, 193, 1)",
|
||||||
|
"rgba(240, 199, 171, 1)",
|
||||||
|
"rgba(168, 200, 121, 1)",
|
||||||
|
"rgba(150, 177, 208, 1)",
|
||||||
|
"rgba(200, 194, 189, 1)",
|
||||||
|
"rgba(244, 244, 244, 1)",
|
||||||
|
"rgba(255, 99, 132, 1)",
|
||||||
|
"rgba(54, 162, 235, 1)",
|
||||||
|
"rgba(255, 206, 86, 1)",
|
||||||
|
"rgba(75, 192, 192, 1)",
|
||||||
|
"rgba(153, 102, 255, 1)",
|
||||||
|
"rgba(255, 159, 64, 1)",
|
||||||
|
"rgba(170, 183, 184, 1)",
|
||||||
|
];
|
||||||
|
|
||||||
|
const backgroundColors = [
|
||||||
|
'rgba(193, 0, 50, 0.2)',
|
||||||
|
'rgba(0, 101, 68, 0.2)',
|
||||||
|
'rgba(0, 45, 98, 0.2)',
|
||||||
|
'rgba(253, 184, 0, 0.2)',
|
||||||
|
'rgba(200, 112, 126, 0.2)',
|
||||||
|
'rgba(228, 142, 88, 0.2)',
|
||||||
|
'rgba(90, 160, 141, 0.2)',
|
||||||
|
'rgba(103, 143, 174, 0.2)',
|
||||||
|
'rgba(192, 136, 99, 0.2)',
|
||||||
|
'rgba(234, 3, 55, 0.2)',
|
||||||
|
'rgba(0, 149, 67, 0.2)',
|
||||||
|
'rgba(0, 81, 155, 0.2)',
|
||||||
|
'rgba(226, 143, 173, 0.2)',
|
||||||
|
'rgba(237, 170, 125, 0.2)',
|
||||||
|
'rgba(76, 146, 177, 0.2)',
|
||||||
|
'rgba(172, 153, 193, 0.2)',
|
||||||
|
'rgba(173, 167, 89, 0.2)',
|
||||||
|
'rgba(254, 197, 222, 0.2)',
|
||||||
|
'rgba(177, 231, 223, 0.2)',
|
||||||
|
'rgba(120, 199, 235, 0.2)',
|
||||||
|
'rgba(239, 180, 193, 0.2)',
|
||||||
|
'rgba(240, 199, 171, 0.2)',
|
||||||
|
'rgba(168, 200, 121, 0.2)',
|
||||||
|
'rgba(150, 177, 208, 0.2)',
|
||||||
|
'rgba(200, 194, 189, 0.2)',
|
||||||
|
'rgba(244, 244, 244, 0.2)',
|
||||||
|
'rgba(255, 99, 132, 0.2)',
|
||||||
|
'rgba(54, 162, 235, 0.2)',
|
||||||
|
'rgba(255, 206, 86, 0.2)',
|
||||||
|
'rgba(75, 192, 192, 0.2)',
|
||||||
|
'rgba(153, 102, 255, 0.2)',
|
||||||
|
'rgba(255, 159, 64, 0.2)',
|
||||||
|
'rgba(170, 183, 184, 0.2)',
|
||||||
|
];
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
borderColors,
|
||||||
|
backgroundColors,
|
||||||
|
};
|
||||||
92
server/render/canvas-handler.js
Normal file
92
server/render/canvas-handler.js
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
const { createCanvas } = require("canvas");
|
||||||
|
const Chart = require("chart.js/auto");
|
||||||
|
|
||||||
|
const { backgroundColors, borderColors } = require("./canvas-colors");
|
||||||
|
const { isObject, defaultsDeep, isNumber } = require("lodash");
|
||||||
|
|
||||||
|
exports.canvastest = function (req, res) {
|
||||||
|
console.log("Incoming test request.", req);
|
||||||
|
res.status(200).send("OK");
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.canvas = function (req, res) {
|
||||||
|
const { w, h, values, keys, override } = req.body;
|
||||||
|
console.log("Incoming Canvas Request:", w, h, values, keys, override);
|
||||||
|
|
||||||
|
// Gate required values
|
||||||
|
if (!values || !keys) {
|
||||||
|
res.status(400).send("Missing required data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override must be an object if it exists
|
||||||
|
if (override && !isObject(override)) {
|
||||||
|
res.status(400).send("Override must be an object");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the default Width and Height
|
||||||
|
let [width, height] = [500, 275];
|
||||||
|
|
||||||
|
// Allow for custom width and height
|
||||||
|
if (isNumber(w)) {
|
||||||
|
width = w;
|
||||||
|
}
|
||||||
|
if (isNumber(h)) {
|
||||||
|
height = h;
|
||||||
|
}
|
||||||
|
|
||||||
|
const configuration = {
|
||||||
|
type: "doughnut",
|
||||||
|
data: {
|
||||||
|
labels: keys,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
data: values,
|
||||||
|
backgroundColor: backgroundColors,
|
||||||
|
borderColor: borderColors,
|
||||||
|
borderWidth: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
devicePixelRatio: 4,
|
||||||
|
responsive: false,
|
||||||
|
maintainAspectRatio: true,
|
||||||
|
circumference: 180,
|
||||||
|
rotation: -90,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
labels: {
|
||||||
|
boxWidth: 20,
|
||||||
|
font: {
|
||||||
|
family: "'Montserrat'",
|
||||||
|
size: 10,
|
||||||
|
style: "normal",
|
||||||
|
weight: "normal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
position: "left"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// If we have a valid override object, merge it with the default configuration object.
|
||||||
|
// This allows for you to override the default configuration with a custom one.
|
||||||
|
const defaults = () => {
|
||||||
|
if (!override || !isObject(override)) {
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
return defaultsDeep(override, configuration);
|
||||||
|
};
|
||||||
|
|
||||||
|
res.status(200).send(
|
||||||
|
(() => {
|
||||||
|
const canvas = createCanvas(width, height);
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
new Chart(ctx, defaults());
|
||||||
|
return canvas.toDataURL();
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
const express = require("express");
|
const express = require("express");
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const cdkGetMake = require("../cdk/cdk-get-makes");
|
const cdkGetMake = require("../cdk/cdk-get-makes");
|
||||||
|
const cdkCalculateAllocations = require("../cdk/cdk-calculate-allocations");
|
||||||
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware");
|
const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware");
|
||||||
|
|
||||||
router.use(validateFirebaseIdTokenMiddleware);
|
router.use(validateFirebaseIdTokenMiddleware);
|
||||||
|
|
||||||
router.post("/getvehicles", withUserGraphQLClientMiddleware, cdkGetMake.default);
|
router.post("/getvehicles", withUserGraphQLClientMiddleware, cdkGetMake.default);
|
||||||
|
router.post("/calculate-allocations", withUserGraphQLClientMiddleware, cdkCalculateAllocations.defaultRoute);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const eventAuthorizationMiddleware = require("../middleware/eventAuthorizationMI
|
|||||||
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware");
|
const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware");
|
||||||
const { taskAssignedEmail, tasksRemindEmail } = require("../email/tasksEmails");
|
const { taskAssignedEmail, tasksRemindEmail } = require("../email/tasksEmails");
|
||||||
|
const { canvastest } = require("../render/canvas-handler");
|
||||||
|
|
||||||
//Test route to ensure Express is responding.
|
//Test route to ensure Express is responding.
|
||||||
router.get("/test", async function (req, res) {
|
router.get("/test", async function (req, res) {
|
||||||
@@ -49,4 +50,7 @@ router.post("/tasks-remind-handler", eventAuthorizationMiddleware, tasksRemindEm
|
|||||||
router.post("/record-handler/arms", data.arms);
|
router.post("/record-handler/arms", data.arms);
|
||||||
router.post("/taskHandler", validateFirebaseIdTokenMiddleware, taskHandler.taskHandler);
|
router.post("/taskHandler", validateFirebaseIdTokenMiddleware, taskHandler.taskHandler);
|
||||||
|
|
||||||
|
// Canvas Test
|
||||||
|
router.post("/canvastest", validateFirebaseIdTokenMiddleware, canvastest);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ const express = require("express");
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const { inlinecss } = require("../render/inlinecss");
|
const { inlinecss } = require("../render/inlinecss");
|
||||||
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
const { canvas } = require("../render/canvas-handler");
|
||||||
|
|
||||||
// Define the route for inline CSS rendering
|
// Define the route for inline CSS rendering
|
||||||
router.post("/inlinecss", validateFirebaseIdTokenMiddleware, inlinecss);
|
router.post("/inlinecss", validateFirebaseIdTokenMiddleware, inlinecss);
|
||||||
|
router.post("/canvas", validateFirebaseIdTokenMiddleware, canvas);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
Reference in New Issue
Block a user