From 56c1b6f992ed9cad4ab70dece9f8ae8bde8c7ba3 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Mon, 11 Sep 2023 09:07:38 -0700 Subject: [PATCH 01/23] IO-2368 Confirm that there are successful transaction --- .../jobs-close-export-button.component.jsx | 2 +- .../jobs-export-all-button/jobs-export-all-button.component.jsx | 2 +- .../payable-export-all-button.component.jsx | 2 +- .../payable-export-button/payable-export-button.component.jsx | 2 +- .../payment-export-button/payment-export-button.component.jsx | 2 +- .../payments-export-all-button.component.jsx | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/src/components/jobs-close-export-button/jobs-close-export-button.component.jsx b/client/src/components/jobs-close-export-button/jobs-close-export-button.component.jsx index 795e60110..8bbda03e1 100644 --- a/client/src/components/jobs-close-export-button/jobs-close-export-button.component.jsx +++ b/client/src/components/jobs-close-export-button/jobs-close-export-button.component.jsx @@ -192,7 +192,7 @@ export function JobsCloseExportButton({ }); } } - if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { + if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) { notification.open({ type: "success", key: "jobsuccessexport", diff --git a/client/src/components/jobs-export-all-button/jobs-export-all-button.component.jsx b/client/src/components/jobs-export-all-button/jobs-export-all-button.component.jsx index 15fdb32b4..3204d7311 100644 --- a/client/src/components/jobs-export-all-button/jobs-export-all-button.component.jsx +++ b/client/src/components/jobs-export-all-button/jobs-export-all-button.component.jsx @@ -190,7 +190,7 @@ export function JobsExportAllButton({ }); } } - if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { + if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) { notification.open({ type: "success", key: "jobsuccessexport", diff --git a/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx b/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx index 723fd0526..c448e4f4a 100644 --- a/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx +++ b/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx @@ -192,7 +192,7 @@ export function PayableExportAll({ }); } } - if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { + if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) { notification.open({ type: "success", key: "billsuccessexport", diff --git a/client/src/components/payable-export-button/payable-export-button.component.jsx b/client/src/components/payable-export-button/payable-export-button.component.jsx index 1836acb18..c2970b8d8 100644 --- a/client/src/components/payable-export-button/payable-export-button.component.jsx +++ b/client/src/components/payable-export-button/payable-export-button.component.jsx @@ -185,7 +185,7 @@ export function PayableExportButton({ }); } } - if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { + if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) { notification.open({ type: "success", key: "billsuccessexport", diff --git a/client/src/components/payment-export-button/payment-export-button.component.jsx b/client/src/components/payment-export-button/payment-export-button.component.jsx index 91f9ccc88..931254b92 100644 --- a/client/src/components/payment-export-button/payment-export-button.component.jsx +++ b/client/src/components/payment-export-button/payment-export-button.component.jsx @@ -191,7 +191,7 @@ export function PaymentExportButton({ }); } } - if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { + if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) { notification.open({ type: "success", key: "paymentsuccessexport", diff --git a/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx b/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx index 721e906d5..0b2e6cd7b 100644 --- a/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx +++ b/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx @@ -179,7 +179,7 @@ export function PaymentsExportAllButton({ }); } } - if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { + if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) { notification.open({ type: "success", key: "paymentsuccessexport", From 53e3b3fa037a110f24be84dbdae6e9382185bf58 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Mon, 11 Sep 2023 11:15:50 -0700 Subject: [PATCH 02/23] IO-2395 Payment Expansion Formatting --- .../payment-expanded-row.component.jsx | 19 ++++++++++++------- client/src/graphql/jobs.queries.js | 2 +- client/src/translations/en_us/common.json | 1 + client/src/translations/es/common.json | 1 + client/src/translations/fr/common.json | 1 + 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/client/src/components/payment-expanded-row/payment-expanded-row.component.jsx b/client/src/components/payment-expanded-row/payment-expanded-row.component.jsx index 6266457a7..a96683b70 100644 --- a/client/src/components/payment-expanded-row/payment-expanded-row.component.jsx +++ b/client/src/components/payment-expanded-row/payment-expanded-row.component.jsx @@ -1,15 +1,17 @@ -import React, { useState } from "react"; import { useMutation, useQuery } from "@apollo/client"; +import { Button, Descriptions, InputNumber, Modal, notification } from "antd"; +import axios from "axios"; +import moment from "moment"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; import { GET_REFUNDABLE_AMOUNT_BY_JOBID, INSERT_PAYMENT_RESPONSE, QUERY_PAYMENT_RESPONSE_BY_PAYMENT_ID, } from "../../graphql/payment_response.queries"; -import { Button, Descriptions, InputNumber, Modal, notification } from "antd"; -import moment from "moment"; -import axios from "axios"; import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries"; -import { useTranslation } from "react-i18next"; +import CurrencyFormatter from "../../utils/CurrencyFormatter"; +import { DateTimeFormatter } from "../../utils/DateFormatter"; const { confirm } = Modal; @@ -137,10 +139,10 @@ const PaymentExpandedRowComponent = ({ record, bodyshop }) => { {payment_response?.response?.nameOnCard ?? ""} - {record.amount} + {record.amount} - {moment(record.created_at).format("YYYY-MM-DD HH:mm:ss")} + {{record.created_at}} {record.transactionid} @@ -151,6 +153,9 @@ const PaymentExpandedRowComponent = ({ record, bodyshop }) => { {record.type} + + {record.paymentnum} + {payment_response && ( Date: Mon, 11 Sep 2023 11:45:35 -0700 Subject: [PATCH 03/23] Add CIECA PFT to job. --- hasura/metadata/tables.yaml | 3 +++ .../down.sql | 4 ++++ .../up.sql | 2 ++ 3 files changed, 9 insertions(+) create mode 100644 hasura/migrations/1694457793769_alter_table_public_jobs_add_column_cieca_pft/down.sql create mode 100644 hasura/migrations/1694457793769_alter_table_public_jobs_add_column_cieca_pft/up.sql diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml index 7b3e5a4b7..1d05bd8fd 100644 --- a/hasura/metadata/tables.yaml +++ b/hasura/metadata/tables.yaml @@ -3274,6 +3274,7 @@ - cat_no - category - cieca_pfl + - cieca_pft - cieca_stl - cieca_ttl - ciecaid @@ -3541,6 +3542,7 @@ - cat_no - category - cieca_pfl + - cieca_pft - cieca_stl - cieca_ttl - ciecaid @@ -3819,6 +3821,7 @@ - cat_no - category - cieca_pfl + - cieca_pft - cieca_stl - cieca_ttl - ciecaid diff --git a/hasura/migrations/1694457793769_alter_table_public_jobs_add_column_cieca_pft/down.sql b/hasura/migrations/1694457793769_alter_table_public_jobs_add_column_cieca_pft/down.sql new file mode 100644 index 000000000..2d4c20d18 --- /dev/null +++ b/hasura/migrations/1694457793769_alter_table_public_jobs_add_column_cieca_pft/down.sql @@ -0,0 +1,4 @@ +-- Could not auto-generate a down migration. +-- Please write an appropriate down migration for the SQL below: +-- alter table "public"."jobs" add column "cieca_pft" jsonb +-- null default jsonb_build_object(); diff --git a/hasura/migrations/1694457793769_alter_table_public_jobs_add_column_cieca_pft/up.sql b/hasura/migrations/1694457793769_alter_table_public_jobs_add_column_cieca_pft/up.sql new file mode 100644 index 000000000..5baeecbb8 --- /dev/null +++ b/hasura/migrations/1694457793769_alter_table_public_jobs_add_column_cieca_pft/up.sql @@ -0,0 +1,2 @@ +alter table "public"."jobs" add column "cieca_pft" jsonb + null default jsonb_build_object(); From 6a9e871b0846794492ee0dfe0ebece46afe84b74 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Mon, 11 Sep 2023 20:44:12 -0700 Subject: [PATCH 04/23] 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() From 536f8d9cb977803ea4c37510c1f9dc17ccbfc3b6 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Tue, 12 Sep 2023 14:16:01 -0700 Subject: [PATCH 05/23] Add UI elements for part tax and improve calculations. --- .../jobs-detail-rates.parts.component.jsx | 462 +++++++++++++++++- .../jobs-detail.page.component.jsx | 12 + client/src/translations/en_us/common.json | 2 +- job-totals-testing-util.js | 8 +- server/job/job-totals.js | 160 ++++-- 5 files changed, 587 insertions(+), 57 deletions(-) diff --git a/client/src/components/jobs-detail-rates/jobs-detail-rates.parts.component.jsx b/client/src/components/jobs-detail-rates/jobs-detail-rates.parts.component.jsx index fcd56740d..2cd2b8917 100644 --- a/client/src/components/jobs-detail-rates/jobs-detail-rates.parts.component.jsx +++ b/client/src/components/jobs-detail-rates/jobs-detail-rates.parts.component.jsx @@ -68,11 +68,51 @@ export function JobsDetailRatesParts({ }, ]} > - + ); }} + + + + + + + + + + + + + + + - + ); }} + + + + + + + + + + + + + + + - + ); }} + + + + + + + + + + + + + + + - + ); }} + + + + + + + + + + + + + + + - + ); }} + + + + + + + + + + + + + + + - + ); }} + + + + + + + + + + + + + + + - + ); }} + + + + + + + + + + + + + + + - + ); }} + + + + + + + + + + + + + + + - + ); }} + + + + + + + + + + + + + + + - + ); }} + + + + + + + + + + + + + + + - + ); }} + + + + + + + + + + + + + + + { + Object.keys(job.parts_tax_rates).forEach((parttype) => { + Object.keys(job.parts_tax_rates[parttype]).forEach((key) => { + if (key.includes("tx_in")) { + if (job.parts_tax_rates[parttype][key] === "Y") { + job.parts_tax_rates[parttype][key] = true; + } else { + job.parts_tax_rates[parttype][key] = false; + } + } + }); + }); + return { ...job, loss_date: job.loss_date ? moment(job.loss_date) : null, diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index b2a0bd895..b48315846 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -1798,7 +1798,7 @@ "totalreturns": "The total retail amount of returns created for this job." }, "ppc": "This line contains a part price change.", - "profileadjustments": "Profile Disc./Mkup (Already included above)", + "profileadjustments": "Profile Disc./Mkup", "prt_dsmk_total": "Line Item Adjustment", "rates": "Rates", "rates_subtotal": "All Rates Subtotal", diff --git a/job-totals-testing-util.js b/job-totals-testing-util.js index 9e5220a87..53eea2552 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 eyJhbGciOiJSUzI1NiIsImtpZCI6IjE5MGFkMTE4YTk0MGFkYzlmMmY1Mzc2YjM1MjkyZmVkZThjMmQwZWUiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiUm9tZSBEZXZlbG9wbWVudCIsImh0dHBzOi8vaGFzdXJhLmlvL2p3dC9jbGFpbXMiOnsieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoidXNlciIsIngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsidXNlciJdLCJ4LWhhc3VyYS11c2VyLWlkIjoidDZZbTFORGxDRE9QWnIzRjliZ3VXSDRMaFNYMiJ9LCJpb2FkbWluIjp0cnVlLCJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vcm9tZS1wcm9kLTEiLCJhdWQiOiJyb21lLXByb2QtMSIsImF1dGhfdGltZSI6MTY5NDQ2NjM3OCwidXNlcl9pZCI6InQ2WW0xTkRsQ0RPUFpyM0Y5Ymd1V0g0TGhTWDIiLCJzdWIiOiJ0NlltMU5EbENET1BacjNGOWJndVdINExoU1gyIiwiaWF0IjoxNjk0NDY5OTY3LCJleHAiOjE2OTQ0NzM1NjcsImVtYWlsIjoicGF0cmlja0Byb21lLmRldiIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJwYXRyaWNrQHJvbWUuZGV2Il19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0.cL1tTMmFwnyg-mXqRRaFLGSn92pwCDrbTGZviVFKTPqwejDDe_OTla6zizy5w5XBk1_S2CA4SrsH6-8uXkrRSLECd1QMsYK1XKUj0zkfe-o3Z1dMva9gPpVrs-fiCXzjcfs-P12hPfvubNZgqpj1EwZmQ6imq8MidkAo1c4V-IWGpxsmCTNBLN_09N0071spsabwtMosXHJVnkG44StXEM2w-FRak6dE0uZZkbPGgg_2uDwFQb3um6MJ8FbK398pLMs3cUrdQiB-5YozYoIuEI-L00s7dDTvjOKUzOvcDme5UCAS6RmwWoZYkTWrMJU3jf5ivAvjHNIhy3aMGyQxzg`; + const bearerToken = `Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjE5MGFkMTE4YTk0MGFkYzlmMmY1Mzc2YjM1MjkyZmVkZThjMmQwZWUiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiUm9tZSBEZXZlbG9wbWVudCIsImh0dHBzOi8vaGFzdXJhLmlvL2p3dC9jbGFpbXMiOnsieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoidXNlciIsIngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsidXNlciJdLCJ4LWhhc3VyYS11c2VyLWlkIjoidDZZbTFORGxDRE9QWnIzRjliZ3VXSDRMaFNYMiJ9LCJpb2FkbWluIjp0cnVlLCJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vcm9tZS1wcm9kLTEiLCJhdWQiOiJyb21lLXByb2QtMSIsImF1dGhfdGltZSI6MTY5NDQ2NjM3OCwidXNlcl9pZCI6InQ2WW0xTkRsQ0RPUFpyM0Y5Ymd1V0g0TGhTWDIiLCJzdWIiOiJ0NlltMU5EbENET1BacjNGOWJndVdINExoU1gyIiwiaWF0IjoxNjk0NTQ4MTIwLCJleHAiOjE2OTQ1NTE3MjAsImVtYWlsIjoicGF0cmlja0Byb21lLmRldiIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJwYXRyaWNrQHJvbWUuZGV2Il19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0.hIR3tAIOZFydmuCC5OS1KmIAG3Rq3mB1ZJAcnWUrJRuioNw1DeezAiUvXlb2iQ3Pow3zpyTkZgNMrLhWNpZmywyjPQQaTU7krw2gNhmRfrILmIWjODepvRcp4mcwvdf65WkqXm88S82b-8nkgvPhogvXmYWHrrDFl9EP4nMXdhjJ8rZ-euBcH9wz9o4BehsW4x91JJxeTU_jk4Fa0h6ppG6XdTmPyTlQb79g-WgLbqtyXEIjQr9q_ZbE4br_PLLhFd7SnUV0e-raw3FcK9m4Mc-n37M4KtKEpDbhXM_2MtGSCWbKZ7m3lfydFaV8LlgnCTiX_gSCvoAmCeRyH5w1yQ`; const { jobs } = await client.request( gql` query GET_JOBS($bodyshopids: [uuid!]!) { @@ -61,6 +61,7 @@ async function RunTheTest() { ownr_ln ownr_co_nm ins_co_nm + comment } } `, @@ -73,19 +74,20 @@ async function RunTheTest() { id: newjob.id, owner: `${newjob.ownr_fn} ${newjob.ownr_ln} ${job.ownr_co_nm || ""}`, ins_co: newjob.ins_co_nm, + comment: newjob.comment, }; const calcTotal = newjob.job_totals.totals.total_repairs.amount; const ttlTotal = newjob.cieca_ttl.data.g_ttl_amt * 100; result.difference = (calcTotal - ttlTotal) / 100; - if (Math.abs(calcTotal - ttlTotal) > 5) { + if (Math.abs(calcTotal - ttlTotal) > 3) { //Diff is greater than 5 cents. Fail it. result.result = "***FAIL***"; } else { result.result = "PASS"; } - console.log(`${result.result} => RO ${job.ro_number}`); + console.log(`${result.result} => RO ${job.ro_number} - ${job.id} `); results.push(result); } catch (error) { diff --git a/server/job/job-totals.js b/server/job/job-totals.js index 86a9c1bea..c2ec10132 100644 --- a/server/job/job-totals.js +++ b/server/job/job-totals.js @@ -1,6 +1,8 @@ const Dinero = require("dinero.js"); const queries = require("../graphql-client/queries"); const GraphQLClient = require("graphql-request").GraphQLClient; +const adminClient = require("../graphql-client/graphql-client").client; +const _ = require("lodash"); const logger = require("../utils/logger"); // Dinero.defaultCurrency = "USD"; // Dinero.globalLocale = "en-CA"; @@ -59,7 +61,7 @@ async function TotalsServerSide(req, res) { try { let ret = { rates: await CalculateRatesTotals({ job, client }), - parts: CalculatePartsTotals(job.joblines, job.parts_tax_rates), + parts: CalculatePartsTotals(job.joblines, job.parts_tax_rates, job), additional: CalculateAdditional(job), }; ret.totals = CalculateTaxesTotals(job, ret); @@ -93,7 +95,7 @@ async function Totals(req, res) { try { let ret = { rates: await CalculateRatesTotals({ job, client }), - parts: CalculatePartsTotals(job.joblines, job.parts_tax_rates), + parts: CalculatePartsTotals(job.joblines, job.parts_tax_rates, job), additional: CalculateAdditional(job), }; ret.totals = CalculateTaxesTotals(job, ret); @@ -389,7 +391,7 @@ async function CalculateRatesTotals({ job, client }) { lbr_op: "OP11", lbr_amt: 0, op_code_desc: "REMOVE / REPLACE", - tax_part: hasMahwLine.tax_amt > 0 ? true : false, + tax_part: stlMahw.tax_amt > 0 ? true : false, db_ref: null, manual_line: true, jobid: job.id, @@ -427,7 +429,7 @@ async function CalculateRatesTotals({ job, client }) { return ret; } -function CalculatePartsTotals(jobLines, parts_tax_rates) { +function CalculatePartsTotals(jobLines, parts_tax_rates, job) { const jl = jobLines.filter((jl) => !jl.removed); const ret = jl.reduce( (acc, value) => { @@ -564,23 +566,27 @@ function CalculatePartsTotals(jobLines, parts_tax_rates) { PAS: Dinero(), PAT: Dinero(), }; + //Track all adjustments that need to be made. + + const linesToAdjustForDiscount = []; Object.keys(parts_tax_rates).forEach((key) => { //Check if there's a discount or a mark up. let disc = Dinero(), markup = Dinero(); + + let discountRate, markupRate; if ( parts_tax_rates[key].prt_discp !== undefined && parts_tax_rates[key].prt_discp >= 0 ) { //Check if there's any parts in this part type. if (ret.parts.list[key] !== undefined) { - disc = ret.parts.list[key].total - .percentage( - Math.abs(parts_tax_rates[key].prt_discp) > 1 - ? parts_tax_rates[key].prt_discp - : parts_tax_rates[key].prt_discp * 100 - ) - .multiply(-1); + discountRate = + Math.abs(parts_tax_rates[key].prt_discp) > 1 + ? parts_tax_rates[key].prt_discp + : parts_tax_rates[key].prt_discp * 100; + + disc = ret.parts.list[key].total.percentage(discountRate).multiply(-1); } } if ( @@ -589,26 +595,70 @@ function CalculatePartsTotals(jobLines, parts_tax_rates) { ) { //Check if there's any parts in this part type. if (ret.parts.list[key] !== undefined) { - markup = ret.parts.list[key].total.percentage( + markupRate = Math.abs(parts_tax_rates[key].prt_mkupp) > 1 ? parts_tax_rates[key].prt_mkupp - : parts_tax_rates[key].prt_mkupp * 100 //Seems that mark up is written as decimal not %. - ); + : parts_tax_rates[key].prt_mkupp * 100; //Seems that mark up is written as decimal not %. + + 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 + ); + + //If the difference is greater than a penny, fix it. + + if ( + correspondingCiecaStlTotalLine && + Math.abs( + ret.parts.list[key]?.total.getAmount() - + 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.add( + totalDiscountToAdjustBy + ); + ret.parts.subtotal = ret.parts.subtotal.subtract(totalDiscountToAdjustBy); + ret.parts.total = ret.parts.total.subtract(totalDiscountToAdjustBy); + } }); - //Temporarily commenting this out since these totals appear to be already included in the calculation. - // Object.keys(adjustments).forEach((key) => { - // if (ret.parts.list[key] !== undefined) { - // ret.parts.list[key].total = ret.parts.list[key].total.add( - // adjustments[key] - // ); - // ret.parts.subtotal = ret.parts.subtotal.add(adjustments[key]); - // } - // }); + //UpdateJobLines(linesToAdjustForDiscount.filter((l) => l.prt_dsmk_m !== 0)); return { adjustments, @@ -779,8 +829,8 @@ function CalculateTaxesTotals(job, otherTotals) { }; const remainingTaxableAmounts = taxableAmounts; - console.log("Taxable Parts Totals", JSON.stringify(taxableAmounts, null, 2)); - + console.log("Taxable Amounts"); + console.table(JSON.parse(JSON.stringify(taxableAmounts))); Object.keys(taxableAmounts).forEach((part_type) => { //Check it's taxability in the PFP try { @@ -793,14 +843,12 @@ function CalculateTaxesTotals(job, otherTotals) { 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}` - // ); + 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) { @@ -820,7 +868,7 @@ function CalculateTaxesTotals(job, otherTotals) { } 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), + amount: Math.round(thresholdAmount * 100), }); remainingTaxableAmounts[typeOfPart] = remainingTaxableAmounts[ typeOfPart @@ -842,7 +890,7 @@ function CalculateTaxesTotals(job, otherTotals) { } } } catch (error) { - console.log("Shit the bed."); + console.error("Shit the bed."); } }); @@ -976,13 +1024,13 @@ function DiscountNotAlreadyCounted(jobline, joblines) { } //Check it against the database price too? If it's an OE part. - if ( - Math.abs(jobline.db_price - jobline.act_price) - - Math.abs(jobline.prt_dsmk_m) < - 0.01 - ) { - return false; - } + // if ( + // Math.abs(jobline.db_price - jobline.act_price) - + // Math.abs(jobline.prt_dsmk_m) < + // 0.01 + // ) { + // return false; + // } if ( //If it's not a discount line, then it definitely hasn't been counted yet. @@ -1006,3 +1054,31 @@ function ParseCalopCode(opcode) { function IsTrueOrYes(value) { return value === true || value === "Y" || value === "y"; } + +async function UpdateJobLines(joblinesToUpdate) { + if (joblinesToUpdate.length === 0) return; + const updateQueries = joblinesToUpdate.map((line, index) => + generateUpdateQuery(_.pick(line, ["id", "prt_dsmk_m", "prt_dsmk_p"]), index) + ); + const query = ` +mutation UPDATE_EST_LINES{ + ${updateQueries} +} +`; + + const result = await adminClient.request(query); +} + +const generateUpdateQuery = (lineToUpdate, index) => { + return ` + update_joblines${index}: update_joblines(where: { id: { _eq: "${ + lineToUpdate.id + }" } }, _set: ${JSON.stringify(lineToUpdate).replace( + /"(\w+)"\s*:/g, + "$1:" + )}) { + returning { + id + } + }`; +}; From d1ba90408d89e15254be40a37df4b5d53a415f94 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 13 Sep 2023 14:39:58 -0700 Subject: [PATCH 06/23] Update hazard waste calculation. --- server/job/job-totals.js | 65 ++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/server/job/job-totals.js b/server/job/job-totals.js index c2ec10132..675ea942c 100644 --- a/server/job/job-totals.js +++ b/server/job/job-totals.js @@ -378,28 +378,41 @@ async function CalculateRatesTotals({ job, client }) { (!hasMahwLine || hasMahwLine.act_price !== stlMahw.ttl_amt) ) { //Add a hazardous waste material line in case there isn't one on the estimate. - const newMahwLine = { - line_desc: "Hazardous Waste Removal*", - part_type: "PAS", - oem_partno: null, - db_price: 0, - act_price: stlMahw.ttl_amt, - part_qty: 1, - //mod_lbr_ty: "LAB", - db_hrs: 0, - mod_lb_hrs: 0, - lbr_op: "OP11", - lbr_amt: 0, - op_code_desc: "REMOVE / REPLACE", - tax_part: stlMahw.tax_amt > 0 ? true : false, - db_ref: null, - manual_line: true, - jobid: job.id, - }; - job.joblines.push(newMahwLine); - await client.request(queries.INSERT_NEW_JOB_LINE, { - lineInput: [newMahwLine], - }); + if (hasMahwLine) { + //Update it + job.joblines.forEach((jl) => { + if (jl.id === hasMahwLine.id) { + jl.act_price = stlMahw.ttl_amt; + } + }); + await client.request(queries.UPDATE_JOB_LINE, { + lineId: hasMahwLine.id, + line: { act_price: stlMahw.ttl_amt }, + }); + } else { + const newMahwLine = { + line_desc: "Hazardous Waste Removal*", + part_type: "PAS", + oem_partno: null, + db_price: 0, + act_price: stlMahw.ttl_amt, + part_qty: 1, + //mod_lbr_ty: "LAB", + db_hrs: 0, + mod_lb_hrs: 0, + lbr_op: "OP11", + lbr_amt: 0, + op_code_desc: "REMOVE / REPLACE", + tax_part: stlMahw.tax_amt > 0 ? true : false, + db_ref: null, + manual_line: true, + jobid: job.id, + }; + job.joblines.push(newMahwLine); + await client.request(queries.INSERT_NEW_JOB_LINE, { + lineInput: [newMahwLine], + }); + } } //Materials Scrubbing as required by CCC. @@ -1061,10 +1074,10 @@ async function UpdateJobLines(joblinesToUpdate) { generateUpdateQuery(_.pick(line, ["id", "prt_dsmk_m", "prt_dsmk_p"]), index) ); const query = ` -mutation UPDATE_EST_LINES{ - ${updateQueries} -} -`; + mutation UPDATE_EST_LINES{ + ${updateQueries} + } + `; const result = await adminClient.request(query); } From ff318599f5ecd45b3313a07f65d13bb74731c8f7 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 13 Sep 2023 14:55:20 -0700 Subject: [PATCH 07/23] Add regression check. --- .../jobs-detail-rates.parts.component.jsx | 9 +- server/job/job-totals.js | 272 +++++++++++------- 2 files changed, 174 insertions(+), 107 deletions(-) diff --git a/client/src/components/jobs-detail-rates/jobs-detail-rates.parts.component.jsx b/client/src/components/jobs-detail-rates/jobs-detail-rates.parts.component.jsx index 2cd2b8917..71f2103c7 100644 --- a/client/src/components/jobs-detail-rates/jobs-detail-rates.parts.component.jsx +++ b/client/src/components/jobs-detail-rates/jobs-detail-rates.parts.component.jsx @@ -1,4 +1,4 @@ -import { Collapse, Form, InputNumber, Switch } from "antd"; +import { Collapse, Form, Input, InputNumber, Switch } from "antd"; import React from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; @@ -563,6 +563,13 @@ export function JobsDetailRatesParts({ > + + + !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 === "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( - 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() + 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 === "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( + 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() + ) + ); + } + }); - //Taxable amounts should match with what is in the STL file. - //Now that we have the taxable amounts, apply the tax. + //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 tieredTaxAmounts = { + ty1Tax: Dinero(), + ty2Tax: Dinero(), + ty3Tax: Dinero(), + ty4Tax: Dinero(), + ty5Tax: Dinero(), + ty6Tax: Dinero(), + }; - const remainingTaxableAmounts = taxableAmounts; - console.log("Taxable Amounts"); - console.table(JSON.parse(JSON.stringify(taxableAmounts))); - Object.keys(taxableAmounts).forEach((part_type) => { - //Check it's taxability in the PFP - try { - const pfp = job.parts_tax_rates; + const remainingTaxableAmounts = taxableAmounts; + console.log("Taxable Amounts"); + console.table(JSON.parse(JSON.stringify(taxableAmounts))); + 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 = parseFloat( - job.cieca_pft[`ty${tyCounter}_thres${threshCounter}`] - ); - const thresholdTaxRate = parseFloat( - job.cieca_pft[`ty${tyCounter}_rate${threshCounter}`] - ); + 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 = 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[typeOfPart]; - remainingTaxableAmounts[typeOfPart] = Dinero(); - } else { - if ( - thresholdAmount >= - remainingTaxableAmounts[typeOfPart].getAmount() / 100 - ) { - //This threshold is bigger than the remaining taxable balance. Add it all. + let taxableAmountInThisThreshold; + if (thresholdAmount === 9999.99) { + // THis is the last threshold. Tax the entire remaining amount. 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(thresholdAmount * 100), - }); - remainingTaxableAmounts[typeOfPart] = remainingTaxableAmounts[ - typeOfPart - ].subtract( - Dinero({ - amount: Math.round(taxableAmountInThisThreshold * 100), - }) - ); + 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(thresholdAmount * 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); } - - const taxAmountToAdd = - taxableAmountInThisThreshold.percentage(thresholdTaxRate); - - tieredTaxAmounts[`ty${tyCounter}Tax`] = - tieredTaxAmounts[`ty${tyCounter}Tax`].add(taxAmountToAdd); } } } + } catch (error) { + console.error("Shit the bed."); } - } catch (error) { - console.error("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()); + } 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 + ) + ); + } + console.log(statePartsTax.toFormat(), val.line_desc); + }); + } - 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) { @@ -975,7 +1035,7 @@ function CalculateTaxesTotals(job, otherTotals) { .add( otherTotals.additional.storage.percentage((job.tax_str_rt || 0) * 100) ) - // .add(additionalItemsTax) + .add(additionalItemsTax) // 0 if using PFP method. .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( From fef680fb995dc253ec4c70dabdb823c59e0b48ca Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Thu, 14 Sep 2023 15:18:59 -0700 Subject: [PATCH 08/23] Add labor to tax thresholds. --- job-totals-testing-util.js | 2 +- server/job/job-totals.js | 223 +++++++++++++++++++++---------------- 2 files changed, 129 insertions(+), 96 deletions(-) diff --git a/job-totals-testing-util.js b/job-totals-testing-util.js index 53eea2552..b69ec111b 100644 --- a/job-totals-testing-util.js +++ b/job-totals-testing-util.js @@ -99,7 +99,7 @@ async function RunTheTest() { } } - console.table(results); + console.table(results.filter((r) => r.result !== "PASS")); const summary = results.reduce( (acc, val) => { if (val.result === "PASS") { diff --git a/server/job/job-totals.js b/server/job/job-totals.js index d80805e4e..16b5ca5bd 100644 --- a/server/job/job-totals.js +++ b/server/job/job-totals.js @@ -762,9 +762,6 @@ function CalculateTaxesTotals(job, otherTotals) { .add(otherTotals.rates.subtotal) //No longer using just rates subtotal to include mapa/mash. .add(otherTotals.additional.total); - // .add(Dinero({ amount: (job.towing_payable || 0) * 100 })) - // .add(Dinero({ amount: (job.storage_payable || 0) * 100 })); - //Potential issue here with Sublet Calculation. Sublets are calculated under labor in Mitchell, but it's done in IO //Under the parts rates. @@ -791,6 +788,21 @@ function CalculateTaxesTotals(job, otherTotals) { PAS: Dinero(), PAP: Dinero(), PAM: Dinero(), + + LA1: Dinero(), + LA2: Dinero(), + LA3: Dinero(), + LA4: Dinero(), + LAU: Dinero(), + LAA: Dinero(), + LAB: Dinero(), + LAD: Dinero(), + LAE: Dinero(), + LAF: Dinero(), + LAG: Dinero(), + LAM: Dinero(), + LAR: Dinero(), + LAS: Dinero(), }; if ( @@ -811,7 +823,7 @@ function CalculateTaxesTotals(job, otherTotals) { } else if (!val.part_type) { //Do nothing for now. } else { - const typeOfPart = val.part_type === "PAM" ? "PAC" : val.part_type; + 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) @@ -833,10 +845,33 @@ function CalculateTaxesTotals(job, otherTotals) { } }); - //Taxable amounts should match with what is in the STL file. - //Now that we have the taxable amounts, apply the tax. + //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 tieredTaxAmounts = { + 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(), @@ -845,80 +880,104 @@ function CalculateTaxesTotals(job, otherTotals) { ty6Tax: Dinero(), }; - const remainingTaxableAmounts = taxableAmounts; - console.log("Taxable Amounts"); - console.table(JSON.parse(JSON.stringify(taxableAmounts))); - Object.keys(taxableAmounts).forEach((part_type) => { - //Check it's taxability in the PFP + const pfp = job.parts_tax_rates; + const pfl = job.cieca_pfl; + Object.keys(taxableAmounts).map((key) => { try { - const pfp = job.parts_tax_rates; - - const typeOfPart = part_type === "PAM" ? "PAC" : part_type; - if (IsTrueOrYes(pfp[typeOfPart].prt_tax_in)) { + 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}`])) { - //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[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(thresholdAmount * 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); - } + //This amount is taxable for this type. + taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[ + `ty${tyCounter}Tax` + ].add(taxableAmounts[typeOfPart]); + } + } + } else { + //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]); } } } } catch (error) { - console.error("Shit the bed."); + 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 { + //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); + } + }); + + console.log("*** Total Tax by Tier Amounts***"); + console.table(JSON.parse(JSON.stringify(totalTaxByTier))); + statePartsTax = statePartsTax - .add(tieredTaxAmounts.ty1Tax) - .add(tieredTaxAmounts.ty2Tax) - .add(tieredTaxAmounts.ty3Tax) - .add(tieredTaxAmounts.ty4Tax) - .add(tieredTaxAmounts.ty5Tax) - .add(tieredTaxAmounts.ty6Tax); + .add(totalTaxByTier.ty1Tax) + .add(totalTaxByTier.ty2Tax) + .add(totalTaxByTier.ty3Tax) + .add(totalTaxByTier.ty4Tax) + .add(totalTaxByTier.ty5Tax) + .add(totalTaxByTier.ty6Tax); + console.log("Tiered Taxes Total for Parts", statePartsTax.toFormat()); } else { //Use the old thing. @@ -978,32 +1037,6 @@ function CalculateTaxesTotals(job, otherTotals) { let laborTaxTotal = Dinero(); if (Object.keys(job.cieca_pfl).length > 0) { - //Do it by labor type - const types = [ - "la1", - "la2", - "la3", - "la4", - "lau", - "laa", - "lab", - "lad", - "lae", - "laf", - "lag", - "lam", - "lar", - "las", - ]; - types.forEach((type) => { - laborTaxTotal = laborTaxTotal.add( - otherTotals.rates[type].total.percentage( - job.cieca_pfl[type.toUpperCase()] - ? job.cieca_pfl[type.toUpperCase()].lbr_taxp - : (job.tax_lbr_rt || 0) * 100 - ) - ); - }); } else { //We don't have it, just add in how it was before. laborTaxTotal = otherTotals.rates.subtotal.percentage( From 41849644f363a4dee9e033c7b34111280ba89b5e Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Fri, 15 Sep 2023 09:15:34 -0700 Subject: [PATCH 09/23] IO-2395 Adjust Payment Number Label --- .../payment-expanded-row/payment-expanded-row.component.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/payment-expanded-row/payment-expanded-row.component.jsx b/client/src/components/payment-expanded-row/payment-expanded-row.component.jsx index a96683b70..eaa0d20ed 100644 --- a/client/src/components/payment-expanded-row/payment-expanded-row.component.jsx +++ b/client/src/components/payment-expanded-row/payment-expanded-row.component.jsx @@ -153,7 +153,7 @@ const PaymentExpandedRowComponent = ({ record, bodyshop }) => { {record.type} - + {record.paymentnum} {payment_response && ( From 5ca34105eff766c9f918d080c1e7c798b604f505 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Fri, 15 Sep 2023 10:37:09 -0700 Subject: [PATCH 10/23] Refactor payments for intellipay. --- bodyshop_translations.babel | 21 + .../card-payment-modal.component..jsx | 361 +++++++++++------- .../card-payment-modal.container..jsx | 2 +- .../components/header/header.component.jsx | 2 +- .../payment-expanded-row.component.jsx | 27 +- .../src/graphql/payment_response.queries.js | 16 +- client/src/translations/en_us/common.json | 3 +- client/src/translations/es/common.json | 3 +- client/src/translations/fr/common.json | 3 +- 9 files changed, 281 insertions(+), 157 deletions(-) diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 13e91e869..5bc83e3b8 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -37301,6 +37301,27 @@ + + inserting + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + diff --git a/client/src/components/card-payment-modal/card-payment-modal.component..jsx b/client/src/components/card-payment-modal/card-payment-modal.component..jsx index 15c4ffe3a..071e2a758 100644 --- a/client/src/components/card-payment-modal/card-payment-modal.component..jsx +++ b/client/src/components/card-payment-modal/card-payment-modal.component..jsx @@ -1,12 +1,15 @@ -import { useMutation, useQuery } from "@apollo/client"; +import { DeleteFilled } from "@ant-design/icons"; +import { useLazyQuery, useMutation } from "@apollo/client"; import { Button, Card, + Col, Form, Input, - InputNumber, Row, + Space, Spin, + Statistic, notification, } from "antd"; import axios from "axios"; @@ -17,7 +20,7 @@ import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { INSERT_PAYMENT_RESPONSE, - QUERY_RO_AND_OWNER_BY_JOB_PK, + QUERY_RO_AND_OWNER_BY_JOB_PKS, } from "../../graphql/payment_response.queries"; import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries"; import { insertAuditTrail } from "../../redux/application/application.actions"; @@ -25,9 +28,8 @@ import { toggleModalVisible } from "../../redux/modals/modals.actions"; import { selectCardPayment } from "../../redux/modals/modals.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; -import DataLabel from "../data-label/data-label.component"; +import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component"; import JobSearchSelectComponent from "../job-search-select/job-search-select.component"; -import LayoutFormRow from "../layout-form-row/layout-form-row.component"; const mapStateToProps = createStructuredSelector({ cardPaymentModal: selectCardPayment, @@ -49,18 +51,21 @@ const CardPaymentModalComponent = ({ const { context } = cardPaymentModal; const [form] = Form.useForm(); - const amount = Form.useWatch("amount", form); - const jobid = Form.useWatch("jobid", form); + const [loading, setLoading] = useState(false); const [insertPayment] = useMutation(INSERT_NEW_PAYMENT); const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE); const { t } = useTranslation(); - const { data, refetch } = useQuery(QUERY_RO_AND_OWNER_BY_JOB_PK, { - variables: { jobid: context?.jobid ?? "" }, - skip: !context?.jobid, - }); + const [, { data, refetch, queryLoading }] = useLazyQuery( + QUERY_RO_AND_OWNER_BY_JOB_PKS, + { + variables: { jobids: [context.jobid] }, + skip: true, + } + ); + console.log("🚀 ~ file: card-payment-modal.component..jsx:61 ~ data:", data); //Initialize the intellipay window. const SetIntellipayCallbackFunctions = () => { console.log("*** Set IntelliPay callback functions."); @@ -76,69 +81,68 @@ const CardPaymentModalComponent = ({ window.intellipay.runOnNonApproval(async function (response) { // Mutate unsuccessful payment + + const { payments } = form.getFieldsValue(); + await insertPaymentResponse({ variables: { - paymentResponse: { - amount: response.amount, + paymentResponse: payments.map((payment) => ({ + amount: payment.amount, bodyshopid: bodyshop.id, - jobid: jobid || context.jobid, + jobid: payment.jobid, declinereason: response.declinereason, ext_paymentid: response.paymentid.toString(), successful: false, response, - }, + })), }, }); - insertAuditTrail({ - jobid: jobid || context?.jobid, - operation: AuditTrailMapping.failedpayment(), - }); + + payments.forEach((payment) => + insertAuditTrail({ + jobid: payment.jobid, + operation: AuditTrailMapping.failedpayment(), + }) + ); }); }; const handleFinish = async (values) => { try { - const paymentResult = await insertPayment({ + await insertPayment({ variables: { - paymentInput: { - amount: values.amount, + paymentInput: values.payments.map((payment) => ({ + amount: payment.amount, transactionid: (values.paymentResponse.paymentid || "").toString(), payer: t("payments.labels.customer"), type: values.paymentResponse.cardbrand, - jobid: values.jobid, + jobid: payment.jobid, date: moment(Date.now()), - }, + payment_responses: { + data: [ + { + amount: payment.amount, + bodyshopid: bodyshop.id, + + jobid: payment.jobid, + declinereason: values.paymentResponse.declinereason, + ext_paymentid: values.paymentResponse.paymentid.toString(), + successful: true, + response: values.paymentResponse, + }, + ], + }, + })), }, refetchQueries: ["GET_JOB_BY_PK"], - update(cache, { data }) { - cache.modify({ - id: cache.identify({ id: values.jobid, __typename: "jobs" }), - fields: { - payments(cachedPayments) { - return [...data.insert_payments.returning, ...cachedPayments]; - }, - }, - }); - }, - }); - - await insertPaymentResponse({ - variables: { - paymentResponse: { - amount: values.amount, - bodyshopid: bodyshop.id, - paymentid: paymentResult.data.insert_payments.returning[0].id, - jobid: values.jobid, - declinereason: values.paymentResponse.declinereason, - ext_paymentid: values.paymentResponse.paymentid.toString(), - successful: true, - response: values.paymentResponse, - }, - }, }); toggleModalVisible(); } catch (error) { console.error(error); + notification.open({ + type: "error", + message: t("payments.errors.inserting", { error: error.message }), + }); } finally { setLoading(false); } @@ -146,9 +150,16 @@ const CardPaymentModalComponent = ({ const handleIntelliPayCharge = async () => { setLoading(true); - try { - console.warn("*** Window.Intellipay", !!window.intellipay); + //Validate + try { + await form.validateFields(); + } catch (error) { + setLoading(false); + return; + } + + try { const response = await axios.post("/intellipay/lightbox_credentials", { bodyshop, refresh: !!window.intellipay, @@ -182,93 +193,175 @@ const CardPaymentModalComponent = ({
- - - { - refetch({ jobid: e }); - }} - /> - - + + {(fields, { add, remove, move }) => { + return ( +
+ {fields.map((field, index) => ( + + + + + + + + + + + + + + { + remove(field.name); + }} + /> + + + + ))} + + + +
+ ); + }} +
- {/* Lighbox Input amount needs to be hidden */} - - - - {/* Lightbox payment response when it is completed */} -
{payment_response && ( - + + - + + )} diff --git a/client/src/graphql/payment_response.queries.js b/client/src/graphql/payment_response.queries.js index 8722e6cd8..a23d00dbb 100644 --- a/client/src/graphql/payment_response.queries.js +++ b/client/src/graphql/payment_response.queries.js @@ -28,16 +28,14 @@ export const QUERY_PAYMENT_RESPONSE_BY_PAYMENT_ID = gql` } `; -export const QUERY_RO_AND_OWNER_BY_JOB_PK = gql` - query QUERY_RO_AND_OWNER_BY_JOB_PK($jobid: uuid!) { - jobs_by_pk(id: $jobid) { +export const QUERY_RO_AND_OWNER_BY_JOB_PKS = gql` + query QUERY_RO_AND_OWNER_BY_JOB_PKS($jobids: [uuid!]!) { + jobs(where: { id: { _in: $jobids } }) { ro_number - owner { - ownr_fn - ownr_ln - ownr_ea - ownr_zip - } + ownr_fn + ownr_ln + ownr_ea + ownr_zip } } `; diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index e3d3a4a15..f25ae0656 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -2206,7 +2206,8 @@ }, "errors": { "exporting": "Error exporting payment(s). {{error}}", - "exporting-partner": "Error exporting to partner. Please check the partner interaction log for more errors." + "exporting-partner": "Error exporting to partner. Please check the partner interaction log for more errors.", + "inserting": "Error inserting payment. {{error}}" }, "fields": { "amount": "Amount", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 9ea597631..95187d8c4 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -2206,7 +2206,8 @@ }, "errors": { "exporting": "", - "exporting-partner": "" + "exporting-partner": "", + "inserting": "" }, "fields": { "amount": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 4a76f081c..7f3b4706a 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -2206,7 +2206,8 @@ }, "errors": { "exporting": "", - "exporting-partner": "" + "exporting-partner": "", + "inserting": "" }, "fields": { "amount": "", From f1ef28e544efd841a4e2f9961d73d4daf8d070f3 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Fri, 15 Sep 2023 12:53:53 -0700 Subject: [PATCH 11/23] Updated test.env and circle CI for rome test. --- .circleci/config.yml | 51 ++++++++++++++++++++++++++++++++++++++++++++ client/.env.test | 14 ++++++------ 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 07882fa41..1d698d040 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -132,6 +132,57 @@ jobs: to: "s3://rome-online-production/" - jira/notify + test-rome-hasura-migrate: + docker: + - image: cimg/node:16.15.0 + parameters: + secret: + type: string + default: $HASURA_ROME_TEST_SECRET + working_directory: ~/repo/hasura + steps: + - checkout: + path: ~/repo + - run: + name: Execute migration + command: | + npm install hasura-cli -g + echo ${HASURA_TEST_SECRET} + hasura migrate apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >> + hasura metadata apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >> + hasura metadata reload --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >> + + test-rome-app-build: + docker: + - image: cimg/node:16.15.0 + + working_directory: ~/repo/client + + steps: + - checkout: + path: ~/repo + + - restore_cache: + name: Restore Yarn Package Cache + keys: + - yarn-packages-{{ checksum "yarn.lock" }} + - run: + name: Install Dependencies + command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn + - save_cache: + name: Save Yarn Package Cache + key: yarn-packages-{{ checksum "yarn.lock" }} + paths: + - ~/.cache/yarn + + - run: yarn run build:test + + - aws-s3/sync: + from: build + to: "s3://rome-online-test/" + - jira/notify + + test-hasura-migrate: docker: - image: cimg/node:16.15.0 diff --git a/client/.env.test b/client/.env.test index 6768939ff..1601c4415 100644 --- a/client/.env.test +++ b/client/.env.test @@ -1,14 +1,14 @@ -REACT_APP_GRAPHQL_ENDPOINT=https://db.test.bodyshop.app/v1/graphql -REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.bodyshop.app/v1/graphql -REACT_APP_GA_CODE=231099835 -REACT_APP_FIREBASE_CONFIG={ "apiKey":"AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c", "authDomain":"imex-test.firebaseapp.com", "projectId":"imex-test", "storageBucket":"imex-test.appspot.com", "messagingSenderId":"991923618608", "appId":"1:991923618608:web:633437569cdad78299bef5", "measurementId":"G-TW0XLZEH18"} +REACT_APP_GRAPHQL_ENDPOINT=https://db.test.romeonline.io/v1/graphql +REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.romeonline.io/v1/graphql +REACT_APP_GA_CODE=231103507 +REACT_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"} REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop REACT_APP_CLOUDINARY_API_KEY=473322739956866 REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250 -REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BN2GcDPjipR5MTEosO5dT4CfQ3cmrdBIsI4juoOQrRijn_5aRiHlwj1mlq0W145mOusx6xynEKl_tvYJhpCc9lo' +REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo' REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g -REACT_APP_AXIOS_BASE_API_URL=https://api.romeonline.io/ -REACT_APP_REPORTS_SERVER_URL=https://reports.romeonline.io +REACT_APP_AXIOS_BASE_API_URL=https://api.test.romeonline.io/ +REACT_APP_REPORTS_SERVER_URL=https://reports.test.romeonline.io REACT_APP_IS_TEST=true REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc \ No newline at end of file From 659a0bc0fd6b5cceae235171ad727be0a77cc4ac Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Fri, 15 Sep 2023 12:53:53 -0700 Subject: [PATCH 12/23] Updated test.env and circle CI for rome test. --- .circleci/config.yml | 60 ++++++++++++++++++++++++++++++++++++++++++++ client/.env.test | 14 +++++------ 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 07882fa41..60f516702 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -132,6 +132,57 @@ jobs: to: "s3://rome-online-production/" - jira/notify + test-rome-hasura-migrate: + docker: + - image: cimg/node:16.15.0 + parameters: + secret: + type: string + default: $HASURA_ROME_TEST_SECRET + working_directory: ~/repo/hasura + steps: + - checkout: + path: ~/repo + - run: + name: Execute migration + command: | + npm install hasura-cli -g + echo ${HASURA_TEST_SECRET} + hasura migrate apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >> + hasura metadata apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >> + hasura metadata reload --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >> + + test-rome-app-build: + docker: + - image: cimg/node:16.15.0 + + working_directory: ~/repo/client + + steps: + - checkout: + path: ~/repo + + - restore_cache: + name: Restore Yarn Package Cache + keys: + - yarn-packages-{{ checksum "yarn.lock" }} + - run: + name: Install Dependencies + command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn + - save_cache: + name: Save Yarn Package Cache + key: yarn-packages-{{ checksum "yarn.lock" }} + paths: + - ~/.cache/yarn + + - run: yarn run build:test + + - aws-s3/sync: + from: build + to: "s3://rome-online-test/" + - jira/notify + + test-hasura-migrate: docker: - image: cimg/node:16.15.0 @@ -250,6 +301,15 @@ workflows: filters: branches: only: test + - test-rome-app-build: + filters: + branches: + only: rome/test + - test-rome-hasura-migrate: + secret: ${HASURA_ROME_TEST_SECRET} + filters: + branches: + only: rome/test #- admin-app-build: #filters: #branches: diff --git a/client/.env.test b/client/.env.test index 6768939ff..1601c4415 100644 --- a/client/.env.test +++ b/client/.env.test @@ -1,14 +1,14 @@ -REACT_APP_GRAPHQL_ENDPOINT=https://db.test.bodyshop.app/v1/graphql -REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.bodyshop.app/v1/graphql -REACT_APP_GA_CODE=231099835 -REACT_APP_FIREBASE_CONFIG={ "apiKey":"AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c", "authDomain":"imex-test.firebaseapp.com", "projectId":"imex-test", "storageBucket":"imex-test.appspot.com", "messagingSenderId":"991923618608", "appId":"1:991923618608:web:633437569cdad78299bef5", "measurementId":"G-TW0XLZEH18"} +REACT_APP_GRAPHQL_ENDPOINT=https://db.test.romeonline.io/v1/graphql +REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.romeonline.io/v1/graphql +REACT_APP_GA_CODE=231103507 +REACT_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"} REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop REACT_APP_CLOUDINARY_API_KEY=473322739956866 REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250 -REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BN2GcDPjipR5MTEosO5dT4CfQ3cmrdBIsI4juoOQrRijn_5aRiHlwj1mlq0W145mOusx6xynEKl_tvYJhpCc9lo' +REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo' REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g -REACT_APP_AXIOS_BASE_API_URL=https://api.romeonline.io/ -REACT_APP_REPORTS_SERVER_URL=https://reports.romeonline.io +REACT_APP_AXIOS_BASE_API_URL=https://api.test.romeonline.io/ +REACT_APP_REPORTS_SERVER_URL=https://reports.test.romeonline.io REACT_APP_IS_TEST=true REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc \ No newline at end of file From eff4f82ad7360b431f11d3c8a10e9f44dc635166 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Tue, 19 Sep 2023 15:23:47 -0700 Subject: [PATCH 13/23] Add tax rates to shop config, resolve rate population on job detail. --- bodyshop_translations.babel | 380 +++++++++++++++++- .../job-totals.table.totals.component.jsx | 44 +- ...p-info.responsibilitycenters.component.jsx | 2 + ....responsibilitycenters.taxes.component.jsx | 232 +++++++++++ .../jobs-detail.page.component.jsx | 2 +- client/src/translations/en_us/common.json | 7 +- client/src/translations/es/common.json | 6 + client/src/translations/fr/common.json | 6 + server/job/job-totals.js | 9 +- 9 files changed, 665 insertions(+), 23 deletions(-) create mode 100644 client/src/components/shop-info/shop-info.responsibilitycenters.taxes.component.jsx diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 3039e4986..7cfb277dd 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -1,4 +1,4 @@ - +