Added pie to job costinng, combined columns on job costing IO-569
This commit is contained in:
33
admin/package-lock.json
generated
33
admin/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1112,6 +1112,7 @@
|
||||
"reconciliationheader": "",
|
||||
"sale_labor": "",
|
||||
"sale_parts": "",
|
||||
"sales": "",
|
||||
"state_tax_amt": "",
|
||||
"subletstotal": "",
|
||||
"subtotal": "",
|
||||
|
||||
@@ -1112,6 +1112,7 @@
|
||||
"reconciliationheader": "",
|
||||
"sale_labor": "",
|
||||
"sale_parts": "",
|
||||
"sales": "",
|
||||
"state_tax_amt": "",
|
||||
"subletstotal": "",
|
||||
"subtotal": "",
|
||||
|
||||
Reference in New Issue
Block a user