Added pie to job costinng, combined columns on job costing IO-569

This commit is contained in:
Patrick Fic
2021-01-11 12:13:50 -08:00
parent ae7f67461b
commit 32cbe95d0b
8 changed files with 161 additions and 35 deletions

View File

@@ -4040,6 +4040,15 @@
"integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==",
"optional": true
},
"bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"optional": true,
"requires": {
"file-uri-to-path": "1.0.0"
}
},
"bluebird": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
@@ -7153,6 +7162,12 @@
}
}
},
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"optional": true
},
"filesize": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz",
@@ -10752,6 +10767,12 @@
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
},
"nan": {
"version": "2.14.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==",
"optional": true
},
"nanoid": {
"version": "3.1.16",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.16.tgz",
@@ -16724,7 +16745,11 @@
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
"optional": true
"optional": true,
"requires": {
"bindings": "^1.5.0",
"nan": "^2.12.1"
}
},
"glob-parent": {
"version": "3.1.0",
@@ -17326,7 +17351,11 @@
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
"optional": true
"optional": true,
"requires": {
"bindings": "^1.5.0",
"nan": "^2.12.1"
}
},
"glob-parent": {
"version": "3.1.0",

View File

@@ -18265,6 +18265,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>sales</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>state_tax_amt</name>
<definition_loaded>false</definition_loaded>

View File

@@ -1,10 +1,13 @@
import { Typography } from "antd";
import Dinero from "dinero.js";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import JobCostingPartsTable from "../job-costing-parts-table/job-costing-parts-table.component";
import JobCostingStatistics from "../job-costing-statistics/job-costing-statistics.component";
import JobCostingPie from "./job-costing-modal.pie.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -16,7 +19,7 @@ const mapDispatchToProps = (dispatch) => ({
export function JobCostingModalComponent({ bodyshop, job }) {
const defaultProfits = bodyshop.md_responsibility_centers.defaults.profits;
// const defaultCosts = bodyshop.md_responsibility_centers.defaults.costs;
const { t } = useTranslation();
const jobLineTotalsByProfitCenter =
job &&
job.joblines.reduce(
@@ -115,11 +118,11 @@ export function JobCostingModalComponent({ bodyshop, job }) {
ticketTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 });
const cost_parts = billTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 });
const cost = (billTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 })).add(
ticketTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 })
);
const costs = (
billTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 })
).add(ticketTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 }));
const totalSales = sale_labor.add(sale_parts);
const gpdollars = totalSales.subtract(cost);
const gpdollars = totalSales.subtract(costs);
const gppercent = (
(gpdollars.getAmount() / totalSales.getAmount()) *
100
@@ -139,16 +142,19 @@ export function JobCostingModalComponent({ bodyshop, job }) {
.add(sale_parts);
summaryData.totalLaborCost = summaryData.totalLaborCost.add(cost_labor);
summaryData.totalPartsCost = summaryData.totalPartsCost.add(cost_parts);
summaryData.totalCost = summaryData.totalCost.add(cost);
summaryData.totalCost = summaryData.totalCost.add(costs);
return {
id: idx,
cost_center: ccVal,
sale_labor: sale_labor && sale_labor.toFormat(),
sale_parts: sale_parts && sale_parts.toFormat(),
sales: sale_labor.add(sale_parts).toFormat(),
sales_dinero: sale_labor.add(sale_parts),
cost_parts: cost_parts && cost_parts.toFormat(),
cost_labor: cost_labor && cost_labor.toFormat(),
cost: cost && cost.toFormat(),
costs: cost_parts.add(cost_labor).toFormat(),
costs_dinero: cost_parts.add(cost_labor),
gpdollars: gpdollars.toFormat(),
gppercent: gppercentFormatted,
};
@@ -173,6 +179,18 @@ export function JobCostingModalComponent({ bodyshop, job }) {
<div>
<JobCostingStatistics job={job} summaryData={summaryData} />
<JobCostingPartsTable job={job} data={costCenterData} />
<div className="imex-flex-row">
<div style={{ flex: 1 }}>
<Typography.Title level={4}>
{t("jobs.labels.sales")}
</Typography.Title>
<JobCostingPie type="sales" costCenterData={costCenterData} />
</div>
<div style={{ flex: 1 }}>
<Typography.Title level={4}>{t("jobs.labels.cost")}</Typography.Title>
<JobCostingPie type="cost" costCenterData={costCenterData} />
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,69 @@
import React, { useCallback, useMemo } from "react";
import { Cell, Pie, PieChart, ResponsiveContainer } from "recharts";
export default function JobCostingPieComponent({
type = "sales",
costCenterData,
}) {
const Calculatedata = useCallback(
(data) => {
if (data && data.length > 0) {
return data.reduce((acc, i) => {
const value =
type === "sales"
? i.sales_dinero.getAmount()
: i.costs_dinero.getAmount();
if (value > 0) {
acc.push({
name: i.cost_center,
color: "#" + Math.floor(Math.random() * 16777215).toString(16),
label: `${i.cost_center} - ${
type === "sales"
? i.sales_dinero.toFormat()
: i.costs_dinero.toFormat()
}`,
value:
type === "sales"
? i.sales_dinero.getAmount()
: i.costs_dinero.getAmount(),
});
}
return acc;
}, []);
} else {
return [];
}
},
[type]
);
const memoizedData = useMemo(() => Calculatedata(costCenterData), [
costCenterData,
Calculatedata,
]);
console.log(type, memoizedData);
return (
<ResponsiveContainer width="100%" height={175}>
<PieChart>
<Pie
data={memoizedData}
innerRadius={40}
outerRadius={50}
fill="#8884d8"
paddingAngle={5}
dataKey="value"
label={(entry) => entry.label}
labelLine
>
{memoizedData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
</PieChart>
</ResponsiveContainer>
);
}

View File

@@ -24,37 +24,23 @@ export default function JobCostingPartsTable({ job, data }) {
state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order,
},
{
title: t("jobs.labels.sale_labor"),
dataIndex: "sale_labor",
key: "sale_labor",
sorter: (a, b) => alphaSort(a.sale_labor, b.sale_labor),
title: t("jobs.labels.sales"),
dataIndex: "sales",
key: "sales",
sorter: (a, b) => alphaSort(a.sales, b.sales),
sortOrder:
state.sortedInfo.columnKey === "sale_labor" && state.sortedInfo.order,
state.sortedInfo.columnKey === "sales" && state.sortedInfo.order,
},
{
title: t("jobs.labels.sale_parts"),
dataIndex: "sale_parts",
key: "sale_parts",
sorter: (a, b) => alphaSort(a.sale_parts, b.sale_parts),
title: t("jobs.labels.costs"),
dataIndex: "costs",
key: "costs",
sorter: (a, b) => a.costs - b.costs,
sortOrder:
state.sortedInfo.columnKey === "sale_parts" && state.sortedInfo.order,
},
{
title: t("jobs.labels.cost_labor"),
dataIndex: "cost_labor",
key: "cost_labor",
sorter: (a, b) => a.cost_labor - b.cost_labor,
sortOrder:
state.sortedInfo.columnKey === "cost_labor" && state.sortedInfo.order,
},
{
title: t("jobs.labels.cost_parts"),
dataIndex: "cost_parts",
key: "cost_parts",
sorter: (a, b) => a.cost_parts - b.cost_parts,
sortOrder:
state.sortedInfo.columnKey === "cost_parts" && state.sortedInfo.order,
state.sortedInfo.columnKey === "costs" && state.sortedInfo.order,
},
{
title: t("jobs.labels.gpdollars"),
dataIndex: "gpdollars",

View File

@@ -1112,6 +1112,7 @@
"reconciliationheader": "Parts & Sublet Reconciliation",
"sale_labor": "Sales - Labor",
"sale_parts": "Sales - Parts",
"sales": "Sales",
"state_tax_amt": "State/Provincial Taxes",
"subletstotal": "Sublets Total",
"subtotal": "Subtotal",

View File

@@ -1112,6 +1112,7 @@
"reconciliationheader": "",
"sale_labor": "",
"sale_parts": "",
"sales": "",
"state_tax_amt": "",
"subletstotal": "",
"subtotal": "",

View File

@@ -1112,6 +1112,7 @@
"reconciliationheader": "",
"sale_labor": "",
"sale_parts": "",
"sales": "",
"state_tax_amt": "",
"subletstotal": "",
"subtotal": "",