Files
bodyshop/client/src/components/job-costing-modal/job-costing-modal.component.jsx

159 lines
5.3 KiB
JavaScript

import Dinero from "dinero.js";
import React from "react";
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";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function JobCostingModalComponent({ bodyshop, job }) {
const defaultProfits = bodyshop.md_responsibility_centers.defaults.profits;
// const defaultCosts = bodyshop.md_responsibility_centers.defaults.costs;
const jobLineTotalsByProfitCenter = job.joblines.reduce(
(acc, val) => {
const laborProfitCenter = defaultProfits[val.mod_lbr_ty];
if (!!!laborProfitCenter)
console.log(
"Unknown cost/profit center mapping for labor.",
val.mod_lbr_ty
);
const rateName = `rate_${(val.mod_lbr_ty || "").toLowerCase()}`;
const laborAmount = Dinero({
amount: Math.round((job[rateName] || 0) * 100),
}).multiply(val.mod_lb_hrs || 0);
if (!!!acc.labor[laborProfitCenter])
acc.labor[laborProfitCenter] = Dinero();
acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter].add(
laborAmount
);
const partsProfitCenter = defaultProfits[val.part_type];
if (!!!partsProfitCenter)
console.log(
"Unknown cost/profit center mapping for parts.",
val.part_type
);
const partsAmount = Dinero({
amount: Math.round((val.act_price || 0) * 100),
}).multiply(val.part_qty || 1);
if (!!!acc.parts[partsProfitCenter])
acc.parts[partsProfitCenter] = Dinero();
acc.parts[partsProfitCenter] = acc.parts[partsProfitCenter].add(
partsAmount
);
return acc;
},
{ parts: {}, labor: {} }
);
const invoiceTotalsByProfitCenter = job.invoices.reduce(
(inv_acc, inv_val) => {
//At the invoice level.
inv_val.invoicelines.map((line_val) => {
//At the invoice line level.
//console.log("JobCostingPartsTable -> line_val", line_val);
if (!!!inv_acc[line_val.cost_center])
inv_acc[line_val.cost_center] = Dinero();
inv_acc[line_val.cost_center] = inv_acc[line_val.cost_center].add(
Dinero({
amount: Math.round((line_val.actual_cost || 0) * 100),
})
.multiply(line_val.quantity)
.multiply(inv_val.is_credit_memo ? -1 : 1)
);
return null;
});
return inv_acc;
},
{}
);
const summaryData = {
totalLaborSales: Dinero({ amount: 0 }),
totalPartsSales: Dinero({ amount: 0 }),
totalSales: Dinero({ amount: 0 }),
totalCost: Dinero({ amount: 0 }),
gpdollars: Dinero({ amount: 0 }),
gppercent: null,
gppercentFormatted: null,
};
const costCenterData = Object.keys(defaultProfits).map((key, idx) => {
const ccVal = defaultProfits[key];
const sale_labor =
jobLineTotalsByProfitCenter.labor[ccVal] || Dinero({ amount: 0 });
const sale_parts =
jobLineTotalsByProfitCenter.parts[ccVal] || Dinero({ amount: 0 });
const cost = invoiceTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 });
const totalSales = sale_labor.add(sale_parts);
const gpdollars = totalSales.subtract(cost);
const gppercent = (
(gpdollars.getAmount() / totalSales.getAmount()) *
100
).toFixed(2);
let gppercentFormatted;
if (isNaN(gppercent)) gppercentFormatted = "0%";
else if (!isFinite(gppercent)) gppercentFormatted = "-∞";
else {
gppercentFormatted = `${gppercent}%`;
}
//Push summary data to avoid extra loop.
summaryData.totalLaborSales = summaryData.totalLaborSales.add(sale_labor);
summaryData.totalPartsSales = summaryData.totalPartsSales.add(sale_parts);
summaryData.totalSales = summaryData.totalSales
.add(sale_labor)
.add(sale_parts);
summaryData.totalCost = summaryData.totalCost.add(cost);
return {
id: idx,
cost_center: ccVal,
sale_labor: sale_labor && sale_labor.toFormat(),
sale_parts: sale_parts && sale_parts.toFormat(),
cost: cost && cost.toFormat(),
gpdollars: gpdollars.toFormat(),
gppercent: gppercentFormatted,
};
});
//Final summary data massaging.
summaryData.gpdollars = summaryData.totalSales.subtract(
summaryData.totalCost
);
summaryData.gppercent = (
(summaryData.gpdollars.getAmount() / summaryData.totalSales.getAmount()) *
100
).toFixed(2);
if (isNaN(summaryData.gppercent)) summaryData.gppercentFormatted = 0;
else if (!isFinite(summaryData.gppercent))
summaryData.gppercentFormatted = "-∞";
else {
summaryData.gppercentFormatted = summaryData.gppercent;
}
console.log("JobCostingModalComponent -> summaryData", summaryData);
return (
<div>
<JobCostingStatistics job={job} summaryData={summaryData} />
<JobCostingPartsTable job={job} data={costCenterData} />
</div>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobCostingModalComponent);