diff --git a/client/src/components/job-calculate-totals/job-calculate-totals.component.jsx b/client/src/components/job-calculate-totals/job-calculate-totals.component.jsx index 0e062c24a..0cf4bc1b2 100644 --- a/client/src/components/job-calculate-totals/job-calculate-totals.component.jsx +++ b/client/src/components/job-calculate-totals/job-calculate-totals.component.jsx @@ -1,47 +1,43 @@ import { Button, notification } from "antd"; import Axios from "axios"; import React, { useState } from "react"; -import { useMutation } from "@apollo/client"; import { useTranslation } from "react-i18next"; -import { UPDATE_JOB } from "../../graphql/jobs.queries"; -import Dinero from "dinero.js"; -export default function JobCalculateTotals({ job, disabled }) { +export default function JobCalculateTotals({ job, disabled, refetch }) { const { t } = useTranslation(); const [loading, setLoading] = useState(false); - const [updateJob] = useMutation(UPDATE_JOB); const handleCalculate = async () => { try { setLoading(true); - const newTotals = ( - await Axios.post("/job/totals", { - job: job, - }) - ).data; - const result = await updateJob({ - refetchQueries: ["GET_JOB_BY_PK"], - awaitRefetchQueries: true, - variables: { - jobId: job.id, - job: { - job_totals: newTotals, - clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"), - owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat( - "0.00" - ), - }, - }, + await Axios.post("/job/totalsssu", { + id: job.id, }); - if (!!!result.errors) { - notification["success"]({ message: t("jobs.successes.updated") }); - } else { - notification["error"]({ - message: t("jobs.errors.updating", { - error: JSON.stringify(result.errors), - }), - }); - } + + if (refetch) refetch(); + // const result = await updateJob({ + // refetchQueries: ["GET_JOB_BY_PK"], + // awaitRefetchQueries: true, + // variables: { + // jobId: job.id, + // job: { + // job_totals: newTotals, + // clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"), + // owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat( + // "0.00" + // ), + // }, + // }, + // }); + // if (!!!result.errors) { + // notification["success"]({ message: t("jobs.successes.updated") }); + // } else { + // notification["error"]({ + // message: t("jobs.errors.updating", { + // error: JSON.stringify(result.errors), + // }), + // }); + // } } catch (error) { notification["error"]({ message: t("jobs.errors.updating", { diff --git a/client/src/components/jobs-available-table/jobs-available-table.container.jsx b/client/src/components/jobs-available-table/jobs-available-table.container.jsx index 1e845ec72..0341ee839 100644 --- a/client/src/components/jobs-available-table/jobs-available-table.container.jsx +++ b/client/src/components/jobs-available-table/jobs-available-table.container.jsx @@ -3,12 +3,11 @@ import { useApolloClient, useLazyQuery, useMutation, - useQuery + useQuery, } from "@apollo/client"; import { useTreatments } from "@splitsoftware/splitio-react"; import { Col, notification, Row } from "antd"; import Axios from "axios"; -import Dinero from "dinero.js"; import moment from "moment"; import queryString from "query-string"; import React, { useCallback, useEffect, useState } from "react"; @@ -20,7 +19,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils"; import { DELETE_AVAILABLE_JOB, QUERY_AVAILABLE_JOBS, - QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK + QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK, } from "../../graphql/available-jobs.queries"; import { INSERT_NEW_JOB, UPDATE_JOB } from "../../graphql/jobs.queries"; import { INSERT_NEW_NOTE } from "../../graphql/notes.queries"; @@ -28,7 +27,7 @@ import { SEARCH_VEHICLE_BY_VIN } from "../../graphql/vehicles.queries"; import { insertAuditTrail } from "../../redux/application/application.actions"; import { selectBodyshop, - selectCurrentUser + selectCurrentUser, } from "../../redux/user/user.selectors"; import confirmDialog from "../../utils/asyncConfirm"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; @@ -114,14 +113,14 @@ export function JobsAvailableContainer({ //IO-539 Check for Parts Rate on PAL for SGI use case. await CheckTaxRates(estData.est_data, bodyshop); // } - const newTotals = ( - await Axios.post("/job/totals", { - job: { - ...estData.est_data, - joblines: estData.est_data.joblines.data, - }, - }) - ).data; + // const newTotals = ( + // await Axios.post("/job/totals", { + // job: { + // ...estData.est_data, + // joblines: estData.est_data.joblines.data, + // }, + // }) + // ).data; let existingVehicles; if (estData.est_data.v_vin) { @@ -136,9 +135,9 @@ export function JobsAvailableContainer({ const newJob = { ...estData.est_data, - clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"), - owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"), - job_totals: newTotals, + // clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"), + // owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"), + // job_totals: newTotals, date_open: moment(), notes: { data: { @@ -167,6 +166,10 @@ export function JobsAvailableContainer({ }, }) .then((r) => { + Axios.post("/job/totalsssu", { + id: r.data.insert_jobs.returning[0].id, + }); + if (CriticalPartsScanning.treatment === "on") { CriticalPartsScan(r.data.insert_jobs.returning[0].id); } diff --git a/job-totals-testing-util.js b/job-totals-testing-util.js index 7217fe418..3eb8c6261 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 = ["6c63a820-542c-497e-8c82-0cc38fb2bbca"]; - const bearerToken = `Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImFlYjMxMjdiMjRjZTg2MDJjODEyNDUxZThmZTczZDU4MjkyMDg4N2MiLCJ0eXAiOiJKV1QifQ.eyJodHRwczovL2hhc3VyYS5pby9qd3QvY2xhaW1zIjp7IngtaGFzdXJhLWRlZmF1bHQtcm9sZSI6InVzZXIiLCJ4LWhhc3VyYS1hbGxvd2VkLXJvbGVzIjpbInVzZXIiXSwieC1oYXN1cmEtdXNlci1pZCI6InQ2WW0xTkRsQ0RPUFpyM0Y5Ymd1V0g0TGhTWDIifSwiaXNzIjoiaHR0cHM6Ly9zZWN1cmV0b2tlbi5nb29nbGUuY29tL3JvbWUtcHJvZC0xIiwiYXVkIjoicm9tZS1wcm9kLTEiLCJhdXRoX3RpbWUiOjE2NzY0ODIxOTYsInVzZXJfaWQiOiJ0NlltMU5EbENET1BacjNGOWJndVdINExoU1gyIiwic3ViIjoidDZZbTFORGxDRE9QWnIzRjliZ3VXSDRMaFNYMiIsImlhdCI6MTY3NjU4NzYxNSwiZXhwIjoxNjc2NTkxMjE1LCJlbWFpbCI6InBhdHJpY2tAcm9tZS5kZXYiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsicGF0cmlja0Byb21lLmRldiJdfSwic2lnbl9pbl9wcm92aWRlciI6InBhc3N3b3JkIn19.53cLDjRthvAWUOKjSmdMII78MxD1s-mkEbG9z9KVxLB18NsKS-iZMAfIZcYb-LXZGscH8O-jR0OSyMgXitc-mv6xYV6bAGcO7gUgxwMKqnbh9-pK_uyGQ5LQ-yxMG2F397ObJu3fyB1RZ1e8LRYkIpV9LwAm4XiHQdGAfYyFDA2fSOS-9x9k6im07hAYsEeIx2hNr-8vVaEpkCENF2JFpJ9qjtfp6pRnbwQY2VA8nsJly1oOz56GLhb5f1m2Ta22eVqAye9of5EXmNSTsvDkAv7Xs3NNuNbHu8fM76tAuKPniurMNV5VwJZX7RhsjFelmoUFFTVOj6JVL-Sw-vs65A`; + const bearerToken = `Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Ijk3OWVkMTU1OTdhYjM1Zjc4MjljZTc0NDMwN2I3OTNiN2ViZWIyZjAiLCJ0eXAiOiJKV1QifQ.eyJodHRwczovL2hhc3VyYS5pby9qd3QvY2xhaW1zIjp7IngtaGFzdXJhLWRlZmF1bHQtcm9sZSI6InVzZXIiLCJ4LWhhc3VyYS1hbGxvd2VkLXJvbGVzIjpbInVzZXIiXSwieC1oYXN1cmEtdXNlci1pZCI6InQ2WW0xTkRsQ0RPUFpyM0Y5Ymd1V0g0TGhTWDIifSwiaXNzIjoiaHR0cHM6Ly9zZWN1cmV0b2tlbi5nb29nbGUuY29tL3JvbWUtcHJvZC0xIiwiYXVkIjoicm9tZS1wcm9kLTEiLCJhdXRoX3RpbWUiOjE2NzkzNDc4NzAsInVzZXJfaWQiOiJ0NlltMU5EbENET1BacjNGOWJndVdINExoU1gyIiwic3ViIjoidDZZbTFORGxDRE9QWnIzRjliZ3VXSDRMaFNYMiIsImlhdCI6MTY3OTk1NDk3MiwiZXhwIjoxNjc5OTU4NTcyLCJlbWFpbCI6InBhdHJpY2tAcm9tZS5kZXYiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsicGF0cmlja0Byb21lLmRldiJdfSwic2lnbl9pbl9wcm92aWRlciI6InBhc3N3b3JkIn19.Dnq_xo5tffFf-LK0qD_iieUa_UYe4cJqOxcuJnRGH0aqirMMeQLRR4B_Z3pOsD3T20ML3qZMQNUKx-Ivz1mfyK_aA7_4GKtHRKOpIrAyssw_l5aXuCAEmC8iLQHDGvKi7Vp8LsTMPKqjJSjtaW2zuFqcIGrqncWkBMYSnCKjCFsKjryp35hQiIynAN1W0ajgjmFZHCy7hG1h4wFtLKNXEAGxWA0tE7m7ZZBZk3W7J3nMbYiMuGZfw0y2yYeILQGw3UW6sb9B2Jx2bAR3x-GWhPzQHNZEPolE-andm900cFgdph1z7eBE5P2udc2rp8JsAPdUdovt8ZImhCUeE5wD6g`; const { jobs } = await client.request( gql` query GET_JOBS($bodyshopids: [uuid!]!) { @@ -77,7 +77,7 @@ async function RunTheTest() { const calcTotal = newjob.job_totals.totals.total_repairs.amount; const ttlTotal = newjob.cieca_ttl.data.g_ttl_amt * 100; - result.difference = Math.abs(calcTotal - ttlTotal) / 100; + result.difference = (calcTotal - ttlTotal) / 100; if (Math.abs(calcTotal - ttlTotal) > 5) { //Diff is greater than 5 cents. Fail it. diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index 0df62f193..650d6d76a 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -908,6 +908,7 @@ exports.UPDATE_JOB = ` exports.GET_JOB_BY_PK = `query GET_JOB_BY_PK($id: uuid!) { jobs_by_pk(id: $id) { + cieca_stl updated_at alt_transport intakechecklist @@ -1073,6 +1074,8 @@ vehicle{ manual_line prt_dsmk_p prt_dsmk_m + misc_amt + misc_tax parts_order_lines { id parts_order { diff --git a/server/job/job-totals.js b/server/job/job-totals.js index 74eead012..f11419376 100644 --- a/server/job/job-totals.js +++ b/server/job/job-totals.js @@ -58,15 +58,15 @@ async function TotalsServerSide(req, res) { try { let ret = { + rates: await CalculateRatesTotals({ job, client }), parts: CalculatePartsTotals(job.joblines, job.parts_tax_rates), - rates: CalculateRatesTotals(job), additional: CalculateAdditional(job), }; ret.totals = CalculateTaxesTotals(job, ret); return ret; } catch (error) { - logger.log("job-totals-ssu-error", "ERROR", req.user.email, job.id, { + logger.log("job-totals-ssu-error", "ERROR", req.user?.email, job.id, { jobid: job.id, error, }); @@ -92,8 +92,8 @@ async function Totals(req, res) { await AutoAddAtsIfRequired({ job, client }); try { let ret = { + rates: await CalculateRatesTotals({ job, client }), parts: CalculatePartsTotals(job.joblines, job.parts_tax_rates), - rates: CalculateRatesTotals(job), additional: CalculateAdditional(job), }; ret.totals = CalculateTaxesTotals(job, ret); @@ -183,72 +183,72 @@ async function AutoAddAtsIfRequired({ job, client }) { } } -function CalculateRatesTotals(ratesList) { - const jobLines = ratesList.joblines.filter((jl) => !jl.removed); +async function CalculateRatesTotals({ job, client }) { + const jobLines = job.joblines.filter((jl) => !jl.removed); let ret = { la1: { hours: 0, - rate: ratesList.rate_la1 || 0, + rate: job.rate_la1 || 0, }, la2: { hours: 0, - rate: ratesList.rate_la2 || 0, + rate: job.rate_la2 || 0, }, la3: { - rate: ratesList.rate_la3 || 0, + rate: job.rate_la3 || 0, hours: 0, }, la4: { - rate: ratesList.rate_la4 || 0, + rate: job.rate_la4 || 0, hours: 0, }, laa: { - rate: ratesList.rate_laa || 0, + rate: job.rate_laa || 0, hours: 0, }, lab: { - rate: ratesList.rate_lab || 0, + rate: job.rate_lab || 0, hours: 0, }, lad: { - rate: ratesList.rate_lad || 0, + rate: job.rate_lad || 0, hours: 0, }, lae: { - rate: ratesList.rate_lae || 0, + rate: job.rate_lae || 0, hours: 0, }, laf: { - rate: ratesList.rate_laf || 0, + rate: job.rate_laf || 0, hours: 0, }, lag: { - rate: ratesList.rate_lag || 0, + rate: job.rate_lag || 0, hours: 0, }, lam: { - rate: ratesList.rate_lam || 0, + rate: job.rate_lam || 0, hours: 0, }, lar: { - rate: ratesList.rate_lar || 0, + rate: job.rate_lar || 0, hours: 0, }, las: { - rate: ratesList.rate_las || 0, + rate: job.rate_las || 0, hours: 0, }, lau: { - rate: ratesList.rate_lau || 0, + rate: job.rate_lau || 0, hours: 0, }, mapa: { - rate: ratesList.rate_mapa || 0, + rate: job.rate_mapa || 0, hours: 0, }, mash: { - rate: ratesList.rate_mash || 0, + rate: job.rate_mash || 0, hours: 0, }, }; @@ -258,8 +258,9 @@ function CalculateRatesTotals(ratesList) { //Otherwise, calculate them and add them to the default MAPA and MASH centers. let hasMapaLine = false; let hasMashLine = false; - let mapaOpCodes = ParseCalopCode(ratesList.materials["mapa"]?.cal_opcode); - let mashOpCodes = ParseCalopCode(ratesList.materials["mash"]?.cal_opcode); + let hasMahwLine = false; + let mapaOpCodes = ParseCalopCode(job.materials["mapa"]?.cal_opcode); + let mashOpCodes = ParseCalopCode(job.materials["mash"]?.cal_opcode); jobLines.forEach((item) => { //IO-1317 Use the lines on the estimate if they exist instead. @@ -276,6 +277,9 @@ function CalculateRatesTotals(ratesList) { amount: Math.round((item.act_price || 0) * 100), }); } + if (item.line_desc.toLowerCase().includes("hazardous waste")) { + hasMahwLine = item; + } if (item.mod_lbr_ty) { //Check to see if it has 0 hours and a price instead. @@ -333,16 +337,16 @@ function CalculateRatesTotals(ratesList) { } let threshold; //Check if there is a max for this type. - if (ratesList.materials && ratesList.materials[property]) { + if (job.materials && job.materials[property]) { // if ( - ratesList.materials[property].cal_maxdlr !== undefined && - ratesList.materials[property].cal_maxdlr >= 0 + job.materials[property].cal_maxdlr !== undefined && + job.materials[property].cal_maxdlr >= 0 ) { //It has an upper threshhold. threshold = Dinero({ - amount: Math.round(ratesList.materials[property].cal_maxdlr * 100), + amount: Math.round(job.materials[property].cal_maxdlr * 100), }); } } @@ -364,6 +368,37 @@ function CalculateRatesTotals(ratesList) { rates_subtotal = rates_subtotal.add(ret[property].total); } + const stlMahw = job.cieca_stl.data.find((c) => c.ttl_typecd === "MAHW"); + + if ( + stlMahw && + stlMahw.ttl_amt !== 0 && + (!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: true, + db_ref: null, + manual_line: true, + jobid: job.id, + }; + job.joblines.push(newMahwLine); + await client.request(queries.INSERT_NEW_JOB_LINE, { + lineInput: [newMahwLine], + }); + } ret.subtotal = subtotal; ret.rates_subtotal = rates_subtotal; @@ -374,6 +409,54 @@ function CalculateRatesTotals(ratesList) { 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) => { @@ -715,7 +798,7 @@ function CalculateTaxesTotals(job, otherTotals) { } console.log(statePartsTax.toFormat(), val.line_desc); }); - console.log("State Parts Tax", statePartsTax.toFormat()); + let ret = { subtotal: subtotal, federal_tax: subtotal @@ -742,21 +825,21 @@ function CalculateTaxesTotals(job, otherTotals) { otherTotals.additional.storage.percentage((job.tax_str_rt || 0) * 100) ) .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( - (job.tax_paint_mat_rt || 0) * 100 - ) - : Dinero() - ) - .add( - otherTotals.rates.mash.hasMashLine === false //If parts and materials were not added as lines, we must calculate the taxes on them. - ? otherTotals.rates.mash.total.percentage( - (job.tax_paint_mat_rt || 0) * 100 - ) - : Dinero() - ), - + .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( + (job.tax_paint_mat_rt || 0) * 100 + ) + : Dinero() + ) + .add( + otherTotals.rates.mash.hasMashLine === false //If parts and materials were not added as lines, we must calculate the taxes on them. + ? otherTotals.rates.mash.total.percentage( + (job.tax_paint_mat_rt || 0) * 100 + ) + : Dinero() + ), + local_tax: subtotal.percentage((job.local_tax_rate || 0) * 100), }; ret.total_repairs = ret.subtotal @@ -803,6 +886,17 @@ function DiscountNotAlreadyCounted(jobline, joblines) { return false; } + //Check it against the database price too? If it's an OE part. + console.log(jobline.db_price - jobline.act_price); + if ( + Math.abs(jobline.db_price - jobline.act_price) - + Math.abs(jobline.prt_dsmk_m) < + 0.01 + ) { + console.log(jobline.line_desc, "Already had the discount counted."); + return false; + } + if ( //If it's not a discount line, then it definitely hasn't been counted yet. jobline.db_ref !== "900510" &&