From 6a9e871b0846794492ee0dfe0ebece46afe84b74 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Mon, 11 Sep 2023 20:44:12 -0700 Subject: [PATCH] Add parts tax calculations. --- job-totals-testing-util.js | 4 +- server/graphql-client/queries.js | 1 + server/job/job-totals.js | 192 +++++++++++++++++++------------ setadmin.js | 2 +- 4 files changed, 121 insertions(+), 78 deletions(-) diff --git a/job-totals-testing-util.js b/job-totals-testing-util.js index 9fd3550d4..9e5220a87 100644 --- a/job-totals-testing-util.js +++ b/job-totals-testing-util.js @@ -19,8 +19,8 @@ require("dotenv").config({ }); async function RunTheTest() { - const bodyshopids = ["6c63a820-542c-497e-8c82-0cc38fb2bbca"]; - const bearerToken = `Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImM2MGI5ZGUwODBmZmFmYmZjMTgzMzllY2Q0NGFjNzdmN2ZhNGU4ZDMiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiUm9tZSBEZXZlbG9wbWVudCIsImh0dHBzOi8vaGFzdXJhLmlvL2p3dC9jbGFpbXMiOnsieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoiYWRtaW4iLCJ4LWhhc3VyYS1hbGxvd2VkLXJvbGVzIjpbImFkbWluIl0sIngtaGFzdXJhLXVzZXItaWQiOiJ0NlltMU5EbENET1BacjNGOWJndVdINExoU1gyIn0sImlvYWRtaW4iOnRydWUsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9yb21lLXByb2QtMSIsImF1ZCI6InJvbWUtcHJvZC0xIiwiYXV0aF90aW1lIjoxNjkyODk5ODE2LCJ1c2VyX2lkIjoidDZZbTFORGxDRE9QWnIzRjliZ3VXSDRMaFNYMiIsInN1YiI6InQ2WW0xTkRsQ0RPUFpyM0Y5Ymd1V0g0TGhTWDIiLCJpYXQiOjE2OTMyNTA1NjIsImV4cCI6MTY5MzI1NDE2MiwiZW1haWwiOiJwYXRyaWNrQHJvbWUuZGV2IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWlsIjpbInBhdHJpY2tAcm9tZS5kZXYiXX0sInNpZ25faW5fcHJvdmlkZXIiOiJwYXNzd29yZCJ9fQ.POr8U2pP4XtTJEDRJ_BveRkCs92CIfDDdfU24OYe_aZh6LFPN0bQukNHXrLt3gaD30SUcg5mgmI2VUphgmwviMEGY-zizPC9o6GUKEEppZWQXfrfTyJNa1VKKH9h5zZPPFFW8UJRMi131pCc0ev26GS8Do-FJAgwHLJd6Jp2RbbqiCIeafNMhQCEoXohOk-VArNe7tPAb6-IjxqGVyNqvVyIo6niSXYvmgNjyF1WnnIw0CsnPoJlc5kVMtRdYeshJI7V117MOlUwZicF62vsm32eCunjn3qhN5XsujI7gy9us3vzwhdR1lxISZCLhLOXEYHPL373HJh7I_KN1C3NuA`; + const bodyshopids = ["a7ee1503-ee05-4a02-b80e-bdb11d1cc8ac"]; + const bearerToken = `Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjE5MGFkMTE4YTk0MGFkYzlmMmY1Mzc2YjM1MjkyZmVkZThjMmQwZWUiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiUm9tZSBEZXZlbG9wbWVudCIsImh0dHBzOi8vaGFzdXJhLmlvL2p3dC9jbGFpbXMiOnsieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoidXNlciIsIngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsidXNlciJdLCJ4LWhhc3VyYS11c2VyLWlkIjoidDZZbTFORGxDRE9QWnIzRjliZ3VXSDRMaFNYMiJ9LCJpb2FkbWluIjp0cnVlLCJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vcm9tZS1wcm9kLTEiLCJhdWQiOiJyb21lLXByb2QtMSIsImF1dGhfdGltZSI6MTY5NDQ2NjM3OCwidXNlcl9pZCI6InQ2WW0xTkRsQ0RPUFpyM0Y5Ymd1V0g0TGhTWDIiLCJzdWIiOiJ0NlltMU5EbENET1BacjNGOWJndVdINExoU1gyIiwiaWF0IjoxNjk0NDY5OTY3LCJleHAiOjE2OTQ0NzM1NjcsImVtYWlsIjoicGF0cmlja0Byb21lLmRldiIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJwYXRyaWNrQHJvbWUuZGV2Il19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0.cL1tTMmFwnyg-mXqRRaFLGSn92pwCDrbTGZviVFKTPqwejDDe_OTla6zizy5w5XBk1_S2CA4SrsH6-8uXkrRSLECd1QMsYK1XKUj0zkfe-o3Z1dMva9gPpVrs-fiCXzjcfs-P12hPfvubNZgqpj1EwZmQ6imq8MidkAo1c4V-IWGpxsmCTNBLN_09N0071spsabwtMosXHJVnkG44StXEM2w-FRak6dE0uZZkbPGgg_2uDwFQb3um6MJ8FbK398pLMs3cUrdQiB-5YozYoIuEI-L00s7dDTvjOKUzOvcDme5UCAS6RmwWoZYkTWrMJU3jf5ivAvjHNIhy3aMGyQxzg`; const { jobs } = await client.request( gql` query GET_JOBS($bodyshopids: [uuid!]!) { diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index 3bba58cf4..149184329 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -1168,6 +1168,7 @@ exports.GET_JOB_BY_PK = `query GET_JOB_BY_PK($id: uuid!) { shopid est_ct_ln cieca_pfl + cieca_pft vehicle{ id notes diff --git a/server/job/job-totals.js b/server/job/job-totals.js index 9c825ada4..86a9c1bea 100644 --- a/server/job/job-totals.js +++ b/server/job/job-totals.js @@ -429,55 +429,6 @@ async function CalculateRatesTotals({ job, client }) { function CalculatePartsTotals(jobLines, parts_tax_rates) { const jl = jobLines.filter((jl) => !jl.removed); - // jl.forEach((line) => { - // //Some profile based estimates don't automatically add the discount to the line, some do. - // //Clean up the ones that don't to add it in. - - // //Apply a discount to the line if there is a profile discount, but it isn't added to the line itself. - // const partTax = parts_tax_rates[line.part_type]; - // if ( - // line.act_price > 0 && - // partTax && - // partTax.prt_discp && - // partTax.prt_discp > 0 - // ) { - // //apply a discount - // const discount = Dinero({ - // amount: Math.round(line.act_price * 100), - // }).percentage( - // Math.abs(partTax.prt_discp) > 1 - // ? partTax.prt_discp - // : partTax.prt_discp * 100 - // ); - // line.prt_dsmk_m = discount.toFormat("0.0"); - // line.prt_dsmk_p = partTax.prt_discp; - // line.act_price = Dinero({ - // amount: Math.round(line.act_price * 100), - // }) - // .subtract(discount) - // .toFormat("0.0"); - // } else if ( - // line.act_price > 0 && - // partTax && - // partTax.prt_mkupp && - // partTax.prt_mkupp > 0 - // ) { - // //apply a mark up - // const markup = Dinero({ - // amount: Math.round(line.act_price * 100), - // }).percentage( - // Math.abs(partTax.prt_mkupp) > 1 - // ? partTax.prt_mkupp - // : partTax.prt_mkupp * 100 - // ); - // line.prt_dsmk_m = markup.toFormat("0.0"); - // line.prt_dsmk_p = partTax.prt_mkupp; - // line.act_price = Dinero({ amount: Math.round(line.act_price * 100) }) - // .add(markup) - // .toFormat("0.0"); - // } - // }); - const ret = jl.reduce( (acc, value) => { switch (value.part_type) { @@ -738,7 +689,6 @@ function CalculateAdditional(job) { .add(ret.adjustments) //IO-813 Adjustment takes care of GST & PST at labor rate. .add(ret.towing) .add(ret.storage); - //.add(ret.pvrt); return ret; } @@ -767,23 +717,35 @@ function CalculateTaxesTotals(job, otherTotals) { job.parts_tax_rates.PAGQ || job.parts_tax_rates.PAGR); + const taxableAmounts = { + PAA: Dinero(), + PAN: Dinero(), + PAL: Dinero(), + PAR: Dinero(), + PAC: Dinero(), + PAG: Dinero(), + PAO: Dinero(), + PAS: Dinero(), + PAP: Dinero(), + PAM: Dinero(), + }; + + //For each line, determine if it's taxable, and if it is, add the line amount to the taxable amounts total. job.joblines .filter((jl) => !jl.removed) .forEach((val) => { if (!val.tax_part) return; if (!val.part_type && IsAdditionalCost(val)) { - additionalItemsTax = additionalItemsTax.add( - Dinero({ amount: Math.round((val.act_price || 0) * 100) }) - .multiply(val.part_qty || 0) - .percentage( - ((job.parts_tax_rates && - job.parts_tax_rates["PAN"] && - job.parts_tax_rates["PAN"].prt_tax_rt) || - 0) * 100 - ) + taxableAmounts.PAO = taxableAmounts.PAO.add( + Dinero({ amount: Math.round((val.act_price || 0) * 100) }).multiply( + val.part_qty || 0 + ) ); + } else if (!val.part_type) { + //Do nothing for now. } else { - statePartsTax = statePartsTax.add( + const typeOfPart = val.part_type === "PAM" ? "PAC" : val.part_type; + taxableAmounts[typeOfPart] = taxableAmounts[typeOfPart].add( Dinero({ amount: Math.round((val.act_price || 0) * 100) }) .multiply(val.part_qty || 0) .add( @@ -800,24 +762,98 @@ function CalculateTaxesTotals(job, otherTotals) { .multiply(val.prt_dsmk_p > 0 ? 1 : -1) : Dinero() ) - .percentage( - ((job.parts_tax_rates && - job.parts_tax_rates[val.part_type] && - job.parts_tax_rates[val.part_type].prt_tax_rt) || - (val.part_type && - val.part_type.startsWith("PAG") && - BackupGlassTax && - BackupGlassTax.prt_tax_rt) || - (!val.part_type && - val.db_ref === "900510" && - job.parts_tax_rates["PAN"] && - job.parts_tax_rates["PAN"].prt_tax_rt) || - 0) * 100 - ) ); } }); + //Taxable amounts should match with what is in the STL file. + //Now that we have the taxable amounts, apply the tax. + + const tieredTaxAmounts = { + ty1Tax: Dinero(), + ty2Tax: Dinero(), + ty3Tax: Dinero(), + ty4Tax: Dinero(), + ty5Tax: Dinero(), + ty6Tax: Dinero(), + }; + + const remainingTaxableAmounts = taxableAmounts; + console.log("Taxable Parts Totals", JSON.stringify(taxableAmounts, null, 2)); + + Object.keys(taxableAmounts).forEach((part_type) => { + //Check it's taxability in the PFP + try { + const pfp = job.parts_tax_rates; + + const typeOfPart = part_type === "PAM" ? "PAC" : part_type; + if (IsTrueOrYes(pfp[typeOfPart].prt_tax_in)) { + //At least one of these scenarios must be taxable. + for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { + if (IsTrueOrYes(pfp[typeOfPart][`prt_tx_in${tyCounter}`])) { + //i represents the tax number. If we got here, this type of tax is applicable. Now we need to add based on the thresholds. + for (let threshCounter = 1; threshCounter <= 5; threshCounter++) { + const thresholdAmount = + job.cieca_pft[`ty${tyCounter}_thres${threshCounter}`]; + const thresholdTaxRate = + job.cieca_pft[`ty${tyCounter}_rate${threshCounter}`]; + + // console.log( + // `P: ${typeOfPart} tyCount: ${tyCounter} theshCount: ${threshCounter} TaxRt: ${thresholdTaxRate}` + // ); + + let taxableAmountInThisThreshold; + if (thresholdAmount === 9999.99) { + // THis is the last threshold. Tax the entire remaining amount. + taxableAmountInThisThreshold = + remainingTaxableAmounts[typeOfPart]; + remainingTaxableAmounts[typeOfPart] = Dinero(); + } else { + if ( + thresholdAmount >= + remainingTaxableAmounts[typeOfPart].getAmount() / 100 + ) { + //This threshold is bigger than the remaining taxable balance. Add it all. + taxableAmountInThisThreshold = + remainingTaxableAmounts[typeOfPart]; + remainingTaxableAmounts[typeOfPart] = Dinero(); + } else { + //Take the size of the threshold from the remaining amount, tax it, and do it all over. + taxableAmountInThisThreshold = Dinero({ + amount: Math.round(taxableAmountInThisThreshold * 100), + }); + remainingTaxableAmounts[typeOfPart] = remainingTaxableAmounts[ + typeOfPart + ].subtract( + Dinero({ + amount: Math.round(taxableAmountInThisThreshold * 100), + }) + ); + } + } + + const taxAmountToAdd = + taxableAmountInThisThreshold.percentage(thresholdTaxRate); + + tieredTaxAmounts[`ty${tyCounter}Tax`] = + tieredTaxAmounts[`ty${tyCounter}Tax`].add(taxAmountToAdd); + } + } + } + } + } catch (error) { + console.log("Shit the bed."); + } + }); + + statePartsTax = statePartsTax + .add(tieredTaxAmounts.ty1Tax) + .add(tieredTaxAmounts.ty2Tax) + .add(tieredTaxAmounts.ty3Tax) + .add(tieredTaxAmounts.ty4Tax) + .add(tieredTaxAmounts.ty5Tax) + .add(tieredTaxAmounts.ty6Tax); + console.log("Tiered Taxes Total for Parts", statePartsTax.toFormat()); let laborTaxTotal = Dinero(); if (Object.keys(job.cieca_pfl).length > 0) { @@ -853,6 +889,8 @@ function CalculateTaxesTotals(job, otherTotals) { (job.tax_lbr_rt || 0) * 100 ); // THis is currently using the lbr tax rate from PFH not PFL. } + + console.log("Labor Tax Total", laborTaxTotal.toFormat()); let ret = { subtotal: subtotal, federal_tax: subtotal @@ -876,7 +914,7 @@ function CalculateTaxesTotals(job, otherTotals) { .add( otherTotals.additional.storage.percentage((job.tax_str_rt || 0) * 100) ) - .add(additionalItemsTax) + // .add(additionalItemsTax) .add( otherTotals.rates.mapa.hasMapaLine === false //If parts and materials were not added as lines, we must calculate the taxes on them. ? otherTotals.rates.mapa.total.percentage( @@ -964,3 +1002,7 @@ function ParseCalopCode(opcode) { if (!opcode) return []; return opcode.trim().split(" "); } + +function IsTrueOrYes(value) { + return value === true || value === "Y" || value === "y"; +} diff --git a/setadmin.js b/setadmin.js index c62f8c0a2..1f4f9dc0b 100644 --- a/setadmin.js +++ b/setadmin.js @@ -1,6 +1,6 @@ var { admin } = require("./server/firebase/firebase-handler"); -const uidToMakeAdmin = "yTvpfkcNnGckLd1JnoXC7bTdvtu1"; +const uidToMakeAdmin = "fIaZcVQQfUR12Fu14I2fyA5vXbp1"; admin .auth()