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==",
|
"integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==",
|
||||||
"optional": true
|
"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": {
|
"bluebird": {
|
||||||
"version": "3.7.2",
|
"version": "3.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
"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": {
|
"filesize": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
|
||||||
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
|
"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": {
|
"nanoid": {
|
||||||
"version": "3.1.16",
|
"version": "3.1.16",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.16.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.16.tgz",
|
||||||
@@ -16724,7 +16745,11 @@
|
|||||||
"version": "1.2.13",
|
"version": "1.2.13",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
|
||||||
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
||||||
"optional": true
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"bindings": "^1.5.0",
|
||||||
|
"nan": "^2.12.1"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"glob-parent": {
|
"glob-parent": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
@@ -17326,7 +17351,11 @@
|
|||||||
"version": "1.2.13",
|
"version": "1.2.13",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
|
||||||
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
||||||
"optional": true
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"bindings": "^1.5.0",
|
||||||
|
"nan": "^2.12.1"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"glob-parent": {
|
"glob-parent": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
|
|||||||
@@ -18265,6 +18265,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</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>
|
<concept_node>
|
||||||
<name>state_tax_amt</name>
|
<name>state_tax_amt</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
|
import { Typography } from "antd";
|
||||||
import Dinero from "dinero.js";
|
import Dinero from "dinero.js";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import JobCostingPartsTable from "../job-costing-parts-table/job-costing-parts-table.component";
|
import JobCostingPartsTable from "../job-costing-parts-table/job-costing-parts-table.component";
|
||||||
import JobCostingStatistics from "../job-costing-statistics/job-costing-statistics.component";
|
import JobCostingStatistics from "../job-costing-statistics/job-costing-statistics.component";
|
||||||
|
import JobCostingPie from "./job-costing-modal.pie.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -16,7 +19,7 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
export function JobCostingModalComponent({ bodyshop, job }) {
|
export function JobCostingModalComponent({ bodyshop, job }) {
|
||||||
const defaultProfits = bodyshop.md_responsibility_centers.defaults.profits;
|
const defaultProfits = bodyshop.md_responsibility_centers.defaults.profits;
|
||||||
// const defaultCosts = bodyshop.md_responsibility_centers.defaults.costs;
|
// const defaultCosts = bodyshop.md_responsibility_centers.defaults.costs;
|
||||||
|
const { t } = useTranslation();
|
||||||
const jobLineTotalsByProfitCenter =
|
const jobLineTotalsByProfitCenter =
|
||||||
job &&
|
job &&
|
||||||
job.joblines.reduce(
|
job.joblines.reduce(
|
||||||
@@ -115,11 +118,11 @@ export function JobCostingModalComponent({ bodyshop, job }) {
|
|||||||
ticketTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 });
|
ticketTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 });
|
||||||
const cost_parts = billTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 });
|
const cost_parts = billTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 });
|
||||||
|
|
||||||
const cost = (billTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 })).add(
|
const costs = (
|
||||||
ticketTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 })
|
billTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 })
|
||||||
);
|
).add(ticketTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 }));
|
||||||
const totalSales = sale_labor.add(sale_parts);
|
const totalSales = sale_labor.add(sale_parts);
|
||||||
const gpdollars = totalSales.subtract(cost);
|
const gpdollars = totalSales.subtract(costs);
|
||||||
const gppercent = (
|
const gppercent = (
|
||||||
(gpdollars.getAmount() / totalSales.getAmount()) *
|
(gpdollars.getAmount() / totalSales.getAmount()) *
|
||||||
100
|
100
|
||||||
@@ -139,16 +142,19 @@ export function JobCostingModalComponent({ bodyshop, job }) {
|
|||||||
.add(sale_parts);
|
.add(sale_parts);
|
||||||
summaryData.totalLaborCost = summaryData.totalLaborCost.add(cost_labor);
|
summaryData.totalLaborCost = summaryData.totalLaborCost.add(cost_labor);
|
||||||
summaryData.totalPartsCost = summaryData.totalPartsCost.add(cost_parts);
|
summaryData.totalPartsCost = summaryData.totalPartsCost.add(cost_parts);
|
||||||
summaryData.totalCost = summaryData.totalCost.add(cost);
|
summaryData.totalCost = summaryData.totalCost.add(costs);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: idx,
|
id: idx,
|
||||||
cost_center: ccVal,
|
cost_center: ccVal,
|
||||||
sale_labor: sale_labor && sale_labor.toFormat(),
|
sale_labor: sale_labor && sale_labor.toFormat(),
|
||||||
sale_parts: sale_parts && sale_parts.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_parts: cost_parts && cost_parts.toFormat(),
|
||||||
cost_labor: cost_labor && cost_labor.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(),
|
gpdollars: gpdollars.toFormat(),
|
||||||
gppercent: gppercentFormatted,
|
gppercent: gppercentFormatted,
|
||||||
};
|
};
|
||||||
@@ -173,6 +179,18 @@ export function JobCostingModalComponent({ bodyshop, job }) {
|
|||||||
<div>
|
<div>
|
||||||
<JobCostingStatistics job={job} summaryData={summaryData} />
|
<JobCostingStatistics job={job} summaryData={summaryData} />
|
||||||
<JobCostingPartsTable job={job} data={costCenterData} />
|
<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>
|
</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,
|
state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("jobs.labels.sale_labor"),
|
title: t("jobs.labels.sales"),
|
||||||
dataIndex: "sale_labor",
|
dataIndex: "sales",
|
||||||
key: "sale_labor",
|
key: "sales",
|
||||||
sorter: (a, b) => alphaSort(a.sale_labor, b.sale_labor),
|
sorter: (a, b) => alphaSort(a.sales, b.sales),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "sale_labor" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "sales" && state.sortedInfo.order,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: t("jobs.labels.sale_parts"),
|
title: t("jobs.labels.costs"),
|
||||||
dataIndex: "sale_parts",
|
dataIndex: "costs",
|
||||||
key: "sale_parts",
|
key: "costs",
|
||||||
sorter: (a, b) => alphaSort(a.sale_parts, b.sale_parts),
|
sorter: (a, b) => a.costs - b.costs,
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "sale_parts" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "costs" && 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,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: t("jobs.labels.gpdollars"),
|
title: t("jobs.labels.gpdollars"),
|
||||||
dataIndex: "gpdollars",
|
dataIndex: "gpdollars",
|
||||||
|
|||||||
@@ -1112,6 +1112,7 @@
|
|||||||
"reconciliationheader": "Parts & Sublet Reconciliation",
|
"reconciliationheader": "Parts & Sublet Reconciliation",
|
||||||
"sale_labor": "Sales - Labor",
|
"sale_labor": "Sales - Labor",
|
||||||
"sale_parts": "Sales - Parts",
|
"sale_parts": "Sales - Parts",
|
||||||
|
"sales": "Sales",
|
||||||
"state_tax_amt": "State/Provincial Taxes",
|
"state_tax_amt": "State/Provincial Taxes",
|
||||||
"subletstotal": "Sublets Total",
|
"subletstotal": "Sublets Total",
|
||||||
"subtotal": "Subtotal",
|
"subtotal": "Subtotal",
|
||||||
|
|||||||
@@ -1112,6 +1112,7 @@
|
|||||||
"reconciliationheader": "",
|
"reconciliationheader": "",
|
||||||
"sale_labor": "",
|
"sale_labor": "",
|
||||||
"sale_parts": "",
|
"sale_parts": "",
|
||||||
|
"sales": "",
|
||||||
"state_tax_amt": "",
|
"state_tax_amt": "",
|
||||||
"subletstotal": "",
|
"subletstotal": "",
|
||||||
"subtotal": "",
|
"subtotal": "",
|
||||||
|
|||||||
@@ -1112,6 +1112,7 @@
|
|||||||
"reconciliationheader": "",
|
"reconciliationheader": "",
|
||||||
"sale_labor": "",
|
"sale_labor": "",
|
||||||
"sale_parts": "",
|
"sale_parts": "",
|
||||||
|
"sales": "",
|
||||||
"state_tax_amt": "",
|
"state_tax_amt": "",
|
||||||
"subletstotal": "",
|
"subletstotal": "",
|
||||||
"subtotal": "",
|
"subtotal": "",
|
||||||
|
|||||||
Reference in New Issue
Block a user