From 99d3a6a64e1ebb40da0a9b6794b31a238a306d8b Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 29 Apr 2020 13:25:31 -0700 Subject: [PATCH] BOD-72 Refactored calculations to use Dinero library. --- .../job-totals-table.component.jsx | 189 ++++++++++-------- .../job-totals-table/job-totals.utility.js | 185 +++++++++-------- .../jobs-available-new.container.jsx | 7 +- .../jobs-available-supplement.container.jsx | 6 +- .../jobs-detail-financial.component.jsx | 8 +- .../jobs-detail.page.component.jsx | 54 +++-- 6 files changed, 240 insertions(+), 209 deletions(-) diff --git a/client/src/components/job-totals-table/job-totals-table.component.jsx b/client/src/components/job-totals-table/job-totals-table.component.jsx index 2c5eaf522..a5d8f4320 100644 --- a/client/src/components/job-totals-table/job-totals-table.component.jsx +++ b/client/src/components/job-totals-table/job-totals-table.component.jsx @@ -1,12 +1,28 @@ -import { Col, Descriptions, Row, Statistic, Result } from "antd"; -import React from "react"; +import { Col, Descriptions, Row, Statistic } from "antd"; +import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; +import { CalculateJob } from "./job-totals.utility"; +const mapStateToProps = createStructuredSelector({ + //currentUser: selectCurrentUser + bodyshop: selectBodyshop, +}); -export default function JobsTotalsTableComponent({ totals }) { +export function JobsTotalsTableComponent({ bodyshop, job }) { const { t } = useTranslation(); - if (!!!totals) - return ; + const [totals, setTotals] = useState(null); + useEffect(() => { + setTotals(CalculateJob(job, bodyshop.shoprates)); + }, [bodyshop, job]); + + if (!!!totals) { + console.log("Totals was falsey."); + return ; + } return (
@@ -17,161 +33,161 @@ export default function JobsTotalsTableComponent({ totals }) { title={t("jobs.labels.rates")}> - + @@ -184,16 +200,14 @@ export default function JobsTotalsTableComponent({ totals }) { title={t("jobs.labels.partssubletstotal")}> @@ -206,32 +220,31 @@ export default function JobsTotalsTableComponent({ totals }) { column={1} title={t("jobs.labels.totals")}> - + - + - + - +
); } +export default connect(mapStateToProps, null)(JobsTotalsTableComponent); diff --git a/client/src/components/job-totals-table/job-totals.utility.js b/client/src/components/job-totals-table/job-totals.utility.js index bb3f87771..0b1ae7db3 100644 --- a/client/src/components/job-totals-table/job-totals.utility.js +++ b/client/src/components/job-totals-table/job-totals.utility.js @@ -6,68 +6,70 @@ export function CalculateJob(job, shoprates) { rates: CalculateRatesTotals(job, shoprates), custPayable: CalculateCustPayable(job), }; - ret.totals = CalculateTaxesTotals(job, ret); - + console.log("CalculateJob -> Final", ret); return ret; } function CalculateTaxesTotals(job, otherTotals) { - const subtotal = - otherTotals.parts.parts.subtotal + - otherTotals.parts.sublets.subtotal + - otherTotals.rates.subtotal + - (job.towing_payable || 0) + - (job.storage_payable || 0); //Levies should be included?? + const subtotal = otherTotals.parts.parts.subtotal + .add(otherTotals.parts.sublets.subtotal) + .add(otherTotals.rates.subtotal) + .add(Dinero({ amount: (job.towing_payable || 0) * 100 })) + .add(Dinero({ amount: (job.storage_payable || 0) * 100 })); + //TODO Levies should be included?? const statePartsTax = job.joblines.reduce((acc, val) => { if (!!!val.tax_part) return acc; if (!!job.parts_tax_rates[val.part_type]) { - return ( - acc + - val.act_price * - val.part_qty * - (job.parts_tax_rates[val.part_type].prt_tax_rt || 0) + return acc.add( + Dinero({ amount: val.act_price * 100 }) + .multiply(val.part_qty) + .percentage( + (job.parts_tax_rates[val.part_type].prt_tax_rt || 0) * 100 + ) ); } else { return acc; } - }, 0); - - // console.log("otherTotals", otherTotals); - // console.log("job", job); - // console.log("parts pst", statePartsTax); - // console.log( - // "pst on labor", - // otherTotals.rates.rates_subtotal * (job.tax_lbr_rt || 0) - // ); - // console.log( - // "pst on mat", - // (otherTotals.rates.paint_mat.total + otherTotals.rates.shop_mat.total) * - // (job.tax_paint_mat_rt || 0) - // ); + }, Dinero({ amount: 0 })); let ret = { subtotal: subtotal, - federal_tax: subtotal * (job.federal_tax_rate || 0), + federal_tax: subtotal.percentage((job.federal_tax_rate || 0) * 100), statePartsTax, - state_tax: - statePartsTax + - otherTotals.rates.rates_subtotal * (job.tax_lbr_rt || 0) + - ((job.towing_payable || 0) * job.tax_tow_rt || 0) + - ((job.storage_payable || 0) * job.tax_str_rt || 0) + - (otherTotals.rates.paint_mat.total + otherTotals.rates.shop_mat.total) * - (job.tax_paint_mat_rt || 0), - local_tax: subtotal * (job.local_tax_rate || 0), + state_tax: statePartsTax + .add( + otherTotals.rates.rates_subtotal.percentage((job.tax_lbr_rt || 0) * 100) + ) + .add( + Dinero({ amount: (job.towing_payable || 0) * 100 }).percentage( + (job.tax_tow_rt || 0) * 100 + ) + ) + .add( + Dinero({ amount: (job.storage_payable || 0) * 100 }).percentage( + (job.tax_str_rt || 0) * 100 + ) + ) + .add( + otherTotals.rates.paint_mat.total + .add(otherTotals.rates.shop_mat.total) + .percentage((job.tax_paint_mat_rt || 0) * 100) + ), + local_tax: subtotal.percentage((job.local_tax_rate || 0) * 100), }; - - ret.total_repairs = - ret.subtotal + ret.federal_tax + ret.state_tax + ret.local_tax; - ret.net_repairs = ret.total_repairs - otherTotals.custPayable.total; + ret.total_repairs = ret.subtotal + .add(ret.federal_tax) + .add(ret.state_tax) + .add(ret.local_tax); + ret.net_repairs = ret.total_repairs.subtract(otherTotals.custPayable.total); return ret; } +//Rates are multipled by 10 to reduce the errors of rounding. +//Adjusted for when adding to total by dividing by 10. function CalculateRatesTotals(ratesList, shoprates) { const jobLines = ratesList.joblines; @@ -75,86 +77,86 @@ function CalculateRatesTotals(ratesList, shoprates) { rate_la1: { hours: jobLines .filter((item) => item.mod_lbr_ty === "LA1") - .reduce((acc, value) => acc + value.mod_lb_hrs, 0), + .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0), rate: ratesList.rate_la1 || 0, }, rate_la2: { hours: jobLines .filter((item) => item.mod_lbr_ty === "LA2") - .reduce((acc, value) => acc + value.mod_lb_hrs, 0), + .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0), rate: ratesList.rate_la2 || 0, }, rate_la3: { rate: ratesList.rate_la3 || 0, hours: jobLines .filter((item) => item.mod_lbr_ty === "LA3") - .reduce((acc, value) => acc + value.mod_lb_hrs, 0), + .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0), }, rate_la4: { rate: ratesList.rate_la4 || 0, hours: jobLines .filter((item) => item.mod_lbr_ty === "LA4") - .reduce((acc, value) => acc + value.mod_lb_hrs, 0), + .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0), }, rate_laa: { rate: ratesList.rate_laa || 0, hours: jobLines .filter((item) => item.mod_lbr_ty === "LAA") - .reduce((acc, value) => acc + value.mod_lb_hrs, 0), + .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0), }, rate_lab: { rate: ratesList.rate_lab || 0, hours: jobLines .filter((item) => item.mod_lbr_ty === "LAB") - .reduce((acc, value) => acc + value.mod_lb_hrs, 0), + .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0), }, rate_lad: { rate: ratesList.rate_lad || 0, hours: jobLines .filter((item) => item.mod_lbr_ty === "LAD") - .reduce((acc, value) => acc + value.mod_lb_hrs, 0), + .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0), }, rate_lae: { rate: ratesList.rate_lae || 0, hours: jobLines .filter((item) => item.mod_lbr_ty === "LAE") - .reduce((acc, value) => acc + value.mod_lb_hrs, 0), + .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0), }, rate_laf: { rate: ratesList.rate_laf || 0, hours: jobLines .filter((item) => item.mod_lbr_ty === "LAF") - .reduce((acc, value) => acc + value.mod_lb_hrs, 0), + .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0), }, rate_lag: { rate: ratesList.rate_lag || 0, hours: jobLines .filter((item) => item.mod_lbr_ty === "LAG") - .reduce((acc, value) => acc + value.mod_lb_hrs, 0), + .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0), }, rate_lam: { rate: ratesList.rate_lam || 0, hours: jobLines .filter((item) => item.mod_lbr_ty === "LAM") - .reduce((acc, value) => acc + value.mod_lb_hrs, 0), + .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0), }, rate_lar: { rate: ratesList.rate_lar || 0, hours: jobLines .filter((item) => item.mod_lbr_ty === "LAR") - .reduce((acc, value) => acc + value.mod_lb_hrs, 0), + .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0), }, rate_las: { rate: ratesList.rate_las || 0, hours: jobLines .filter((item) => item.mod_lbr_ty === "LAS") - .reduce((acc, value) => acc + value.mod_lb_hrs, 0), + .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0), }, rate_lau: { rate: ratesList.rate_lau || 0, hours: jobLines .filter((item) => item.mod_lbr_ty === "LAU") - .reduce((acc, value) => acc + value.mod_lb_hrs, 0), + .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0), }, rate_atp: { rate: shoprates.rate_atp || 0, @@ -173,38 +175,40 @@ function CalculateRatesTotals(ratesList, shoprates) { item.mod_lbr_ty !== "LAS" && item.mod_lbr_ty !== "LAA" ) - .reduce((acc, value) => acc + value.mod_lb_hrs, 0) + .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0) : 0, }, paint_mat: { rate: ratesList.rate_mapa || 0, hours: jobLines .filter((item) => item.mod_lbr_ty === "LAR") - .reduce((acc, value) => acc + value.mod_lb_hrs, 0), + .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0), }, shop_mat: { rate: ratesList.rate_mash || 0, hours: jobLines .filter((item) => item.mod_lbr_ty !== "LAR") - .reduce((acc, value) => acc + value.mod_lb_hrs, 0), + .reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0), }, }; - let subtotal = 0; - let rates_subtotal = 0; + let subtotal = Dinero({ amount: 0 }); + let rates_subtotal = Dinero({ amount: 0 }); for (const property in ret) { - ret[property].total = ret[property].hours * ret[property].rate; - subtotal = subtotal + ret[property].hours * ret[property].rate; + ret[property].total = Dinero({ amount: ret[property].rate * 100 }) + .multiply(ret[property].hours) + .divide(10); + subtotal = subtotal.add(ret[property].total); if ( property !== "paint_mat" && property !== "shop_mat" //&& property !== "rate_atp" ) - rates_subtotal = - rates_subtotal + ret[property].hours * ret[property].rate; + rates_subtotal = rates_subtotal.add(ret[property].total); } ret.subtotal = subtotal; ret.rates_subtotal = rates_subtotal; + return ret; } @@ -225,7 +229,11 @@ function CalculatePartsTotals(jobLines) { ...acc, parts: { ...acc.parts, - subtotal: acc.parts.subtotal + value.act_price * value.part_qty, + subtotal: acc.parts.subtotal.add( + Dinero({ amount: value.act_price * 100 }).multiply( + value.part_qty + ) + ), //TODO Add Adjustments in }, }; @@ -235,7 +243,9 @@ function CalculatePartsTotals(jobLines) { ...acc, sublets: { ...acc.sublets, - subtotal: acc.sublets.subtotal + value.act_price, + subtotal: acc.sublets.subtotal.add( + Dinero({ amount: value.act_price * 100 }) + ), //TODO Add Adjustments in }, }; @@ -244,31 +254,46 @@ function CalculatePartsTotals(jobLines) { } }, { - parts: { subtotal: 0, adjustments: 0, total: 0 }, - sublets: { subtotal: 0, adjustments: 0, total: 0 }, + parts: { + subtotal: Dinero({ amount: 0 }), + adjustments: Dinero({ amount: 0 }), + total: Dinero({ amount: 0 }), + }, + sublets: { + subtotal: Dinero({ amount: 0 }), + adjustments: Dinero({ amount: 0 }), + total: Dinero({ amount: 0 }), + }, } ); return { - parts: { ...ret.parts, total: ret.parts.subtotal + ret.parts.adjustments }, + parts: { + ...ret.parts, + total: ret.parts.subtotal, //+ ret.parts.adjustments + }, sublets: { ...ret.sublets, - total: ret.sublets.subtotal + ret.sublets.adjustments, + total: ret.sublets.subtotal, // + ret.sublets.adjustments, }, }; } function CalculateCustPayable(job) { - return { - deductible: job.ded_amt || 0, - federal_tax: job.federal_tax_payable || 0, //TODO Should this be renamed to make it more clear this is customer GST? - other_customer_amount: job.other_amount_payable || 0, - dep_taxes: job.depreciation_taxes || 0, - total: - job.ded_amt || - 0 + job.federal_tax_payable || - 0 + job.other_amount_payable || - 0 + job.depreciation_taxes || - 0, + let ret = { + deductible: Dinero({ amount: (job.ded_amt || 0) * 100 }) || 0, + federal_tax: Dinero({ amount: (job.federal_tax_payable || 0) * 100 }), //TODO Should this be renamed to make it more clear this is customer GST? + other_customer_amount: Dinero({ + amount: (job.other_amount_payable || 0) * 100, + }), + dep_taxes: Dinero({ amount: job.depreciation_taxes || 0 }), }; + + ret.total = ret.deductible + .add(ret.federal_tax) + .add(ret.federal_tax) + .add(ret.other_customer_amount) + .add(ret.dep_taxes); + + return ret; } diff --git a/client/src/components/jobs-available-new/jobs-available-new.container.jsx b/client/src/components/jobs-available-new/jobs-available-new.container.jsx index 645013d71..8f1e12d15 100644 --- a/client/src/components/jobs-available-new/jobs-available-new.container.jsx +++ b/client/src/components/jobs-available-new/jobs-available-new.container.jsx @@ -66,12 +66,11 @@ export function JobsAvailableContainer({ const newJob = { ...estData.data.available_jobs_by_pk.est_data, - clm_total: newTotals.totals.total_repairs, - owner_owing: newTotals.custPayable.total, - job_totals: newTotals, + clm_total: newTotals.totals.total_repairs.toFormat("0.00"), + owner_owing: newTotals.custPayable.total.toFormat("0.00"), + job_totals: JSON.stringify(newTotals), }; - console.log("newTotals", newTotals); insertNewJob({ variables: { job: selectedOwner diff --git a/client/src/components/jobs-available-supplement/jobs-available-supplement.container.jsx b/client/src/components/jobs-available-supplement/jobs-available-supplement.container.jsx index 53cd88281..67b3282a8 100644 --- a/client/src/components/jobs-available-supplement/jobs-available-supplement.container.jsx +++ b/client/src/components/jobs-available-supplement/jobs-available-supplement.container.jsx @@ -97,9 +97,9 @@ export function JobsAvailableSupplementContainer({ jobId: selectedJob, job: { ...supp, - clm_total: newTotals.totals.total_repairs, - owner_owing: newTotals.custPayable.total, - job_totals: newTotals, + clm_total: newTotals.totals.total_repairs.toFormat("0.00"), + owner_owing: newTotals.custPayable.total.toFormat("0.00"), + job_totals: JSON.stringify(newTotals), }, }, }) diff --git a/client/src/components/jobs-detail-financial/jobs-detail-financial.component.jsx b/client/src/components/jobs-detail-financial/jobs-detail-financial.component.jsx index 28f1f82a6..6e1f10518 100644 --- a/client/src/components/jobs-detail-financial/jobs-detail-financial.component.jsx +++ b/client/src/components/jobs-detail-financial/jobs-detail-financial.component.jsx @@ -1,8 +1,7 @@ -import { Divider, Form, Input, InputNumber, Row, Col } from "antd"; +import { Col, Divider, Form, Input, InputNumber, Row } from "antd"; import React from "react"; import { useTranslation } from "react-i18next"; import JobTotalsTable from "../job-totals-table/job-totals-table.component"; - export default function JobsDetailFinancials({ job }) { const { t } = useTranslation(); @@ -125,8 +124,9 @@ export default function JobsDetailFinancials({ job }) { - - {JSON.stringify(job.cieca_ttl, null, "\t")} + +

Mitchell: {JSON.stringify(job.cieca_ttl, null, "\t")}

+

Mine:{JSON.stringify(job.job_totals, null, "\t")}

); diff --git a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx index 7f71aae0f..5bd58cd38 100644 --- a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx +++ b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx @@ -109,9 +109,9 @@ export function JobsDetailPage({ jobId: job.id, job: { ...values, - clm_total: newTotals.totals.total_repairs, - owner_owing: newTotals.custPayable.total, - job_totals: newTotals, + clm_total: newTotals.totals.total_repairs.toFormat("0.00"), + owner_owing: newTotals.custPayable.total.toFormat("0.00"), + job_totals: JSON.stringify(newTotals), }, }, }).then((r) => { @@ -124,15 +124,21 @@ export function JobsDetailPage({ return ( } - > + fallback={}> +
console.log("a,b", a, b)} - name="JobDetailForm" + name='JobDetailForm' onFinish={handleFinish} {...formItemLayout} autoComplete={"off"} @@ -163,8 +169,7 @@ export function JobsDetailPage({ date_invoiced: job.date_invoiced ? moment(job.date_invoiced) : null, date_closed: job.date_closed ? moment(job.date_closed) : null, date_exported: job.date_exported ? moment(job.date_exported) : null, - }} - > + }}> history.push({ search: `?tab=${key}` })} - > + onChange={(key) => history.push({ search: `?tab=${key}` })}> @@ -183,8 +187,7 @@ export function JobsDetailPage({ {t("menus.jobsdetail.claimdetail")} } - key="claimdetail" - > + key='claimdetail'> } - key="insurance" - > + key='insurance'> } - key="repairdata" - > + key='repairdata'> } - key="financials" - > + key='financials'> } - key="partssublet" - > + key='partssublet'> } - key="labor" - > + key='labor'> } - key="dates" - > + key='dates'> } } - key="documents" - > + key='documents'> } - key="notes" - > + key='notes'> @@ -283,8 +278,7 @@ export function JobsDetailPage({ {t("jobs.labels.audit")} } - key="audit" - > + key='audit'>