diff --git a/client/src/pages/dms/dms.container.jsx b/client/src/pages/dms/dms.container.jsx index 77c670e60..3a700a89e 100644 --- a/client/src/pages/dms/dms.container.jsx +++ b/client/src/pages/dms/dms.container.jsx @@ -43,10 +43,10 @@ const mapDispatchToProps = (dispatch) => ({ export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer); export const socket = SocketIO( - process.env.NODE_ENV === "production" - ? process.env.REACT_APP_AXIOS_BASE_API_URL - : window.location.origin, - // "http://localhost:4000", // for dev testing, + // process.env.NODE_ENV === "production" + // ? process.env.REACT_APP_AXIOS_BASE_API_URL + // : window.location.origin, + "http://localhost:4000", // for dev testing, { path: "/ws", withCredentials: true, diff --git a/job-totals-testing-util.js b/job-totals-testing-util.js index de955552b..9269048b5 100644 --- a/job-totals-testing-util.js +++ b/job-totals-testing-util.js @@ -20,7 +20,7 @@ require("dotenv").config({ async function RunTheTest() { const bodyshopids = ["a7ee1503-ee05-4a02-b80e-bdb11d1cc8ac"]; - const bearerToken = `Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImFkNWM1ZTlmNTdjOWI2NDYzYzg1ODQ1YTA4OTlhOWQ0MTI5MmM4YzMiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiUm9tZSBEZXZlbG9wbWVudCIsImh0dHBzOi8vaGFzdXJhLmlvL2p3dC9jbGFpbXMiOnsieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoidXNlciIsIngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsidXNlciJdLCJ4LWhhc3VyYS11c2VyLWlkIjoidDZZbTFORGxDRE9QWnIzRjliZ3VXSDRMaFNYMiJ9LCJpb2FkbWluIjp0cnVlLCJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vcm9tZS1wcm9kLTEiLCJhdWQiOiJyb21lLXByb2QtMSIsImF1dGhfdGltZSI6MTY5NTE1MDU0NywidXNlcl9pZCI6InQ2WW0xTkRsQ0RPUFpyM0Y5Ymd1V0g0TGhTWDIiLCJzdWIiOiJ0NlltMU5EbENET1BacjNGOWJndVdINExoU1gyIiwiaWF0IjoxNjk1ODUzNTI5LCJleHAiOjE2OTU4NTcxMjksImVtYWlsIjoicGF0cmlja0Byb21lLmRldiIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJwYXRyaWNrQHJvbWUuZGV2Il19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0.cLDqjTy7dyo3MYwRuWaDxFz0faqDqK5elCFy78qgr4IMhWQEZKdO1FIJoBmxuGn7qbJUfgSRettkYx5YcY3AzrzlTu7UbL6yz7yMyCUjIOF189OLhN-IZH8sHbyb4xvpP4GvYLkaEoBjTCvMaSW-9ycpM3uvYbjgCO81p2gGjo56E2TGoT8tfWE-NGO2nGv_-UacTrZWh_8CGijeZrC9QXeY3DSXTykRV1_xWA7UQNi8IeKphgXsVkOsQI6xC5fXCBWThOfx2RN5af36fU-b3aVXCq21M5y3tJg1IZbzlcYyyBw8Gc71wfp5bjKU92EPH8yCnbic7B5c5Yzk2ikWZg`; + const bearerToken = `Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjlhNTE5MDc0NmU5M2JhZTI0OWIyYWE3YzJhYTRlMzA2M2UzNDFlYzciLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiUm9tZSBEZXZlbG9wbWVudCIsImh0dHBzOi8vaGFzdXJhLmlvL2p3dC9jbGFpbXMiOnsieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoidXNlciIsIngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsidXNlciJdLCJ4LWhhc3VyYS11c2VyLWlkIjoidDZZbTFORGxDRE9QWnIzRjliZ3VXSDRMaFNYMiJ9LCJpb2FkbWluIjp0cnVlLCJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vcm9tZS1wcm9kLTEiLCJhdWQiOiJyb21lLXByb2QtMSIsImF1dGhfdGltZSI6MTY5NTkxNDQ5NywidXNlcl9pZCI6InQ2WW0xTkRsQ0RPUFpyM0Y5Ymd1V0g0TGhTWDIiLCJzdWIiOiJ0NlltMU5EbENET1BacjNGOWJndVdINExoU1gyIiwiaWF0IjoxNjk2NTQzMzgxLCJleHAiOjE2OTY1NDY5ODEsImVtYWlsIjoicGF0cmlja0Byb21lLmRldiIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJwYXRyaWNrQHJvbWUuZGV2Il19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0.nLjEuD_KOTbO2iEoTuweulEtQeZeu5NC7uG0QyaleSxrJ1AXt7r-qT6TcECfXxSpZunzdkl4Tiz6wXqSEcOEPOOv1TZBSCNogTNOF_LCJCn6e8jHWE1ry5m4qQ4wUb_DELauFdZcmNJBHShwcGbMMbApjp6YZ9g7aJNYpP3LtRXi_zBHX_Pmf2sB9RuMyDOSfHGBlt-g-5c6TtOeRXWLY92MOfV_X-1bqKV-honnpZwi3Ht_g6z3nUY6p2VQsD2oy7jjuRPis3P9E_rym5UVIIpF5zEiLb3RaOnHcI_gmX6LFXx5roLIwBjwALQfeE5iRakeylxkgIeuwcjCiRJbhA`; const { jobs } = await client.request( gql` query GET_JOBS($bodyshopids: [uuid!]!) { @@ -44,7 +44,11 @@ async function RunTheTest() { for (const [index, job] of jobs.entries()) { process.stdout.cursorTo(0); - process.stdout.write(`Processing job ${index + 1} of ${jobs.length}`); + process.stdout.write( + `Processing job ${index + 1} of ${jobs.length}. Failed jobs: ${ + results.filter((r) => r.result !== "PASS").length + }` + ); try { await axios.post( diff --git a/server/accounting/qb-receivables-lines.js b/server/accounting/qb-receivables-lines.js index 2463390af..b69e1fa7d 100644 --- a/server/accounting/qb-receivables-lines.js +++ b/server/accounting/qb-receivables-lines.js @@ -420,6 +420,7 @@ exports.default = function ({ Amount: Dinero(jobs_by_pk.job_totals.additional.towing).toFormat( DineroQbFormat ), + SalesItemLineDetail: { ...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } @@ -573,6 +574,68 @@ exports.default = function ({ //Add tax lines const job_totals = jobs_by_pk.job_totals; + //Handle insurance profile adjustments + Object.keys(job_totals.parts.adjustments).forEach((key) => { + if (qbo) { + //Going to always assume that we need to apply GST and PST for labor. + const taxAccountCode = findTaxCode( + { + local: false, + federal: process.env.COUNTRY === "USA" ? false : true, + state: jobs_by_pk.state_tax_rate === 0 ? false : true, + }, + bodyshop.md_responsibility_centers.sales_tax_codes + ); + const account = responsibilityCenters.profits.find( + (c) => c.name === responsibilityCenters.defaults.profits[key] + ); + const QboTaxId = + process.env.COUNTRY === "USA" + ? CheckQBOUSATaxID({ + // jobline: jobline, + job: jobs_by_pk, + type: "storage", + }) + : taxCodes[taxAccountCode]; + InvoiceLineAdd.push({ + DetailType: "SalesItemLineDetail", + Amount: Dinero(job_totals.parts.adjustments[key]).toFormat( + DineroQbFormat + ), + Description: `${account.accountdesc} - Adjustment`, + SalesItemLineDetail: { + ...(jobs_by_pk.class + ? { ClassRef: { value: classes[jobs_by_pk.class] } } + : {}), + ItemRef: { + value: items[account.accountitem], + }, + TaxCodeRef: { + value: QboTaxId, + }, + Qty: 1, + }, + }); + } else { + InvoiceLineAdd.push({ + ItemRef: { + FullName: responsibilityCenters.profits.find( + (c) => c.name === responsibilityCenters.defaults.profits[key] + ).accountitem, + }, + Desc: "Storage", + Quantity: 1, + Amount: Dinero(job_totals.parts.adjustments[key]).toFormat( + DineroQbFormat + ), + SalesTaxCodeRef: { + FullName: + bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON", + }, + }); + } + }); + const federal_tax = Dinero(job_totals.totals.federal_tax); const QboTaxId = process.env.COUNTRY === "USA" diff --git a/server/cdk/cdk-calculate-allocations.js b/server/cdk/cdk-calculate-allocations.js index 2066a04aa..042e09abf 100644 --- a/server/cdk/cdk-calculate-allocations.js +++ b/server/cdk/cdk-calculate-allocations.js @@ -25,58 +25,37 @@ exports.default = async function (socket, jobid) { const { bodyshop } = job; const taxAllocations = { - // local: { - // center: bodyshop.md_responsibility_centers.taxes.local.name, - // sale: Dinero(job.job_totals.totals.local_tax), - // cost: Dinero(), - // profitCenter: bodyshop.md_responsibility_centers.taxes.local, - // costCenter: bodyshop.md_responsibility_centers.taxes.local, - // }, - // state: { - // center: bodyshop.md_responsibility_centers.taxes.state.name, - // sale: Dinero(job.job_totals.totals.state_tax), - // cost: Dinero(), - // profitCenter: bodyshop.md_responsibility_centers.taxes.state, - // costCenter: bodyshop.md_responsibility_centers.taxes.state, - // }, - // federal: { - // center: bodyshop.md_responsibility_centers.taxes.federal.name, - // sale: Dinero(job.job_totals.totals.federal_tax), - // cost: Dinero(), - // profitCenter: bodyshop.md_responsibility_centers.taxes.federal, - // costCenter: bodyshop.md_responsibility_centers.taxes.federal, - // }, tax_ty1: { center: bodyshop.md_responsibility_centers.taxes[`tax_ty1`].name, - sale: Dinero(job_totals.totals.us_sales_tax_breakdown[`ty1Tax`]), + sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty1Tax`]), cost: Dinero(), profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty1`], costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty1`], }, tax_ty2: { center: bodyshop.md_responsibility_centers.taxes[`tax_ty2`].name, - sale: Dinero(job_totals.totals.us_sales_tax_breakdown[`ty2Tax`]), + sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty2Tax`]), cost: Dinero(), profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty2`], costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty2`], }, tax_ty3: { center: bodyshop.md_responsibility_centers.taxes[`tax_ty3`].name, - sale: Dinero(job_totals.totals.us_sales_tax_breakdown[`ty3Tax`]), + sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty3Tax`]), cost: Dinero(), profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty3`], costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty3`], }, tax_ty4: { center: bodyshop.md_responsibility_centers.taxes[`tax_ty4`].name, - sale: Dinero(job_totals.totals.us_sales_tax_breakdown[`ty4Tax`]), + sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty4Tax`]), cost: Dinero(), profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty4`], costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty4`], }, tax_ty5: { center: bodyshop.md_responsibility_centers.taxes[`tax_ty5`].name, - sale: Dinero(job_totals.totals.us_sales_tax_breakdown[`ty5Tax`]), + sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty5Tax`]), cost: Dinero(), profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty5`], costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty5`], @@ -363,6 +342,30 @@ exports.default = async function (socket, jobid) { } } + //profile level adjustments + Object.keys(job.job_totals.parts.adjustments).forEach((key) => { + const accountName = selectedDmsAllocationConfig.profits[key]; + + const otherAccount = bodyshop.md_responsibility_centers.profits.find( + (c) => c.name === accountName + ); + + if (otherAccount) { + if (!profitCenterHash[accountName]) + profitCenterHash[accountName] = Dinero(); + + profitCenterHash[accountName] = profitCenterHash[accountName].add( + Dinero(job.job_totals.parts.adjustments[key]) + ); + } else { + CdkBase.createLogEvent( + socket, + "ERROR", + `Error encountered in CdkCalculateAllocations. Unable to find adjustment account. ${error}` + ); + } + }); + const jobAllocations = _.union( Object.keys(profitCenterHash), Object.keys(costCenterHash) diff --git a/server/job/job-totals.js b/server/job/job-totals.js index 522ad31b5..be0c70c1b 100644 --- a/server/job/job-totals.js +++ b/server/job/job-totals.js @@ -521,6 +521,7 @@ function CalculatePartsTotals(jobLines, parts_tax_rates, job) { .percentage(Math.abs(value.prt_dsmk_p || 0)) .multiply(value.prt_dsmk_p > 0 ? 1 : -1) : Dinero(); + return { ...acc, parts: { @@ -595,19 +596,8 @@ function CalculatePartsTotals(jobLines, parts_tax_rates, job) { } ); - //Apply insurance based parts discuounts/markups. - let adjustments = { - PAA: Dinero(), - PAC: Dinero(), - PAG: Dinero(), - PAL: Dinero(), - PAN: Dinero(), - PAO: Dinero(), - PAP: Dinero(), - PAR: Dinero(), - PAS: Dinero(), - PAT: Dinero(), - }; + //Apply insurance based parts discounts/markups. + let adjustments = {}; //Track all adjustments that need to be made. const linesToAdjustForDiscount = []; @@ -645,9 +635,6 @@ function CalculatePartsTotals(jobLines, parts_tax_rates, job) { markup = ret.parts.list[key].total.percentage(markupRate); } } - let adjustment = disc.add(markup); - adjustments[key] = adjustment; - const correspondingCiecaStlTotalLine = job.cieca_stl?.data.find( (c) => c.ttl_typecd === key ); @@ -661,47 +648,13 @@ function CalculatePartsTotals(jobLines, parts_tax_rates, job) { correspondingCiecaStlTotalLine.ttl_amt * 100 ) > 1 ) { - // Update the total. - console.log( - key, - ret.parts.list[key]?.total.getAmount(), - correspondingCiecaStlTotalLine?.ttl_amt - ); - //Find the corresponding lines. Update the discount/markup for them. - - console.warn("There's a difference! Type: ", key); - let totalDiscountToAdjustBy = Dinero(); - job.joblines.forEach((jobline) => { - //Modify the line in place to add the mark up/discount. - if (jobline.part_type === key) { - const discountAmountDinero = Dinero({ - amount: Math.round(jobline.act_price * 100), - }).percentage(discountRate); - - const discountAmount = parseFloat( - discountAmountDinero.toFormat("0.00") - ); - totalDiscountToAdjustBy = - totalDiscountToAdjustBy.add(discountAmountDinero); - jobline.prt_dsmk_m = discountAmount * -1; - jobline.prt_dsmk_p = discountRate * -1; - - linesToAdjustForDiscount.push(jobline); - } - }); - // ret.parts.list[key].total = ret.parts.list[key]?.total.subtract( - // totalDiscountToAdjustBy - // ); - ret.parts.prt_dsmk_total = ret.parts.prt_dsmk_total.subtract( - totalDiscountToAdjustBy - ); - ret.parts.subtotal = ret.parts.subtotal.subtract(totalDiscountToAdjustBy); - ret.parts.total = ret.parts.total.subtract(totalDiscountToAdjustBy); + let adjustment = disc.add(markup); + adjustments[key] = adjustment; + ret.parts.subtotal = ret.parts.subtotal.add(adjustment); + ret.parts.total = ret.parts.total.add(adjustment); } }); - UpdateJobLines(linesToAdjustForDiscount.filter((l) => l.prt_dsmk_m !== 0)); - return { adjustments, parts: { @@ -847,286 +800,240 @@ function CalculateTaxesTotals(job, otherTotals) { STOR: Dinero(), }; - if ( - job.parts_tax_rates.PAN.prt_tx_ty1 && - job.parts_tax_rates.PAN.prt_tx_ty1 !== "" - ) { - //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)) { - 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 { - const typeOfPart = val.part_type; - taxableAmounts[typeOfPart] = taxableAmounts[typeOfPart].add( - Dinero({ amount: Math.round((val.act_price || 0) * 100) }) - .multiply(val.part_qty || 0) - .add( - val.prt_dsmk_m && - val.prt_dsmk_m !== 0 && - DiscountNotAlreadyCounted(val, job.joblines) // DO WE NEED TO COUNT PFP DISCOUNT HERE? - ? val.prt_dsmk_m - ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) - : Dinero({ - amount: Math.round(val.act_price * 100), - }) - .multiply(val.part_qty || 0) - .percentage(Math.abs(val.prt_dsmk_p || 0)) - .multiply(val.prt_dsmk_p > 0 ? 1 : -1) - : 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)) { + 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 { + const typeOfPart = val.part_type; - //Check in the PFL file which types of labor are taxable. Add the amount that is considered taxable to the taxable amounts total. - Object.keys(taxableAmounts) - .filter((key) => key.startsWith("LA")) - .map((key) => { - const isLaborTypeTaxable = job.cieca_pfl[key]?.lbr_tax_in; - if (isLaborTypeTaxable) { - taxableAmounts[key] = taxableAmounts[key].add( - otherTotals.rates[key.toLowerCase()].total - ); - } - }); + const discMarkupAmount = + val.prt_dsmk_m && + val.prt_dsmk_m !== 0 && + DiscountNotAlreadyCounted(val, job.joblines) // DO WE NEED TO COUNT PFP DISCOUNT HERE? + ? val.prt_dsmk_m + ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) + : Dinero({ + amount: Math.round(val.act_price * 100), + }) + .multiply(val.part_qty || 0) + .percentage(Math.abs(val.prt_dsmk_p || 0)) + .multiply(val.prt_dsmk_p > 0 ? 1 : -1) + : Dinero(); - Object.keys(taxableAmounts) - .filter((key) => key.startsWith("MA")) - .map((key) => { - const isTypeTaxable = job.materials[key]?.tax_ind; - if (isTypeTaxable) { - taxableAmounts[key] = taxableAmounts[key].add( - otherTotals.rates[key.toLowerCase()].total - ); - } - }); - //Add towing and storage taxable amounts - const stlTowing = job.cieca_stl?.data.find((c) => c.ttl_typecd === "OTTW"); - const stlStorage = job.cieca_stl?.data.find((c) => c.ttl_typecd === "OTST"); - - if (stlTowing) - taxableAmounts.TOW = Dinero({ - amount: Math.round(stlTowing.t_amt * 100), - }); - if (stlStorage) - taxableAmounts.TOW = Dinero({ - amount: Math.round(stlStorage.t_amt * 100), - }); - - // console.log("*** Taxable Amounts***"); - // console.table(JSON.parse(JSON.stringify(taxableAmounts))); - - //For the taxable amounts, figure out which tax type applies. - //Then sum up the total of that tax type and then calculate the thresholds. - - const taxableAmountsByTier = { - ty1Tax: Dinero(), - ty2Tax: Dinero(), - ty3Tax: Dinero(), - ty4Tax: Dinero(), - ty5Tax: Dinero(), - ty6Tax: Dinero(), - }; - const totalTaxByTier = { - ty1Tax: Dinero(), - ty2Tax: Dinero(), - ty3Tax: Dinero(), - ty4Tax: Dinero(), - ty5Tax: Dinero(), - ty6Tax: Dinero(), - }; - - const pfp = job.parts_tax_rates; - const pfl = job.cieca_pfl; - const pfm = job.materials; - const pfo = job.cieca_pfo; - Object.keys(taxableAmounts).map((key) => { - try { - if (key.startsWith("PA")) { - const typeOfPart = key; // === "PAM" ? "PAC" : key; - //At least one of these scenarios must be taxable. - for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { - if (IsTrueOrYes(pfp[typeOfPart][`prt_tx_in${tyCounter}`])) { - //This amount is taxable for this type. - taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[ - `ty${tyCounter}Tax` - ].add(taxableAmounts[typeOfPart]); - } - } - } else if (key.startsWith("MA")) { - //Materials Handling - for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { - if (IsTrueOrYes(pfm[key][`mat_tx_in${tyCounter}`])) { - //This amount is taxable for this type. - taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[ - `ty${tyCounter}Tax` - ].add(taxableAmounts[key]); - } - } - } else if (key.startsWith("LA")) { - //Labor. - for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { - if (IsTrueOrYes(pfl[key][`lbr_tx_in${tyCounter}`])) { - //This amount is taxable for this type. - taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[ - `ty${tyCounter}Tax` - ].add(taxableAmounts[key]); - } - } - } else if (key === "TOW") { - for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { - if (IsTrueOrYes(pfo[`tow_t_in${tyCounter}`])) { - //This amount is taxable for this type. - taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[ - `ty${tyCounter}Tax` - ].add(taxableAmounts[key]); - } - } - } else if (key === "STOR") { - for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { - if (IsTrueOrYes(pfo[`stor_t_in${tyCounter}`])) { - //This amount is taxable for this type. - taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[ - `ty${tyCounter}Tax` - ].add(taxableAmounts[key]); - } - } - } - } catch (error) { - console.error("Key with issue", key); + const partAmount = Dinero({ + amount: Math.round((val.act_price || 0) * 100), + }) + .multiply(val.part_qty || 0) + .add(discMarkupAmount); + taxableAmounts[typeOfPart] = taxableAmounts[typeOfPart].add(partAmount); } }); - const remainingTaxableAmounts = taxableAmountsByTier; - // console.log("*** Taxable Amounts by Tier***"); - // console.table(JSON.parse(JSON.stringify(taxableAmountsByTier))); + //Check in the PFL file which types of labor are taxable. Add the amount that is considered taxable to the taxable amounts total. + Object.keys(taxableAmounts) + .filter((key) => key.startsWith("LA")) + .map((key) => { + const isLaborTypeTaxable = job.cieca_pfl[key]?.lbr_tax_in; + if (isLaborTypeTaxable) { + taxableAmounts[key] = taxableAmounts[key].add( + otherTotals.rates[key.toLowerCase()].total + ); + } + }); - Object.keys(taxableAmountsByTier).forEach((taxTierKey) => { - try { - let tyCounter = taxTierKey[2]; //Get the number from the key. - //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 = parseFloat( - job.cieca_pft[`ty${tyCounter}_thres${threshCounter}`] - ); - const thresholdTaxRate = parseFloat( - job.cieca_pft[`ty${tyCounter}_rate${threshCounter}`] - ); + Object.keys(taxableAmounts) + .filter((key) => key.startsWith("MA")) + .map((key) => { + const isTypeTaxable = job.materials[key]?.tax_ind; + if (isTypeTaxable) { + taxableAmounts[key] = taxableAmounts[key].add( + otherTotals.rates[key.toLowerCase()].total + ); + } + }); + //Add towing and storage taxable amounts + const stlTowing = job.cieca_stl?.data.find((c) => c.ttl_typecd === "OTTW"); + const stlStorage = job.cieca_stl?.data.find((c) => c.ttl_typecd === "OTST"); - let taxableAmountInThisThreshold; - if (thresholdAmount === 9999.99) { - // THis is the last threshold. Tax the entire remaining amount. + if (stlTowing) + taxableAmounts.TOW = Dinero({ + amount: Math.round(stlTowing.t_amt * 100), + }); + if (stlStorage) + taxableAmounts.TOW = Dinero({ + amount: Math.round(stlStorage.t_amt * 100), + }); + + const pfp = job.parts_tax_rates; + + //For any profile level markups/discounts, add them in now as well. + Object.keys(otherTotals.parts.adjustments).forEach((key) => { + const adjustmentAmount = otherTotals.parts.adjustments[key]; + if (adjustmentAmount.getAmount() !== 0 && pfp[key]?.prt_tax_in) { + taxableAmounts[key] = taxableAmounts[key].add(adjustmentAmount); + } + }); + + // console.log("*** Taxable Amounts***"); + // console.table(JSON.parse(JSON.stringify(taxableAmounts))); + + //For the taxable amounts, figure out which tax type applies. + //Then sum up the total of that tax type and then calculate the thresholds. + + const taxableAmountsByTier = { + ty1Tax: Dinero(), + ty2Tax: Dinero(), + ty3Tax: Dinero(), + ty4Tax: Dinero(), + ty5Tax: Dinero(), + ty6Tax: Dinero(), + }; + const totalTaxByTier = { + ty1Tax: Dinero(), + ty2Tax: Dinero(), + ty3Tax: Dinero(), + ty4Tax: Dinero(), + ty5Tax: Dinero(), + ty6Tax: Dinero(), + }; + + const pfl = job.cieca_pfl; + const pfm = job.materials; + const pfo = job.cieca_pfo; + Object.keys(taxableAmounts).map((key) => { + try { + if (key.startsWith("PA")) { + const typeOfPart = key; // === "PAM" ? "PAC" : key; + //At least one of these scenarios must be taxable. + for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { + if (IsTrueOrYes(pfp[typeOfPart][`prt_tx_in${tyCounter}`])) { + //This amount is taxable for this type. + taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[ + `ty${tyCounter}Tax` + ].add(taxableAmounts[typeOfPart]); + } + } + } else if (key.startsWith("MA")) { + //Materials Handling + for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { + if (IsTrueOrYes(pfm[key][`mat_tx_in${tyCounter}`])) { + //This amount is taxable for this type. + taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[ + `ty${tyCounter}Tax` + ].add(taxableAmounts[key]); + } + } + } else if (key.startsWith("LA")) { + //Labor. + for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { + if (IsTrueOrYes(pfl[key][`lbr_tx_in${tyCounter}`])) { + //This amount is taxable for this type. + taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[ + `ty${tyCounter}Tax` + ].add(taxableAmounts[key]); + } + } + } else if (key === "TOW") { + for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { + if (IsTrueOrYes(pfo[`tow_t_in${tyCounter}`])) { + //This amount is taxable for this type. + taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[ + `ty${tyCounter}Tax` + ].add(taxableAmounts[key]); + } + } + } else if (key === "STOR") { + for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { + if (IsTrueOrYes(pfo[`stor_t_in${tyCounter}`])) { + //This amount is taxable for this type. + taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[ + `ty${tyCounter}Tax` + ].add(taxableAmounts[key]); + } + } + } + } catch (error) { + console.error("Key with issue", key); + } + }); + + const remainingTaxableAmounts = taxableAmountsByTier; + // console.log("*** Taxable Amounts by Tier***"); + // console.table(JSON.parse(JSON.stringify(taxableAmountsByTier))); + + Object.keys(taxableAmountsByTier).forEach((taxTierKey) => { + try { + let tyCounter = taxTierKey[2]; //Get the number from the key. + //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 = parseFloat( + job.cieca_pft[`ty${tyCounter}_thres${threshCounter}`] + ); + const thresholdTaxRate = parseFloat( + job.cieca_pft[`ty${tyCounter}_rate${threshCounter}`] + ); + + let taxableAmountInThisThreshold; + if (thresholdAmount === 9999.99) { + // THis is the last threshold. Tax the entire remaining amount. + taxableAmountInThisThreshold = remainingTaxableAmounts[taxTierKey]; + remainingTaxableAmounts[taxTierKey] = Dinero(); + } else { + if ( + thresholdAmount >= + remainingTaxableAmounts[taxTierKey].getAmount() / 100 + ) { + //This threshold is bigger than the remaining taxable balance. Add it all. taxableAmountInThisThreshold = remainingTaxableAmounts[taxTierKey]; remainingTaxableAmounts[taxTierKey] = Dinero(); } else { - if ( - thresholdAmount >= - remainingTaxableAmounts[taxTierKey].getAmount() / 100 - ) { - //This threshold is bigger than the remaining taxable balance. Add it all. - taxableAmountInThisThreshold = - remainingTaxableAmounts[taxTierKey]; - remainingTaxableAmounts[taxTierKey] = Dinero(); - } else { - //Take the size of the threshold from the remaining amount, tax it, and do it all over. - taxableAmountInThisThreshold = Dinero({ - amount: Math.round(thresholdAmount * 100), - }); - remainingTaxableAmounts[taxTierKey] = remainingTaxableAmounts[ - taxTierKey - ].subtract( - Dinero({ - amount: Math.round(taxableAmountInThisThreshold * 100), - }) - ); - } + //Take the size of the threshold from the remaining amount, tax it, and do it all over. + taxableAmountInThisThreshold = Dinero({ + amount: Math.round(thresholdAmount * 100), + }); + remainingTaxableAmounts[taxTierKey] = remainingTaxableAmounts[ + taxTierKey + ].subtract( + Dinero({ + amount: Math.round(taxableAmountInThisThreshold * 100), + }) + ); } - - const taxAmountToAdd = - taxableAmountInThisThreshold.percentage(thresholdTaxRate); - - totalTaxByTier[taxTierKey] = - totalTaxByTier[taxTierKey].add(taxAmountToAdd); } - } catch (error) { - console.error("PFP Calculation error", error); + + const taxAmountToAdd = + taxableAmountInThisThreshold.percentage(thresholdTaxRate); + + totalTaxByTier[taxTierKey] = + totalTaxByTier[taxTierKey].add(taxAmountToAdd); } - }); + } catch (error) { + console.error("PFP Calculation error", error); + } + }); - // console.log("*** Total Tax by Tier Amounts***"); - // console.table(JSON.parse(JSON.stringify(totalTaxByTier))); + // console.log("*** Total Tax by Tier Amounts***"); + // console.table(JSON.parse(JSON.stringify(totalTaxByTier))); - statePartsTax = statePartsTax - .add(totalTaxByTier.ty1Tax) - .add(totalTaxByTier.ty2Tax) - .add(totalTaxByTier.ty3Tax) - .add(totalTaxByTier.ty4Tax) - .add(totalTaxByTier.ty5Tax) - .add(totalTaxByTier.ty6Tax); - us_sales_tax_breakdown = totalTaxByTier; - //console.log("Tiered Taxes Total for Parts/Labor", statePartsTax.toFormat()); - } else { - //Use the old thing. - 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 - ) - ); - } else { - statePartsTax = statePartsTax.add( - Dinero({ amount: Math.round((val.act_price || 0) * 100) }) - .multiply(val.part_qty || 0) - .add( - val.prt_dsmk_m && - val.prt_dsmk_m !== 0 && - DiscountNotAlreadyCounted(val, job.joblines) - ? val.prt_dsmk_m - ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) - : Dinero({ - amount: Math.round(val.act_price * 100), - }) - .multiply(val.part_qty || 0) - .percentage(Math.abs(val.prt_dsmk_p || 0)) - .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 - ) - ); - } - }); - } + statePartsTax = statePartsTax + .add(totalTaxByTier.ty1Tax) + .add(totalTaxByTier.ty2Tax) + .add(totalTaxByTier.ty3Tax) + .add(totalTaxByTier.ty4Tax) + .add(totalTaxByTier.ty5Tax) + .add(totalTaxByTier.ty6Tax); + us_sales_tax_breakdown = totalTaxByTier; + //console.log("Tiered Taxes Total for Parts/Labor", statePartsTax.toFormat()); let laborTaxTotal = Dinero(); @@ -1191,32 +1098,6 @@ exports.default = Totals; function DiscountNotAlreadyCounted(jobline, joblines) { return false; - //CCC already factors in the discount. If the difference between the 2 is exactly the discount, it's all good. - if ( - Math.round( - (jobline.prt_dsmk_m / (jobline.act_price - jobline.prt_dsmk_m)) * 100 - ) === Math.abs(jobline.prt_dsmk_p) - ) { - return false; - } - - if ( - jobline.db_price !== jobline.act_price && - jobline.db_price - jobline.act_price - Math.abs(jobline.prt_dsmk_m) < 0.02 - ) { - return false; - } - - if ( - //If it's not a discount line, then it definitely hasn't been counted yet. - jobline.db_ref !== "900510" && - jobline.db_ref !== "900511" - ) - return true; - - const ParentLine = joblines.find((j) => j.unq_seq === jobline.line_ref); - - return ParentLine && !(ParentLine.prt_dsmk_m && ParentLine.prt_dsmk_m !== 0); } exports.DiscountNotAlreadyCounted = DiscountNotAlreadyCounted;