From f8e65ada7600a51ee13a67ba298099a03c38997e Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 30 Oct 2024 14:06:00 -0700 Subject: [PATCH 01/74] IO-3001 Initial adjustments to totals. --- .../job-totals.table.totals.component.jsx | 25 ++++++++---- .../jobs-available-table.container.jsx | 39 ++++++++++--------- job-totals-testing-util.js | 19 ++++++--- server/graphql-client/queries.js | 1 + server/job/job-totals-USA.js | 39 +++++++++++++++++++ 5 files changed, 92 insertions(+), 31 deletions(-) diff --git a/client/src/components/job-totals-table/job-totals.table.totals.component.jsx b/client/src/components/job-totals-table/job-totals.table.totals.component.jsx index a8b5e0604..be92d5994 100644 --- a/client/src/components/job-totals-table/job-totals.table.totals.component.jsx +++ b/client/src/components/job-totals-table/job-totals.table.totals.component.jsx @@ -102,7 +102,7 @@ export function JobTotalsTableTotals({ bodyshop, job }) { total: job.job_totals.totals.us_sales_tax_breakdown.ty4Tax }, { - key: `${bodyshop.md_responsibility_centers.taxes.tax_ty5?.tax_type5 || "TT"} - ${[ + key: `${bodyshop.md_responsibility_centers.taxes.tax_ty5?.tax_type5 || "Adj."} - ${[ job.cieca_pft.ty5_rate1, job.cieca_pft.ty5_rate2, job.cieca_pft.ty5_rate3, @@ -113,6 +113,14 @@ export function JobTotalsTableTotals({ bodyshop, job }) { .join(", ")}%`, total: job.job_totals.totals.us_sales_tax_breakdown.ty5Tax }, + ...(job.job_totals?.totals?.ttl_tax_adjustment + ? [ + { + key: `Adj.`, + total: job.job_totals?.totals?.ttl_tax_adjustment + } + ] + : []), { key: t("jobs.labels.total_sales_tax"), bold: true, @@ -121,6 +129,7 @@ export function JobTotalsTableTotals({ bodyshop, job }) { .add(Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty3Tax)) .add(Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty4Tax)) .add(Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty5Tax)) + .add(Dinero(job.job_totals.totals.ttl_tax_adjustment)) .toJSON() } ].filter((item) => item.total.amount !== 0) @@ -141,14 +150,16 @@ export function JobTotalsTableTotals({ bodyshop, job }) { key: t("jobs.fields.ded_amt"), total: job.job_totals.totals.custPayable.deductible }, - ...(InstanceRenderManager({ - imex: [{ - key: t("jobs.fields.federal_tax_payable"), - total: job.job_totals.totals.custPayable.federal_tax - }], + ...InstanceRenderManager({ + imex: [ + { + key: t("jobs.fields.federal_tax_payable"), + total: job.job_totals.totals.custPayable.federal_tax + } + ], rome: [], promanager: "USE_ROME" - })), + }), { key: t("jobs.fields.other_amount_payable"), total: job.job_totals.totals.custPayable.other_customer_amount 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 d9964d090..3a3b0d7e8 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 @@ -1,6 +1,6 @@ import { gql, useApolloClient, useLazyQuery, useMutation, useQuery } from "@apollo/client"; import { useSplitTreatments } from "@splitsoftware/splitio-react"; -import { Col, Row, notification } from "antd"; +import { Button, Col, Row, notification } from "antd"; import Axios from "axios"; import _ from "lodash"; import queryString from "query-string"; @@ -409,24 +409,24 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail setSchComp={setSchComp} /> { - // currentUser.email.includes("@rome.") || - // currentUser.email.includes("@imex.") ? ( - // - // ) : null + currentUser.email.includes("@rome.") || + currentUser.email.includes("@imex.") ? ( + + ) : null } @@ -617,6 +617,7 @@ function ResolveCCCLineIssues(estData, bodyshop) { // ` | Act Price delete. (prev act price = ${estData.joblines.data[indexInEstData].act_price})`; estData.joblines.data[indexInEstData].act_price = 0; estData.joblines.data[indexInEstData].db_price = 0; + estData.joblines.data[indexInEstData].part_type = null; }); }); } diff --git a/job-totals-testing-util.js b/job-totals-testing-util.js index e2018661f..b2615b485 100644 --- a/job-totals-testing-util.js +++ b/job-totals-testing-util.js @@ -1,9 +1,7 @@ const path = require("path"); const Dinero = require("dinero.js"); const { gql } = require("graphql-request"); -const queries = require("./server/graphql-client/queries"); -const GraphQLClient = require("graphql-request").GraphQLClient; -const logger = require("./server/utils/logger"); + const AxiosLib = require("axios").default; const axios = AxiosLib.create(); @@ -16,8 +14,9 @@ require("dotenv").config({ }); async function RunTheTest() { - const bodyshopids = ["b501bb82-22b2-493a-8a0f-152938194869"]; - const bearerToken = `Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImJhNjI1OTZmNTJmNTJlZDQ0MDQ5Mzk2YmU3ZGYzNGQyYzY0ZjQ1M2UiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiUm9tZSBEZXZlbG9wbWVudCIsImh0dHBzOi8vaGFzdXJhLmlvL2p3dC9jbGFpbXMiOnsieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoidXNlciIsIngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsidXNlciJdLCJ4LWhhc3VyYS11c2VyLWlkIjoidDZZbTFORGxDRE9QWnIzRjliZ3VXSDRMaFNYMiJ9LCJpb2FkbWluIjp0cnVlLCJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vcm9tZS1wcm9kLTEiLCJhdWQiOiJyb21lLXByb2QtMSIsImF1dGhfdGltZSI6MTcxMDk1MTg1MCwidXNlcl9pZCI6InQ2WW0xTkRsQ0RPUFpyM0Y5Ymd1V0g0TGhTWDIiLCJzdWIiOiJ0NlltMU5EbENET1BacjNGOWJndVdINExoU1gyIiwiaWF0IjoxNzExNTczODI1LCJleHAiOjE3MTE1Nzc0MjUsImVtYWlsIjoicGF0cmlja0Byb21lLmRldiIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJwYXRyaWNrQHJvbWUuZGV2Il19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0.0kBySA9tJznLYj8TtncHGVWJO0IcmLKP2G1UyyXwaj45kTa25bjT9RWjM-NslX_zjOvrvmQZzisFAb6M1Jf6geNjOMLIqb8bhihhzEZK4CcRfvjT6cpZxnOO2Dp_1Y5OePbvOBS_GlfdsovVWa84OLuhYC5G_3QwHT8_2Cttz4CbrC6M_vd7QsGODJYBbVKMhOdZhzpNq7AbOUh3749WRjLMMobpnZDrmQlsyg3PAqtX1FHO25WQS2rma9QahGDSY736JfbkuZJ2XbNn0axEGpK7RQLUcuRkFUlfKqYplNbR_e1Q3kEfRAZpxBPXZysrDcbDNhbkWCoTmJ3fle55OA`; + const bodyshopids = ["71f8494c-89f0-43e0-8eb2-820b52d723bc"]; + const bearerToken = `Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImU2YWMzNTcyNzY3ZGUyNjE0ZmM1MTA4NjMzMDg3YTQ5MjMzMDNkM2IiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiUGF0cmljayBGaWMgKERFVikiLCJodHRwczovL2hhc3VyYS5pby9qd3QvY2xhaW1zIjp7IngtaGFzdXJhLWRlZmF1bHQtcm9sZSI6InVzZXIiLCJ4LWhhc3VyYS1hbGxvd2VkLXJvbGVzIjpbInVzZXIiXSwieC1oYXN1cmEtdXNlci1pZCI6ImhOSjhBRHB0REhRQkRFcXNCOFFNWVRqaURuZjEifSwiaXNzIjoiaHR0cHM6Ly9zZWN1cmV0b2tlbi5nb29nbGUuY29tL2ltZXgtZGV2IiwiYXVkIjoiaW1leC1kZXYiLCJhdXRoX3RpbWUiOjE3MzAxMzIwMjksInVzZXJfaWQiOiJoTko4QURwdERIUUJERXFzQjhRTVlUamlEbmYxIiwic3ViIjoiaE5KOEFEcHRESFFCREVxc0I4UU1ZVGppRG5mMSIsImlhdCI6MTczMDE1MjI5NiwiZXhwIjoxNzMwMTU1ODk2LCJlbWFpbCI6InBhdHJpY2tAaW1leC5kZXYiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsicGF0cmlja0BpbWV4LmRldiJdfSwic2lnbl9pbl9wcm92aWRlciI6InBhc3N3b3JkIn19.Fgf_itlA2-CMd2sTYBNjD-g8Jt3wPLW_YWbDtMn8tm-doSPhU7-RHSHON7Vz6o1m6S3x88RODt2uxSifxfgjqXYC_yJiUVEnHp7xokI1X-7EIbv6S_jfD1gKq7gNehkKkm0QETvAX_wmL0hEZyZnBacxjkSZzDZgbfKfj8U2CAU1nY6O5vk90q0HdTrt98RL37Uiz62ftAdBZCLSpZ1AS1hGa1S3NDMhWbWvBVCMY59bhM8lreH-Q1znlSe9jNXbElvrKofSc3Jz2WeTIj3Ifq5Ev-p4rIOoILww8kE9ZKp4s28JXMdrpRnqFM7tufggjZaKx5g1_rwRnzl-dk50RQ`; + const { jobs } = await client.request( gql` query GET_JOBS($bodyshopids: [uuid!]!) { @@ -86,6 +85,16 @@ async function RunTheTest() { } else { result.result = "PASS"; } + const subcalcTotal = newjob.job_totals.totals.subtotal.amount; + const subttlTotal = newjob.cieca_ttl.data.n_ttl_amt * 100; + result.subdifference = (subcalcTotal - subttlTotal) / 100; + + if (Math.abs(subcalcTotal - subttlTotal) > 3) { + //Diff is greater than 5 cents. Fail it. + result.subresult = "***FAIL***"; + } else { + result.subresult = "PASS"; + } // console.log(`${result.result} => RO ${job.ro_number} - ${job.id} `); results.push(result); diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index 99e37bebe..e5b83720d 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -1365,6 +1365,7 @@ exports.GET_JOB_BY_PK = `query GET_JOB_BY_PK($id: uuid!) { cieca_pfl cieca_pft cieca_pfo + cieca_ttl vehicle { id notes diff --git a/server/job/job-totals-USA.js b/server/job/job-totals-USA.js index 4e123850e..d65ed80ef 100644 --- a/server/job/job-totals-USA.js +++ b/server/job/job-totals-USA.js @@ -68,6 +68,45 @@ async function TotalsServerSide(req, res) { ret.additional = CalculateAdditional(job); ret.totals = CalculateTaxesTotals(job, ret); + // Sub total scrubbbing. + const emsTotal = + job.cieca_ttl.data.n_ttl_amt === job.cieca_ttl.data.g_ttl_amt //It looks like sometimes, gross and net are the same, but they shouldn't be. + ? job.cieca_ttl.data.g_ttl_amt - job.cieca_ttl.data.g_tax //If they are, adjust the gross total down by the tax amount. + : job.cieca_ttl.data.n_ttl_amt; + const ttlDifference = emsTotal - ret.totals.subtotal.getAmount() / 100; + + if (Math.abs(ttlDifference) > 0.01) { + //If difference is greater than a pennny, we need to adjust it. + ret.totals.ttl_adjustment = Dinero({ amount: Math.round(ttlDifference * 100) }); + ret.totals.subtotal = ret.totals.subtotal.add(ret.totals.ttl_adjustment); + ret.totals.total_repairs = ret.totals.total_repairs.add(ret.totals.ttl_adjustment); + ret.totals.net_repairs = ret.totals.net_repairs.add(ret.totals.ttl_adjustment); + logger.log("job-totals-USA-ttl-adj", "DEBUG", null, job.id, { + adjAmount: ttlDifference + }); + } + + //Taxes Scrubbing + const emsTaxTotal = job.cieca_ttl.data.g_tax; + const totalUsTaxes = + (ret.totals.us_sales_tax_breakdown.ty1Tax.getAmount() + + ret.totals.us_sales_tax_breakdown.ty2Tax.getAmount() + + ret.totals.us_sales_tax_breakdown.ty3Tax.getAmount() + + ret.totals.us_sales_tax_breakdown.ty4Tax.getAmount() + + ret.totals.us_sales_tax_breakdown.ty5Tax.getAmount()) / + 100; + const ttlTaxDifference = emsTaxTotal - totalUsTaxes; + + if (Math.abs(ttlTaxDifference) > 0.01) { + //If difference is greater than a pennny, we need to adjust it. + ret.totals.ttl_tax_adjustment = Dinero({ amount: Math.round(ttlTaxDifference * 100) }); + ret.totals.total_repairs = ret.totals.total_repairs.add(ret.totals.ttl_tax_adjustment); + ret.totals.net_repairs = ret.totals.net_repairs.add(ret.totals.ttl_tax_adjustment); + logger.log("job-totals-USA-ttl-tax-adj", "DEBUG", null, job.id, { + adjAmount: ttlTaxDifference + }); + } + return ret; } catch (error) { logger.log("job-totals-ssu-USA-error", "ERROR", req.user?.email, job.id, { From 1205e71ea6ce4b712e48e8f0f728a20db6868dfe Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Fri, 1 Nov 2024 19:54:49 -0700 Subject: [PATCH 02/74] IO-3001 Add UI adjustments. --- .../job-totals.table.totals.component.jsx | 10 +- .../jobs-available-table.container.jsx | 37 ++-- job-totals-testing-util.js | 167 +++++++++--------- package-lock.json | 7 +- package.json | 1 + server/job/job-costing.js | 37 +++- 6 files changed, 156 insertions(+), 103 deletions(-) diff --git a/client/src/components/job-totals-table/job-totals.table.totals.component.jsx b/client/src/components/job-totals-table/job-totals.table.totals.component.jsx index be92d5994..b989a9dcf 100644 --- a/client/src/components/job-totals-table/job-totals.table.totals.component.jsx +++ b/client/src/components/job-totals-table/job-totals.table.totals.component.jsx @@ -22,6 +22,14 @@ export function JobTotalsTableTotals({ bodyshop, job }) { const data = useMemo(() => { return [ + ...(job.job_totals?.totals?.ttl_adjustment + ? [ + { + key: `Subtotal Adj.`, + total: job.job_totals?.totals?.ttl_adjustment + } + ] + : []), { key: t("jobs.labels.subtotal"), total: job.job_totals.totals.subtotal, @@ -116,7 +124,7 @@ export function JobTotalsTableTotals({ bodyshop, job }) { ...(job.job_totals?.totals?.ttl_tax_adjustment ? [ { - key: `Adj.`, + key: `Tax Adj.`, total: job.job_totals?.totals?.ttl_tax_adjustment } ] 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 3a3b0d7e8..6266e2dc5 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 @@ -408,26 +408,23 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail updateSchComp={updateSchComp} setSchComp={setSchComp} /> - { - currentUser.email.includes("@rome.") || - currentUser.email.includes("@imex.") ? ( - - ) : null - } + {currentUser.email.includes("@rome.") || currentUser.email.includes("@imex.") ? ( + + ) : null} r.result !== "PASS").length}` - ); + const limit = pLimit(5); // Set concurrency limit to 3 - try { - await axios.post( - `http://localhost:4000/job/totalsssu`, - { id: job.id }, - { headers: { Authorization: bearerToken } } + const tasks = jobs.map((job, index) => { + return limit(async () => { + process.stdout.cursorTo(0); + process.stdout.write( + `Processing job ${index + 1} of ${jobs.length}. Failed jobs: ${results.filter((r) => r.overallTotalCorrect !== "PASS").length}. Correct jobs because of adjustment: ${results.filter((r) => r.correctJobsBecauseOfAdjustment).length}` ); - const { jobs_by_pk: newjob } = await client.request( - gql` - query GET_JOBS($id: uuid!) { - jobs_by_pk(id: $id) { - id - ro_number - cieca_ttl - job_totals - ownr_fn - ownr_ln - ownr_co_nm - ins_co_nm - comment + + try { + await axios.post( + `http://localhost:4000/job/totalsssu`, + { id: job.id }, + { headers: { Authorization: bearerToken } } + ); + const { jobs_by_pk: newjob } = await client.request( + gql` + query GET_JOBS($id: uuid!) { + jobs_by_pk(id: $id) { + id + ro_number + cieca_ttl + job_totals + ownr_fn + ownr_ln + ownr_co_nm + ins_co_nm + comment + } } + `, + { + id: job.id } - `, - { - id: job.id + ); + + const result = { + 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) > 3) { + result.overallTotalCorrect = "***FAIL***"; + } else { + result.overallTotalCorrect = "PASS"; } - ); + result.ttl_adjustment = Dinero(newjob.job_totals.totals.ttl_adjustment).toFormat(); + result.ttl_tax_adjustment = Dinero(newjob.job_totals.totals.ttl_tax_adjustment).toFormat(); - const result = { - id: newjob.id, - owner: `${newjob.ownr_fn} ${newjob.ownr_ln} ${job.ownr_co_nm || ""}`, - ins_co: newjob.ins_co_nm, - comment: newjob.comment - }; + const calcTax = + Dinero(newjob.job_totals.totals.us_sales_tax_breakdown.ty1Tax) + .add(Dinero(newjob.job_totals.totals.us_sales_tax_breakdown.ty2Tax)) + .add(Dinero(newjob.job_totals.totals.us_sales_tax_breakdown.ty3Tax)) + .add(Dinero(newjob.job_totals.totals.us_sales_tax_breakdown.ty4Tax)) + .add(Dinero(newjob.job_totals.totals.us_sales_tax_breakdown.ty5Tax)) + .add(Dinero(newjob.job_totals.totals.ttl_tax_adjustment)) + .getAmount() / 100; - const calcTotal = newjob.job_totals.totals.total_repairs.amount; - const ttlTotal = newjob.cieca_ttl.data.g_ttl_amt * 100; - result.difference = (calcTotal - ttlTotal) / 100; + const emsTax = newjob.cieca_ttl.data.g_tax; + result.taxDifference = calcTax - emsTax; - if (Math.abs(calcTotal - ttlTotal) > 3) { - //Diff is greater than 5 cents. Fail it. - result.result = "***FAIL***"; - } else { - result.result = "PASS"; + if (Math.abs(calcTax - emsTax) > 3) { + result.taxCorrect = "***FAIL***"; + } else { + result.taxCorrect = "PASS"; + } + + results.push(result); + } catch (error) { + results.push({ + ro_number: job.ro_number, + id: job.id, + result: "**503 FAILURE**" + }); } - const subcalcTotal = newjob.job_totals.totals.subtotal.amount; - const subttlTotal = newjob.cieca_ttl.data.n_ttl_amt * 100; - result.subdifference = (subcalcTotal - subttlTotal) / 100; + }); + }); - if (Math.abs(subcalcTotal - subttlTotal) > 3) { - //Diff is greater than 5 cents. Fail it. - result.subresult = "***FAIL***"; - } else { - result.subresult = "PASS"; - } - // console.log(`${result.result} => RO ${job.ro_number} - ${job.id} `); + await Promise.all(tasks); - results.push(result); - } catch (error) { - results.push({ - ro_number: job.ro_number, - id: job.id, - result: "**503 FAILURE**" - }); - } - } - - console.table(results.filter((r) => r.result !== "PASS")); + console.table(results.filter((r) => r.overallTotalCorrect !== "PASS")); + console.log("======================================="); const summary = results.reduce( (acc, val) => { if (val.result === "PASS") { @@ -119,18 +134,12 @@ async function RunTheTest() { { pass: 0, fail: 0 } ); console.log("Pass Rate: ", ((summary.pass / (summary.fail + summary.pass)) * 100).toFixed(1)); + + const ret = converter.json2csv(results, { emptyFieldValue: "" }); + + fs.writeFile(`./logs/totalstest-${Date.now()}.csv`, ret, (error) => console.log(error)); } -RunTheTest(); - -// mutation { -// delete_jobs(where: {shopid: {_eq: "a7ee1503-ee05-4a02-b80e-bdb11d1cc8ac"}}) { -// affected_rows -// } -// delete_owners(where: {shopid: {_eq: "a7ee1503-ee05-4a02-b80e-bdb11d1cc8ac"}}) { -// affected_rows -// } -// delete_vehicles(where: {shopid: {_eq: "a7ee1503-ee05-4a02-b80e-bdb11d1cc8ac"}}) { -// affected_rows -// } -// } +RunTheTest().catch((error) => { + console.log("Error in RunTheTest: ", error); +}); diff --git a/package-lock.json b/package-lock.json index 2f0edfad5..76f862a25 100644 --- a/package-lock.json +++ b/package-lock.json @@ -64,6 +64,7 @@ "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.3.0", "concurrently": "^8.2.2", + "p-limit": "^3.1.0", "prettier": "^3.3.3", "source-map-explorer": "^2.5.2" }, @@ -6726,7 +6727,8 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "optional": true, + "devOptional": true, + "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -8720,7 +8722,8 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "optional": true, + "devOptional": true, + "license": "MIT", "engines": { "node": ">=10" }, diff --git a/package.json b/package.json index 2a3c24ded..954c5ec07 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.3.0", "concurrently": "^8.2.2", + "p-limit": "^3.1.0", "prettier": "^3.3.3", "source-map-explorer": "^2.5.2" } diff --git a/server/job/job-costing.js b/server/job/job-costing.js index 9ce65f71d..d55af58a5 100644 --- a/server/job/job-costing.js +++ b/server/job/job-costing.js @@ -476,7 +476,7 @@ function GenerateCostingData(job) { if (!hasMapaLine) { if (!jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]]) jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]] = Dinero(); - + jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]] = jobLineTotalsByProfitCenter.additional[ defaultProfits["MAPA"] ].add( @@ -809,6 +809,41 @@ function GenerateCostingData(job) { gppercent: formatGpPercent(0) }); } + //Push adjustments to bottom line. + if (job.job_totals.totals.ttl_adjustment) { + //Add to totals. + const Adjustment = Dinero(job.job_totals.totals.ttl_adjustment); //Need to invert, since this is being assigned as a cost. + summaryData.totalAdditionalSales = summaryData.totalAdditionalSales.add(Adjustment); + summaryData.totalSales = summaryData.totalSales.add(Adjustment); + //Add to lines. + costCenterData.push({ + id: "Adj", + cost_center: "Adjustment", + sale_labor: Dinero().toFormat(), + sale_labor_dinero: Dinero(), + sale_parts: Dinero().toFormat(), + sale_parts_dinero: Dinero(), + sale_additional: Adjustment.toFormat(), + sale_additional_dinero: Adjustment, + sale_sublet: Dinero(), + sale_sublet_dinero: Dinero(), + sales: Adjustment.toFormat(), + sales_dinero: Adjustment, + cost_parts: Dinero().toFormat(), + cost_parts_dinero: Dinero(), + cost_labor: Dinero().toFormat(), //Adjustment.toFormat(), + cost_labor_dinero: Dinero(), // Adjustment, + cost_additional: Dinero(), + cost_additional_dinero: Dinero(), + cost_sublet: Dinero(), + cost_sublet_dinero: Dinero(), + costs: Dinero().toFormat(), + costs_dinero: Dinero(), + gpdollars_dinero: Dinero(), + gpdollars: Dinero().toFormat(), + gppercent: formatGpPercent(0) + }); + } //Final summary data massaging. From 94641ae01db59b32cc4d1507c485d73566dd2b9e Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Tue, 5 Nov 2024 15:31:35 -0800 Subject: [PATCH 03/74] IO-3001 Add resp. centers and begin QB testing. --- bodyshop_translations.babel | 357 + ...p-info.responsibilitycenters.component.jsx | 64 + client/src/translations/en_us/common.json | 7200 +++++++++-------- client/src/translations/es/common.json | 7200 +++++++++-------- client/src/translations/fr/common.json | 7200 +++++++++-------- job-totals-testing-util.js | 28 +- server/accounting/qb-receivables-lines.js | 108 + server/job/job-totals-USA.js | 6 +- 8 files changed, 11351 insertions(+), 10812 deletions(-) diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 3f0edb7b9..403e480ff 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -11747,6 +11747,48 @@ + + ttl_adjustment + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + ttl_tax_adjustment + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + @@ -48360,6 +48402,27 @@ + + tasks_in_view + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + tasks_on_board false @@ -48402,6 +48465,27 @@ + + total_amount_in_view + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + total_amount_on_board false @@ -48444,6 +48528,27 @@ + + total_hours_in_view + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + total_hours_on_board false @@ -48465,6 +48570,27 @@ + + total_jobs_in_view + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + total_jobs_on_board false @@ -48507,6 +48633,27 @@ + + total_lab_in_view + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + total_lab_on_board false @@ -48549,6 +48696,27 @@ + + total_lar_in_view + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + total_lar_on_board false @@ -48724,6 +48892,27 @@ + + tasks_in_view + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + tasks_on_board false @@ -48766,6 +48955,27 @@ + + total_amount_in_view + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + total_amount_on_board false @@ -48808,6 +49018,27 @@ + + total_hours_in_view + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + total_hours_on_board false @@ -48829,6 +49060,27 @@ + + total_jobs_in_view + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + total_jobs_on_board false @@ -48871,6 +49123,27 @@ + + total_lab_in_view + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + total_lab_on_board false @@ -48913,6 +49186,27 @@ + + total_lar_in_view + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + total_lar_on_board false @@ -51761,6 +52055,27 @@ + + production_not_production_status + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + production_over_time false @@ -54225,6 +54540,27 @@ + + created_by + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + description false @@ -54487,6 +54823,27 @@ + + related_items + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + remind_at false diff --git a/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx b/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx index ca3c4722d..8343a9be2 100644 --- a/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx +++ b/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx @@ -4334,6 +4334,70 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) { + + {InstanceRenderManager({ + promanager: "USE_ROME", + rome: ( + Adjustments} id="refund"> + {bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber ? ( + <> + + + + + + + + ) : ( + <> + + + + + + + + )} + + ) + })} + {Qb_Multi_Ar.treatment === "on" && ( Multiple Payers Item} id="accountitem"> {{ro_number}} has a data consistency issue. It may have been excluded for scheduling purposes. CODE: {{code}}.", - "expectedjobs": "Expected Jobs in Production: ", - "expectedprodhrs": "Expected Production Hours:", - "history": "History", - "inproduction": "Jobs In Production", - "manualevent": "Add Manual Appointment", - "noarrivingjobs": "No Jobs are arriving.", - "nocompletingjobs": "No Jobs scheduled for completion.", - "nodateselected": "No date has been selected.", - "priorappointments": "Previous Appointments", - "reminder": "This is {{shopname}} reminding you about an appointment on {{date}} at {{time}}. Please let us know if you are not able to make the appointment. We look forward to seeing you soon. ", - "scheduledfor": "Scheduled appointment for: ", - "severalerrorsfound": "Several Jobs have issues which may prevent accurate smart scheduling. Click to expand.", - "smartscheduling": "Smart Scheduling", - "smspaymentreminder": "This is {{shopname}} reminding you about your remaining balance of {{amount}}. To pay for the said balance click the link {{payment_link}}.", - "suggesteddates": "Suggested Dates" - }, - "successes": { - "canceled": "Appointment canceled successfully.", - "created": "Appointment scheduled successfully.", - "saved": "Appointment saved successfully." - } - }, - "associations": { - "actions": { - "activate": "Activate" - }, - "fields": { - "active": "Active?", - "shopname": "Shop Name" - }, - "labels": { - "actions": "Actions" - } - }, - "audit": { - "fields": { - "cc": "CC", - "contents": "Contents", - "created": "Time", - "operation": "Operation", - "status": "Status", - "subject": "Subject", - "to": "To", - "useremail": "User", - "values": "Values" - } - }, - "audit_trail": { - "messages": { - "admin_job_remove_from_ar": "ADMIN: Remove from AR updated to: {{status}}", - "admin_jobmarkexported": "ADMIN: Job marked as exported.", - "admin_jobmarkforreexport": "ADMIN: Job marked for re-export.", - "admin_jobuninvoice": "ADMIN: Job has been uninvoiced.", - "admin_jobunvoid": "ADMIN: Job has been unvoided.", - "alerttoggle": "Alert Toggle set to {{status}}", - "appointmentcancel": "Appointment canceled. Lost Reason: {{lost_sale_reason}}.", - "appointmentinsert": "Appointment created. Appointment Date: {{start}}.", - "assignedlinehours": "Assigned job lines totaling {{hours}} units to {{team}}.", - "billdeleted": "Bill with invoice number {{invoice_number}} deleted.", - "billposted": "Bill with invoice number {{invoice_number}} posted.", - "billupdated": "Bill with invoice number {{invoice_number}} updated.", - "failedpayment": "Failed payment attempt.", - "jobassignmentchange": "Employee {{name}} assigned to {{operation}}", - "jobassignmentremoved": "Employee assignment removed for {{operation}}", - "jobchecklist": "Checklist type \"{{type}}\" completed. In production set to {{inproduction}}. Status set to {{status}}.", - "jobclosedwithbypass": "Job was invoiced using the master bypass password. ", - "jobconverted": "Job converted and assigned number {{ro_number}}.", - "jobdelivery": "Job intake completed. Status set to {{status}}. Actual completion is {{actual_completion}}.", - "jobexported": "Job has been exported", - "jobfieldchanged": "Job field $t(jobs.fields.{{field}}) changed to {{value}}.", - "jobimported": "Job imported.", - "jobinproductionchange": "Job production status set to {{inproduction}}", - "jobintake": "Job intake completed. Status set to {{status}}. Scheduled completion is {{scheduled_completion}}.", - "jobinvoiced": "Job has been invoiced.", - "jobioucreated": "IOU Created.", - "jobmodifylbradj": "Labor adjustments modified {{mod_lbr_ty}} / {{hours}}.", - "jobnoteadded": "Note added to Job.", - "jobnotedeleted": "Note deleted from Job.", - "jobnoteupdated": "Note updated on Job.", - "jobspartsorder": "Parts order {{order_number}} added to Job.", - "jobspartsreturn": "Parts return {{order_number}} added to Job.", - "jobstatuschange": "Job status changed to {{status}}.", - "jobsupplement": "Job supplement imported.", - "jobsuspend": "Suspend Toggle set to {{status}}", - "jobvoid": "Job has been voided.", - "tasks_completed": "Task '{{title}}' completed by {{completedBy}}", - "tasks_created": "Task '{{title}}' created by {{createdBy}}", - "tasks_deleted": "Task '{{title}}' deleted by {{deletedBy}}", - "tasks_uncompleted": "Task '{{title}}' uncompleted by {{uncompletedBy}}", - "tasks_undeleted": "Task '{{title}}' undeleted by {{undeletedBy}}", - "tasks_updated": "Task '{{title}}' updated by {{updatedBy}}" - } - }, - "billlines": { - "actions": { - "newline": "New Line" - }, - "fields": { - "actual_cost": "Actual Cost", - "actual_price": "Retail", - "cost_center": "Cost Center", - "federal_tax_applicable": "Fed. Tax?", - "jobline": "Job Line", - "line_desc": "Line Description", - "local_tax_applicable": "Loc. Tax?", - "location": "Location", - "quantity": "Quantity", - "state_tax_applicable": "St. Tax?" - }, - "labels": { - "deductedfromlbr": "Deduct from Labor?", - "entered": "Entered", - "from": "From", - "mod_lbr_adjustment": "Adjustment Units", - "other": "-- Not On Estimate --", - "reconciled": "Reconciled!", - "unreconciled": "Unreconciled" - }, - "validation": { - "atleastone": "At least one bill line must be entered." - } - }, - "bills": { - "actions": { - "deductallhours": "Deduct all", - "edit": "Edit", - "receive": "Receive Part", - "return": "Return Items" - }, - "errors": { - "creating": "Error adding bill. {{error}}", - "deleting": "Error deleting bill. {{error}}", - "existinginventoryline": "This bill cannot be deleted as it is tied to items in inventory.", - "exporting": "Error exporting payable(s). {{error}}", - "exporting-partner": "Unable to connect to partner application. Please ensure it is running and logged in.", - "invalidro": "Not a valid RO.", - "invalidvendor": "Not a valid vendor.", - "validation": "Please ensure all fields are entered correctly. " - }, - "fields": { - "allpartslocation": "Parts Bin", - "date": "Bill Date", - "exported": "Exported", - "federal_tax_rate": "Federal Tax Rate", - "invoice_number": "Invoice Number", - "is_credit_memo": "Credit Memo?", - "is_credit_memo_short": "CM", - "local_tax_rate": "Local Tax Rate", - "ro_number": "RO Number", - "state_tax_rate": "State Tax Rate", - "total": "Bill Total", - "vendor": "Vendor", - "vendorname": "Vendor Name" - }, - "labels": { - "actions": "Actions", - "bill_lines": "Bill Lines", - "bill_total": "Bill Total Amount", - "billcmtotal": "Credit Memos", - "bills": "Bills", - "calculatedcreditsnotreceived": "Calculated CNR", - "creditsnotreceived": "Credits Not Marked Received", - "creditsreceived": "Credits Received", - "dedfromlbr": "Labor Adjustments", - "deleteconfirm": "Are you sure you want to delete this bill? It cannot be undone. If this bill has deductions from labors, manual changes may be required.", - "discrepancy": "Discrepancy", - "discrepwithcms": "Discrepancy including Credit Memos", - "discrepwithlbradj": "Discrepancy including Lbr. Adj.", - "editadjwarning": "This bill had lines which resulted in labor adjustments. Manual correction to adjustments may be required.", - "entered_total": "Total of Entered Lines", - "enteringcreditmemo": "You are entering a credit memo. Please ensure you are also entering positive values.", - "federal_tax": "Federal Tax", - "federal_tax_exempt": "Federal Tax Exempt?", - "generatepartslabel": "Generate Parts Labels after Saving?", - "iouexists": "An IOU exists that is associated to this RO.", - "local_tax": "Local Tax", - "markexported": "Mark Exported", - "markforreexport": "Mark for Re-export", - "new": "New Bill", - "nobilllines": "", - "noneselected": "No bill selected.", - "onlycmforinvoiced": "Only credit memos can be entered for any Job that has been invoiced, exported, or voided.", - "printlabels": "Print Labels", - "retailtotal": "Bills Retail Total", - "returnfrombill": "Return From Bill", - "savewithdiscrepancy": "You are about to save this bill with a discrepancy. The system will continue to use the calculated amount using the bill lines. Press cancel to return to the bill.", - "state_tax": "State Tax", - "subtotal": "Subtotal", - "totalreturns": "Total Returns" - }, - "successes": { - "created": "Invoice added successfully.", - "deleted": "Bill deleted successfully.", - "exported": "Bill(s) exported successfully.", - "markexported": "Bill marked as exported.", - "reexport": "Bill marked for re-export." - }, - "validation": { - "closingperiod": "This Bill Date is outside of the Closing Period.", - "inventoryquantity": "Quantity must be greater than or equal to what has been added to inventory ({{number}}).", - "manualinhouse": "Manual posting to the in house vendor is restricted. ", - "unique_invoice_number": "This invoice number has already been entered for this vendor." - } - }, - "bodyshop": { - "actions": { - "add_task_preset": "Add Task Preset", - "addapptcolor": "Add Appointment Color", - "addbucket": "Add Definition", - "addpartslocation": "Add Parts Location", - "addpartsrule": "Add Parts Scan Rule", - "addspeedprint": "Add Speed Print", - "addtemplate": "Add Template", - "newlaborrate": "New Labor Rate", - "newsalestaxcode": "New Sales Tax Code", - "newstatus": "Add Status", - "testrender": "Test Render" - }, - "errors": { - "creatingdefaultview": "Error creating default view.", - "loading": "Unable to load shop details. Please call technical support.", - "saving": "Error encountered while saving. {{message}}" - }, - "fields": { - "ReceivableCustomField": "QBO Receivable Custom Field {{number}}", - "address1": "Address 1", - "address2": "Address 2", - "appt_alt_transport": "Appointment Alternative Transportation Options", - "appt_colors": { - "color": "Color", - "label": "Label" - }, - "appt_length": "Default Appointment Length", - "attach_pdf_to_email": "Attach PDF copy to sent emails?", - "batchid": "ADP Batch ID", - "bill_allow_post_to_closed": "Allow Bills to be posted to Closed Jobs", - "bill_federal_tax_rate": "Bills - Federal Tax Rate %", - "bill_local_tax_rate": "Bill - Local Tax Rate %", - "bill_state_tax_rate": "Bill - Provincial/State Tax Rate %", - "city": "City", - "closingperiod": "Closing Period", - "companycode": "ADP Company Code", - "country": "Country", - "dailybodytarget": "Scoreboard - Daily Body Target", - "dailypainttarget": "Scoreboard - Daily Paint Target", - "default_adjustment_rate": "Default Labor Deduction Adjustment Rate", - "deliver": { - "require_actual_delivery_date": "Require Actual Delivery", - "templates": "Delivery Templates" - }, - "dms": { - "apcontrol": "AP Control Number", - "appostingaccount": "AP Posting Account", - "cashierid": "Cashier ID", - "default_journal": "Default Journal", - "disablebillwip": "Disable bill WIP for A/P Posting", - "disablecontactvehiclecreation": "Disable Contact & Vehicle Updates/Creation", - "dms_acctnumber": "DMS Account #", - "dms_control_override": "Static Control # Override", - "dms_wip_acctnumber": "DMS W.I.P. Account #", - "generic_customer_number": "Generic Customer Number", - "itc_federal": "Federal Tax is ITC?", - "itc_local": "Local Tax is ITC?", - "itc_state": "State Tax is ITC?", - "mappingname": "DMS Mapping Name", - "sendmaterialscosting": "Materials Cost as % of Sale", - "srcco": "Source Company #/Dealer #" - }, - "email": "General Shop Email", - "enforce_class": "Enforce Class on Conversion?", - "enforce_conversion_category": "Enforce Category on Conversion?", - "enforce_conversion_csr": "Enforce CSR on Conversion?", - "enforce_referral": "Enforce Referrals", - "federal_tax_id": "Federal Tax ID (GST/HST)", - "ignoreblockeddays": "Scoreboard - Ignore Blocked Days", - "inhousevendorid": "In House Vendor ID", - "insurance_vendor_id": "Insurance Vendor ID", - "intake": { - "next_contact_hours": "Automatic Next Contact Date - Hours from Intake", - "templates": "Intake Templates" - }, - "intellipay_config": { - "cash_discount_percentage": "Cash Discount %", - "enable_cash_discount": "Enable Cash Discounting" - }, - "invoice_federal_tax_rate": "Invoices - Federal Tax Rate", - "invoice_local_tax_rate": "Invoices - Local Tax Rate", - "invoice_state_tax_rate": "Invoices - State Tax Rate", - "jc_hourly_rates": { - "mapa": "Job Costing - Paint Materials Hourly Cost Rate", - "mash": "Job Costing - Shop Materials Hourly Cost Rate" - }, - "last_name_first": "Display Owner Info as , ", - "lastnumberworkingdays": "Scoreboard - Last Number of Working Days", - "localmediaserverhttp": "Local Media Server - HTTP Path", - "localmediaservernetwork": "Local Media Server - Network Path", - "localmediatoken": "Local Media Server - Token", - "logo_img_footer_margin": "Footer Margin (px)", - "logo_img_header_margin": "Header Margin (px)", - "logo_img_path": "Shop Logo", - "logo_img_path_height": "Logo Image Height", - "logo_img_path_width": "Logo Image Width", - "md_categories": "Categories", - "md_ccc_rates": "Courtesy Car Contract Rate Presets", - "md_classes": "Classes", - "md_ded_notes": "Deductible Notes", - "md_email_cc": "Auto Email CC: $t(printcenter.subjects.jobs.{{template}})", - "md_from_emails": "Additional From Emails", - "md_functionality_toggles": { - "parts_queue_toggle": "Auto Add Imported/Supplemented Jobs to Parts Queue" - }, - "md_hour_split": { - "paint": "Paint Hour Split", - "prep": "Prep Hour Split" - }, - "md_ins_co": { - "city": "City", - "name": "Insurance Company Name", - "private": "Private", - "state": "Province/State", - "street1": "Street 1", - "street2": "Street 2", - "zip": "Zip/Postal Code" - }, - "md_jobline_presets": "Jobline Presets", - "md_lost_sale_reasons": "Lost Sale Reasons", - "md_parts_order_comment": "Parts Orders Comments", - "md_parts_scan": { - "expression": "RegEX Expression", - "flags": "Flags" - }, - "md_payment_types": "Payment Types", - "md_referral_sources": "Referral Sources", - "md_ro_guard": { - "enabled": "RO Guard Enabled?", - "enforce_ar": "Enforce AR Balance", - "enforce_bills": "Enforce Bill Discrepancy", - "enforce_cm": "Enforce Credit Memo Entry", - "enforce_labor": "Enforce Labor Allocation", - "enforce_ppd": "Enforce PPD Sync", - "enforce_profit": "Enforce Profit Requirement", - "enforce_sublet": "Enforce Sublet Completion", - "masterbypass": "Master Bypass Password (not encrypted)", - "totalgppercent_minimum": "Minimum Total Gross Profit %" - }, - "md_tasks_presets": { - "enable_tasks": "Enable Hour Flagging", - "hourstype": "Hour Types", - "memo": "Time Ticket Memo", - "name": "Preset Name", - "nextstatus": "Next Status", - "percent": "Percent", - "use_approvals": "Use Time Ticket Approval Queue" - }, - "messaginglabel": "Messaging Preset Label", - "messagingtext": "Messaging Preset Text", - "noteslabel": "Note Label", - "notestext": "Note Text", - "partslocation": "Parts Location", - "phone": "Phone", - "prodtargethrs": "Production Target Hours", - "rbac": { - "accounting": { - "exportlog": "Accounting -> Export Log", - "payables": "Accounting -> Payables", - "payments": "Accounting -> Payments", - "receivables": "Accounting -> Receivables" - }, - "bills": { - "delete": "Bills -> Delete", - "enter": "Bills -> Enter", - "list": "Bills -> List", - "reexport": "Bills -> Re-export", - "view": "Bills -> View" - }, - "contracts": { - "create": "Contracts -> Create", - "detail": "Contracts -> Detail", - "list": "Contracts -> List" - }, - "courtesycar": { - "create": "Courtesy Car -> Create", - "detail": "Courtesy Car -> Detail", - "list": "Courtesy Car -> List" - }, - "csi": { - "export": "CSI -> Export", - "page": "CSI -> Page" - }, - "employee_teams": { - "page": "Employee Teams -> List" - }, - "employees": { - "page": "Employees -> List" - }, - "inventory": { - "delete": "Inventory -> Delete", - "list": "Inventory -> List" - }, - "jobs": { - "admin": "Jobs -> Admin", - "available-list": "Jobs -> Available List", - "checklist-view": "Jobs -> Checklist View", - "close": "Jobs -> Close", - "create": "Jobs -> Create", - "deliver": "Jobs -> Deliver", - "detail": "Jobs -> Detail", - "intake": "Jobs -> Intake", - "list-active": "Jobs -> List Active", - "list-all": "Jobs -> List All", - "list-ready": "Jobs -> List Ready", - "partsqueue": "Jobs -> Parts Queue", - "void": "Jobs -> Void" - }, - "owners": { - "detail": "Owners -> Detail", - "list": "Owners -> List" - }, - "payments": { - "enter": "Payments -> Enter", - "list": "Payments -> List" - }, - "phonebook": { - "edit": "Phonebook -> Edit", - "view": "Phonebook -> View" - }, - "production": { - "board": "Production -> Board", - "list": "Production -> List" - }, - "schedule": { - "view": "Schedule -> View" - }, - "scoreboard": { - "view": "Scoreboard -> View" - }, - "shiftclock": { - "view": "Shift Clock -> View" - }, - "shop": { - "config": "Shop -> Config", - "dashboard": "Shop -> Dashboard", - "rbac": "Shop -> RBAC", - "reportcenter": "Shop -> Report Center", - "templates": "Shop -> Templates", - "vendors": "Shop -> Vendors" - }, - "temporarydocs": { - "view": "Temporary Docs -> View" - }, - "timetickets": { - "edit": "Time Tickets -> Edit", - "editcommitted": "Time Tickets -> Edit Committed", - "enter": "Time Tickets -> Enter", - "list": "Time Tickets -> List", - "shiftedit": "Time Tickets -> Shift Edit" - }, - "ttapprovals": { - "approve": "Time Ticket Approval -> Approve", - "view": "Time Ticket Approval -> View" - }, - "users": { - "editaccess": "Users -> Edit access" - } - }, - "responsibilitycenter": "Responsibility Center", - "responsibilitycenter_accountdesc": "Account Description", - "responsibilitycenter_accountitem": "Item", - "responsibilitycenter_accountname": "Account Name", - "responsibilitycenter_accountnumber": "Account Number", - "responsibilitycenter_rate": "Rate", - "responsibilitycenter_tax_rate": "Tax {{typeNum}} Tier {{typeNumIterator}} Rate", - "responsibilitycenter_tax_sur": "Tax {{typeNum}} Tier {{typeNumIterator}} Surcharge", - "responsibilitycenter_tax_thres": "Tax {{typeNum}} Tier {{typeNumIterator}} Threshold", - "responsibilitycenter_tax_tier": "Tax {{typeNum}} Tier {{typeNumIterator}}", - "responsibilitycenter_tax_type": "Tax {{typeNum}} Type", - "responsibilitycenters": { - "ap": "Accounts Payable", - "ar": "Accounts Receivable", - "ats": "ATS", - "federal_tax": "Federal Tax", - "federal_tax_itc": "Federal Tax Credit", - "gst_override": "GST Override Account #", - "invoiceexemptcode": "QuickBooks US - Invoice Tax Exempt Code", - "itemexemptcode": "QuickBooks US - Line Item Tax Exempt Code", - "la1": "LA1", - "la2": "LA2", - "la3": "LA3", - "la4": "LA4", - "laa": "Aluminum", - "lab": "Body", - "lad": "Diagnostic", - "lae": "Electrical", - "laf": "Frame", - "lag": "Glass", - "lam": "Mechanical", - "lar": "Refinish", - "las": "Structural", - "lau": "User Defined", - "local_tax": "Local Tax", - "mapa": "Paint Materials", - "mash": "Shop Materials", - "paa": "Aftermarket", - "pac": "Chrome", - "pag": "Glass", - "pal": "LKQ", - "pam": "Remanufactured", - "pan": "OEM", - "pao": "Other", - "pap": "OEM Partial", - "par": "Recored", - "pas": "Sublet", - "pasl": "Sublet (L)", - "refund": "Refund", - "sales_tax_codes": { - "code": "Code", - "description": "Description", - "federal": "Federal Tax Applies", - "local": "Local Tax Applies", - "state": "State Tax Applies" - }, - "state_tax": "State Tax", - "tow": "Towing" - }, - "schedule_end_time": "Schedule Ending Time", - "schedule_start_time": "Schedule Starting Time", - "shopname": "Shop Name", - "speedprint": { - "id": "Id", - "label": "Label", - "templates": "Templates" - }, - "ss_configuration": { - "dailyhrslimit": "Daily Incoming Hours Limit" - }, - "ssbuckets": { - "color": "Job Color", - "gte": "Greater Than/Equal to (hrs)", - "id": "ID", - "label": "Label", - "lt": "Less than (hrs)", - "target": "Target (count)" - }, - "state": "Province/State", - "state_tax_id": "State Tax ID", - "status": "Status Label", - "statuses": { - "active_statuses": "Active Statuses (Filtering for Active Jobs throughout system)", - "additional_board_statuses": "Additional Status to Display on Production Board", - "color": "Color", - "default_arrived": "Default Arrived Status (Transition to Production)", - "default_bo": "Default Backordered Status", - "default_canceled": "Default Canceled Status", - "default_completed": "Default Completed Status", - "default_delivered": "Default Delivered Status (Transition to Post-Production)", - "default_exported": "Default Exported Status", - "default_imported": "Default Imported Status", - "default_invoiced": "Default Invoiced Status", - "default_ordered": "Default Ordered Status", - "default_quote": "Default Quote Status", - "default_received": "Default Received Status", - "default_returned": "Default Returned", - "default_scheduled": "Default Scheduled Status", - "default_void": "Default Void", - "open_statuses": "Open Statuses", - "post_production_statuses": "Post-Production Statuses", - "pre_production_statuses": "Pre-Production Statuses", - "production_colors": "Production Status Colors", - "production_statuses": "Production Statuses", - "ready_statuses": "Ready Statuses" - }, - "target_touchtime": "Target Touch Time", - "timezone": "Timezone", - "tt_allow_post_to_invoiced": "Allow Time Tickets to be posted to Invoiced & Exported Jobs", - "tt_enforce_hours_for_tech_console": "Restrict Claimable hours from Tech Console", - "use_fippa": "Conceal Customer Information on Generated Documents?", - "use_paint_scale_data": "Use Paint Scale Data for Job Costing?", - "uselocalmediaserver": "Use Local Media Server?", - "website": "Website", - "zip_post": "Zip/Postal Code" - }, - "labels": { - "2tiername": "Name => RO", - "2tiersetup": "2 Tier Setup", - "2tiersource": "Source => RO", - "accountingsetup": "Accounting Setup", - "accountingtiers": "Number of Tiers to Use for Export", - "alljobstatuses": "All Job Statuses", - "allopenjobstatuses": "All Open Job Statuses", - "apptcolors": "Appointment Colors", - "businessinformation": "Business Information", - "checklists": "Checklists", - "csiq": "CSI Questions", - "customtemplates": "Custom Templates", - "defaultcostsmapping": "Default Costs Mapping", - "defaultprofitsmapping": "Default Profits Mapping", - "deliverchecklist": "Delivery Checklist", - "dms": { - "cdk": { - "controllist": "Control Number List", - "payers": "Payers" - }, - "cdk_dealerid": "CDK Dealer ID", - "costsmapping": "Costs Mapping", - "dms_allocations": "DMS Allocations", - "pbs_serialnumber": "PBS Serial Number", - "profitsmapping": "Profits Mapping", - "title": "DMS" - }, - "emaillater": "Email Later", - "employee_teams": "Employee Teams", - "employees": "Employees", - "estimators": "Estimators", - "filehandlers": "Adjusters", - "insurancecos": "Insurance Companies", - "intakechecklist": "Intake Checklist", - "intellipay": "IntelliPay", - "intellipay_cash_discount": "Please ensure that cash discounting has been enabled on your merchant account. Reach out to IntelliPay Support if you need assistance. ", - "jobstatuses": "Job Statuses", - "laborrates": "Labor Rates", - "licensing": "Licensing", - "md_parts_scan": "Parts Scan Rules", - "md_ro_guard": "RO Guard", - "md_tasks_presets": "Tasks Presets", - "md_to_emails": "Preset To Emails", - "md_to_emails_emails": "Emails", - "messagingpresets": "Messaging Presets", - "notemplatesavailable": "No templates available to add.", - "notespresets": "Notes Presets", - "orderstatuses": "Order Statuses", - "partslocations": "Parts Locations", - "partsscan": "Critical Parts Scanning", - "printlater": "Print Later", - "qbo": "Use QuickBooks Online?", - "qbo_departmentid": "QBO Department ID", - "qbo_usa": "QBO USA Compatibility", - "rbac": "Role Based Access Control", - "responsibilitycenters": { - "costs": "Cost Centers", - "profits": "Profit Centers", - "sales_tax_codes": "Sales Tax Codes", - "tax_accounts": "Tax Accounts", - "title": "Responsibility Centers" - }, - "roguard": { - "title": "RO Guard" - }, - "scheduling": "SMART Scheduling", - "scoreboardsetup": "Scoreboard Setup", - "shopinfo": "Shop Information", - "speedprint": "Speed Print Configuration", - "ssbuckets": "Job Size Definitions", - "systemsettings": "System Settings", - "task-presets": "Task Presets", - "workingdays": "Working Days" - }, - "successes": { - "areyousure": "Are you sure you want to continue?", - "defaultviewcreated": "Default view created successfully.", - "save": "Shop configuration saved successfully. ", - "unsavedchanges": "Unsaved changes will be lost. Are you sure you want to continue?" - }, - "validation": { - "centermustexist": "The chosen responsibility center does not exist.", - "larsplit": "Refinish hour split must add up to 1.", - "useremailmustexist": "This email is not a valid user." - } - }, - "checklist": { - "actions": { - "printall": "Print All Documents" - }, - "errors": { - "complete": "Error during Job checklist completion. {{error}}", - "nochecklist": "No checklist has been configured for your shop. " - }, - "labels": { - "addtoproduction": "Add Job to Production?", - "allow_text_message": "Permission to Text?", - "checklist": "Checklist", - "printpack": "Job Intake Print Pack", - "removefromproduction": "Remove Job from Production?" - }, - "successes": { - "completed": "Job checklist completed." - } - }, - "contracts": { - "actions": { - "changerate": "Change Contract Rates", - "convertoro": "Convert to RO", - "decodelicense": "Decode License", - "find": "Find Contract", - "printcontract": "Print Contract", - "senddltoform": "Insert Driver's License Information" - }, - "errors": { - "fetchingjobinfo": "Error fetching Job Info. {{error}}.", - "returning": "Error returning Courtesy Car. {{error}}", - "saving": "Error saving Contract. {{error}}", - "selectjobandcar": "Please ensure both a car and job are selected." - }, - "fields": { - "actax": "A/C Tax", - "actualreturn": "Actual Return Date", - "agreementnumber": "Agreement Number", - "cc_cardholder": "Cardholder Name", - "cc_expiry": "Credit Card Expiry Date", - "cc_num": "Credit Card Number", - "cleanupcharge": "Clean Up Charge", - "coverage": "Coverage", - "dailyfreekm": "Daily Free Mileage", - "dailyrate": "Daily Rate", - "damage": "Existing Damage", - "damagewaiver": "Damage Waiver", - "driver": "Driver", - "driver_addr1": "Driver Address 1", - "driver_addr2": "Driver Address 2", - "driver_city": "Driver City", - "driver_dlexpiry": "Driver's License Expiration Date", - "driver_dlnumber": "Driver's License Number", - "driver_dlst": "Driver's License Province/State", - "driver_dob": "Driver's DOB", - "driver_fn": "Driver's First Name", - "driver_ln": "Driver's Last Name", - "driver_ph1": "Driver's Phone", - "driver_state": "Driver's Province/State ", - "driver_zip": "Driver's Postal/ZIP Code", - "excesskmrate": "Excess Mileage", - "federaltax": "Federal Taxes", - "fuelin": "Fuel In", - "fuelout": "Fuel Out", - "kmend": "Mileage End", - "kmstart": "Mileage Start", - "length": "Length", - "localtax": "Local Taxes", - "refuelcharge": "Refuel Charge (per liter/gallon)", - "scheduledreturn": "Scheduled Return", - "start": "Contract Start", - "statetax": "Provincial/State Taxes", - "status": "Status" - }, - "labels": { - "agreement": "Agreement {{agreement_num}} - {{status}}", - "availablecars": "Available Cars", - "cardueforservice": "The courtesy car is due for servicing.", - "convertform": { - "applycleanupcharge": "Apply cleanup charge?", - "refuelqty": "Refuel qty.?" - }, - "correctdataonform": "Please review the information above. If any of it is not correct, you can fix it later.", - "dateinpast": "Date is in the past.", - "dlexpirebeforereturn": "The driver's license expires before the car is expected to return. ", - "driverinformation": "Driver's Information", - "findcontract": "Find Contract", - "findermodal": "Contract Finder", - "insuranceexpired": "The courtesy car insurance expires before the car is expected to return.", - "noteconvertedfrom": "R.O. created from converted Courtesy Car Contract {{agreementnumber}}.", - "populatefromjob": "Populate from Job", - "rates": "Contract Rates", - "time": "Time", - "vehicle": "Vehicle", - "waitingforscan": "Please scan driver's license barcode..." - }, - "status": { - "new": "New Contract", - "out": "Out", - "returned": "Returned" - }, - "successes": { - "saved": "Contract saved successfully. " - } - }, - "courtesycars": { - "actions": { - "new": "New Courtesy Car", - "return": "Return Car" - }, - "errors": { - "saving": "Error saving Courtesy Car. {{error}}" - }, - "fields": { - "color": "Color", - "dailycost": "Daily Cost to Rent", - "damage": "Damage", - "fleetnumber": "Fleet Number", - "fuel": "Fuel Level", - "insuranceexpires": "Insurance Expires On", - "leaseenddate": "Lease Ends On", - "make": "Make", - "mileage": "Mileage", - "model": "Model", - "nextservicedate": "Next Service Date", - "nextservicekm": "Next Service KMs", - "notes": "Notes", - "plate": "Plate Number", - "purchasedate": "Purchase Date", - "readiness": "Readiness", - "registrationexpires": "Registration Expires On", - "serviceenddate": "Usage End Date", - "servicestartdate": "Usage Start Date", - "status": "Status", - "vin": "VIN", - "year": "Year" - }, - "labels": { - "courtesycar": "Courtesy Car", - "fuel": { - "12": "1/2", - "14": "1/4", - "18": "1/8", - "34": "3/4", - "38": "3/8", - "58": "5/8", - "78": "7/8", - "empty": "Empty", - "full": "Full" - }, - "outwith": "Out With", - "return": "Return Courtesy Car", - "status": "Status", - "uniquefleet": "Enter a unique fleet number.", - "usage": "Usage", - "vehicle": "Vehicle Description" - }, - "readiness": { - "notready": "Not Ready", - "ready": "Ready" - }, - "status": { - "in": "Available", - "inservice": "Service/Maintenance", - "leasereturn": "Lease Returned", - "out": "Rented", - "sold": "Sold", - "unavailable": "Unavailable" - }, - "successes": { - "saved": "Courtesy Car saved successfully." - } - }, - "csi": { - "actions": { - "activate": "Activate" - }, - "errors": { - "creating": "Error creating survey {{message}}", - "notconfigured": "You do not have any current CSI Question Sets configured.", - "notfoundsubtitle": "We were unable to find a survey using the link you provided. Please ensure the URL is correct or reach out to your shop for more help.", - "notfoundtitle": "No survey found.", - "surveycompletesubtitle": "", - "surveycompletetitle": "" - }, - "fields": { - "completedon": "Completed On", - "created_at": "Created At", - "surveyid": "", - "validuntil": "" - }, - "labels": { - "copyright": "", - "greeting": "", - "intro": "", - "nologgedinuser": "Please log out of {{app}}", - "nologgedinuser_sub": "Users of {{app}} cannot complete CSI surveys while logged in. Please log out and try again.", - "noneselected": "No response selected.", - "title": "Customer Satisfaction Survey" - }, - "successes": { - "created": "CSI created successfully. ", - "submitted": "Your responses have been submitted successfully.", - "submittedsub": "Your input is highly appreciated." - } - }, - "dashboard": { - "actions": { - "addcomponent": "Add Component" - }, - "errors": { - "refreshrequired": "You must refresh the dashboard data to see this component.", - "updatinglayout": "Error saving updated layout {{message}}" - }, - "labels": { - "bodyhrs": "Body Hrs", - "dollarsinproduction": "Dollars in Production", - "phone": "Phone", - "prodhrs": "Production Hrs", - "refhrs": "Refinish Hrs" - }, - "titles": { - "joblifecycle": "Job Life Cycles", - "labhours": "Total Body Hours", - "larhours": "Total Refinish Hours", - "monthlyemployeeefficiency": "Monthly Employee Efficiency", - "monthlyjobcosting": "Monthly Job Costing ", - "monthlylaborsales": "Monthly Labor Sales", - "monthlypartssales": "Monthly Parts Sales", - "monthlyrevenuegraph": "Monthly Revenue Graph", - "prodhrssummary": "Production Hours Summary", - "productiondollars": "Total Dollars in Production", - "productionhours": "Total Hours in Production", - "projectedmonthlysales": "Projected Monthly Sales", - "scheduledindate": "Sheduled In Today: {{date}}", - "scheduledintoday": "Sheduled In Today", - "scheduledoutdate": "Sheduled Out Today: {{date}}", - "scheduledouttoday": "Sheduled Out Today", - "tasks": "Tasks" - } - }, - "dms": { - "errors": { - "alreadyexported": "This job has already been sent to the DMS. If you need to resend it, please use admin permissions to mark the job for re-export." - }, - "labels": { - "refreshallocations": "Refresh to see DMS Allocataions." - } - }, - "documents": { - "actions": { - "delete": "Delete Selected Documents", - "download": "Download Selected Images", - "reassign": "Reassign to another Job", - "selectallimages": "Select All Images", - "selectallotherdocuments": "Select All Other Documents" - }, - "errors": { - "deletes3": "Error deleting document from storage. ", - "deleting": "Error deleting documents {{error}}", - "deleting_cloudinary": "Error deleting document from storage. {{message}}", - "getpresignurl": "Error obtaining presigned URL for document. {{message}}", - "insert": "Unable to upload file. {{message}}", - "nodocuments": "There are no documents.", - "updating": "Error updating document. {{error}}" - }, - "labels": { - "confirmdelete": "Are you sure you want to delete these documents. This CANNOT be undone.", - "doctype": "Document Type", - "newjobid": "Assign to Job", - "openinexplorer": "Open in Explorer", - "optimizedimage": "The below image is optimized. Click on the picture below to open in a new window and view it full size, or open it in explorer.", - "reassign_limitexceeded": "Reassigning all selected documents will exceed the job storage limit for your shop. ", - "reassign_limitexceeded_title": "Unable to reassign document(s)", - "storageexceeded": "You've exceeded your storage limit for this job. Please remove documents, or increase your storage plan.", - "storageexceeded_title": "Storage Limit Exceeded", - "upload": "Upload", - "upload_limitexceeded": "Uploading all selected documents will exceed the job storage limit for your shop. ", - "upload_limitexceeded_title": "Unable to upload document(s)", - "uploading": "Uploading...", - "usage": "of Job storage used. ({{used}} / {{total}})" - }, - "successes": { - "delete": "Document(s) deleted successfully.", - "edituploaded": "Edited document uploaded successfully. Please close this window and refresh the documents list.", - "insert": "Uploaded document successfully. ", - "updated": "Document updated successfully. " - } - }, - "emails": { - "errors": { - "notsent": "Email not sent. Error encountered while sending {{message}}" - }, - "fields": { - "cc": "CC", - "from": "From", - "subject": "Subject", - "to": "To" - }, - "labels": { - "attachments": "Attachments", - "documents": "Documents", - "emailpreview": "Email Preview", - "generatingemail": "Generating email...", - "pdfcopywillbeattached": "A PDF copy of this email will be attached when it is sent.", - "preview": "Email Preview" - }, - "successes": { - "sent": "Email sent successfully." - } - }, - "employee_teams": { - "actions": { - "new": "New Team", - "newmember": "New Team Member" - }, - "fields": { - "active": "Active", - "employeeid": "Employee", - "max_load": "Max Load", - "name": "Team Name", - "percentage": "Percent" - } - }, - "employees": { - "actions": { - "addvacation": "Add Vacation", - "new": "New Employee", - "newrate": "New Rate" - }, - "errors": { - "delete": "Error encountered while deleting employee. {{message}}", - "save": "Error encountered saving employee. {{message}}", - "validation": "Please check all fields.", - "validationtitle": "Unable to save employee." - }, - "fields": { - "active": "Active?", - "base_rate": "Base Rate", - "cost_center": "Cost Center", - "employee_number": "Employee Number", - "external_id": "External Employee ID", - "first_name": "First Name", - "flat_rate": "Flat Rate (Disabled is Straight Time)", - "hire_date": "Hire Date", - "last_name": "Last Name", - "pin": "Tech Console PIN", - "rate": "Rate", - "termination_date": "Termination Date", - "user_email": "User Email", - "vacation": { - "end": "Vacation End", - "length": "Vacation Length", - "start": "Vacation Start" - } - }, - "labels": { - "actions": "Actions", - "active": "Active", - "endmustbeafterstart": "End date must be after start date.", - "flat_rate": "Flat Rate", - "inactive": "Inactive", - "name": "Name", - "rate_type": "Rate Type", - "status": "Status", - "straight_time": "Straight Time" - }, - "successes": { - "delete": "Employee deleted successfully.", - "save": "Employee saved successfully.", - "vacationadded": "Employee vacation added." - }, - "validation": { - "unique_employee_number": "You must enter a unique employee number." - } - }, - "eula": { - "buttons": { - "accept": "Accept EULA" - }, - "content": { - "never_scrolled": "You must scroll to the bottom of the Terms and Conditions before accepting." - }, - "errors": { - "acceptance": { - "description": "Something went wrong while accepting the EULA. Please try again.", - "message": "Eula Acceptance Error" - } - }, - "labels": { - "accepted_terms": "I accept the terms and conditions of this agreement.", - "address": "Address", - "business_name": "Legal Business Name", - "date_accepted": "Date Accepted", - "first_name": "First Name", - "last_name": "Last Name", - "phone_number": "Phone Number" - }, - "messages": { - "accepted_terms": "Please accept the terms and conditions of this agreement.", - "business_name": "Please enter your legal business name.", - "date_accepted": "Please enter Today's Date.", - "first_name": "Please enter your first name.", - "last_name": "Please enter your last name.", - "phone_number": "Please enter your phone number." - }, - "titles": { - "modal": "Terms and Conditions", - "upper_card": "Acknowledgement" - } - }, - "exportlogs": { - "fields": { - "createdat": "Created At" - }, - "labels": { - "attempts": "Export Attempts", - "priorsuccesfulexport": "This record has previously been exported successfully. Please make sure it has already been deleted in the target system." - } - }, - "general": { - "actions": { - "add": "Add", - "autoupdate": "{{app}} will automatically update in {{time}} seconds. Please save all changes.", - "calculate": "Calculate", - "cancel": "Cancel", - "clear": "Clear", - "close": "Close", - "copied": "Copied!", - "copylink": "Copy Link", - "create": "Create", - "defaults": "Defaults", - "delay": "Delay Update (5 mins)", - "delete": "Delete", - "deleteall": "Delete All", - "deselectall": "Deselect All", - "download": "Download", - "edit": "Edit", - "login": "Login", - "next": "Next", - "previous": "Previous", - "print": "Print", - "refresh": "Refresh", - "remove": "Remove", - "remove_alert": "Are you sure you want to dismiss the alert?", - "reset": "Reset your changes.", - "resetpassword": "Reset Password", - "save": "Save", - "saveandnew": "Save and New", - "saveas": "Save As", - "selectall": "Select All", - "send": "Send", - "sendbysms": "Send by SMS", - "senderrortosupport": "Send Error to Support", - "submit": "Submit", - "tryagain": "Try Again", - "view": "View", - "viewreleasenotes": "See What's Changed" - }, - "errors": { - "fcm": "You must allow notification permissions to have real time messaging. Click to try again.", - "notfound": "No record was found.", - "sizelimit": "The selected items exceed the size limit." - }, - "itemtypes": { - "contract": "CC Contract", - "courtesycar": "Courtesy Car", - "job": "Job", - "owner": "Owner", - "vehicle": "Vehicle" - }, - "labels": { - "actions": "Actions", - "areyousure": "Are you sure?", - "barcode": "Barcode", - "cancel": "Are you sure you want to cancel? Your changes will not be saved.", - "clear": "Clear", - "confirmpassword": "Confirm Password", - "created_at": "Created At", - "date": "Select Date", - "datetime": "Select Date & Time", - "email": "Email", - "errors": "Errors", - "excel": "Excel", - "exceptiontitle": "An error has occurred.", - "friday": "Friday", - "globalsearch": "Global Search", - "help": "Help", - "hours": "hrs", - "in": "In", - "instanceconflictext": "Your {{app}} account can only be used on one device at any given time. Refresh your session to take control.", - "instanceconflictitle": "Your account is being used elsewhere.", - "item": "Item", - "label": "Label", - "loading": "Loading...", - "loadingapp": "Loading {{app}}", - "loadingshop": "Loading shop data...", - "loggingin": "Authorizing...", - "markedexported": "Manually marked as exported.", - "media": "Media", - "message": "Message", - "monday": "Monday", - "na": "N/A", - "newpassword": "New Password", - "no": "No", - "nointernet": "It looks like you're not connected to the internet.", - "nointernet_sub": "Please check your connection and try again. ", - "none": "None", - "out": "Out", - "password": "Password", - "passwordresetsuccess": "A password reset link has been sent to you.", - "passwordresetsuccess_sub": "You should receive this email in the next few minutes. Please check your email including any junk or spam folders. ", - "passwordresetvalidatesuccess": "Password successfully reset. ", - "passwordresetvalidatesuccess_sub": "You may now sign in again using your new password. ", - "passwordsdonotmatch": "The passwords you have entered do not match.", - "print": "Print", - "refresh": "Refresh", - "reports": "Reports", - "required": "Required", - "saturday": "Saturday", - "search": "Search...", - "searchresults": "Results for {{search}}", - "selectdate": "Select date...", - "sendagain": "Send Again", - "sendby": "Send By", - "signin": "Sign In", - "sms": "SMS", - "status": "Status", - "sub_status": { - "expired": "The subscription for this shop has expired. Please contact technical support to reactivate the subscription. " - }, - "successful": "Successful", - "sunday": "Sunday", - "text": "Text", - "thursday": "Thursday", - "total": "Total", - "totals": "Totals", - "tuesday": "Tuesday", - "tvmode": "TV Mode", - "unknown": "Unknown", - "unsavedchanges": "Unsaved changes.", - "username": "Username", - "view": "View", - "wednesday": "Wednesday", - "yes": "Yes" - }, - "languages": { - "english": "English", - "french": "French", - "spanish": "Spanish" - }, - "messages": { - "exception": "{{app}} has encountered an error. Please try again. If the problem persists, please submit a support ticket or contact us.", - "newversionmessage": "Click refresh below to update to the latest available version of {{app}}. Please make sure all other tabs and windows are closed.", - "newversiontitle": "New version of {{app}} Available", - "noacctfilepath": "There is no accounting file path set. You will not be able to export any items.", - "nofeatureaccess": "You do not have access to this feature of {{app}}. Please contact support to request a license for this feature.", - "noshop": "You do not have access to any shops. Please reach out to your shop manager or technical support. ", - "notfoundsub": "Please make sure that you have access to the data or that the link is correct.", - "notfoundtitle": "We couldn't find what you're looking for...", - "partnernotrunning": "{{app}} has detected that the partner is not running. Please ensure it is running to enable full functionality.", - "rbacunauth": "You are not authorized to view this content. Please reach out to your shop manager to change your access level.", - "unsavedchanges": "You have unsaved changes.", - "unsavedchangespopup": "You have unsaved changes. Are you sure you want to leave?" - }, - "validation": { - "invalidemail": "Please enter a valid email.", - "invalidphone": "Please enter a valid phone number.", - "required": "{{label}} is required." - } - }, - "help": { - "actions": { - "connect": "Connect" - }, - "labels": { - "codeplacholder": "6 digit PIN code", - "rescuedesc": "Enter the 6 digit code provided by {{app}} Support below and click connect.", - "rescuetitle": "Rescue Me!" - } - }, - "intake": { - "labels": { - "printpack": "Intake Print Pack" - } - }, - "inventory": { - "actions": { - "addtoinventory": "Add to Inventory", - "addtoro": "Add to RO", - "consumefrominventory": "Consume from Inventory?", - "edit": "Edit Inventory LIne", - "new": "New Inventory Line" - }, - "errors": { - "inserting": "Error inserting inventory item. {{error}}" - }, - "fields": { - "comment": "Comment", - "manualinvoicenumber": "Invoice Number", - "manualvendor": "Vendor" - }, - "labels": { - "consumedbyjob": "Consumed by Job", - "deleteconfirm": "Are you sure you want to delete this from inventory? The associated bill will not be modified or deleted. ", - "frombillinvoicenumber": "Original Bill Invoice Number", - "fromvendor": "Original Bill Vendor", - "inventory": "Inventory", - "showall": "Show All Inventory", - "showavailable": "Show Only Available Inventory" - }, - "successes": { - "deleted": "Inventory lined deleted.", - "inserted": "Added line to inventory.", - "updated": "Inventory line updated." - } - }, - "job_lifecycle": { - "columns": { - "duration": "Duration", - "end": "End", - "human_readable": "Human Readable", - "percentage": "Percentage", - "relative_end": "Relative End", - "relative_start": "Relative Start", - "start": "Start", - "status": "Status", - "status_count": "In Status", - "value": "Value" - }, - "content": { - "calculated_based_on": "Calculated based on", - "current_status_accumulated_time": "Current Status Accumulated Time", - "data_unavailable": " There is currently no Lifecycle data for this Job.", - "jobs_in_since": "Jobs in since", - "legend_title": "Legend", - "loading": "Loading Job Timelines....", - "not_available": "N/A", - "previous_status_accumulated_time": "Previous Status Accumulated Time", - "title": "Job Lifecycle Component", - "title_durations": "Historical Status Durations", - "title_loading": "Loading", - "title_transitions": "Transitions" - }, - "errors": { - "fetch": "Error getting Job Lifecycle Data" - }, - "titles": { - "dashboard": "Job Lifecycle", - "top_durations": "Top Durations" - } - }, - "job_payments": { - "buttons": { - "create_short_link": "Generate Short Link", - "goback": "Go Back", - "proceedtopayment": "Proceed to Payment", - "refundpayment": "Refund Payment" - }, - "notifications": { - "error": { - "description": "Please try again. Make sure the refund amount does not exceeds the payment amount.", - "openingip": "Error connecting to IntelliPay service.", - "title": "Error placing refund" - } - }, - "titles": { - "amount": "Amount", - "dateOfPayment": "Date of Payment", - "descriptions": "Payment Details", - "hint": "Hint", - "payer": "Payer", - "payername": "Payer Name", - "paymentid": "Payment Reference ID", - "paymentnum": "Payment Number", - "paymenttype": "Payment Type", - "refundamount": "Refund Amount", - "transactionid": "Transaction ID" - } - }, - "joblines": { - "actions": { - "assign_team": "Assign Team", - "converttolabor": "Convert amount to Labor.", - "dispatchparts": "Dispatch Parts ({{count}})", - "new": "New Line" - }, - "errors": { - "creating": "Error encountered while creating job line. {{message}}", - "updating": "Error encountered updating job line. {{message}}" - }, - "fields": { - "act_price": "Retail Price", - "act_price_before_ppc": "Original Part Price", - "adjustment": "Adjustment", - "ah_detail_line": "Mark as Detail Labor Line (Autohouse Only)", - "amount": "Amount", - "assigned_team": "Team", - "assigned_team_name": "Team {{name}}", - "create_ppc": "Create PPC?", - "db_price": "List Price", - "lbr_types": { - "LA1": "LA1", - "LA2": "LA2", - "LA3": "LA3", - "LA4": "LA4", - "LAA": "Aluminum", - "LAB": "Body", - "LAD": "Diagnostic", - "LAE": "Electrical", - "LAF": "Frame", - "LAG": "Glass", - "LAM": "Mechanical", - "LAR": "Refinish", - "LAS": "Structural", - "LAU": "User Defined" - }, - "line_desc": "Line Desc.", - "line_ind": "S#", - "line_no": "Line #", - "location": "Location", - "mod_lb_hrs": "Hrs", - "mod_lbr_ty": "Labor Type", - "notes": "Notes", - "oem_partno": "OEM Part #", - "op_code_desc": "Op Code Description", - "part_qty": "Qty.", - "part_type": "Part Type", - "part_types": { - "CCC": "CC Cleaning", - "CCD": "CC Damage Waiver", - "CCDR": "CC Daily Rate", - "CCF": "CC Refuel", - "CCM": "CC Mileage", - "PAA": "Aftermarket", - "PAC": "Rechromed", - "PAE": "Existing", - "PAG": "Glass", - "PAL": "LKQ", - "PAM": "Remanufactured", - "PAN": "New/OEM", - "PAO": "Other", - "PAP": "OEM Partial", - "PAR": "Recored", - "PAS": "Sublet", - "PASL": "Sublet (L)" - }, - "profitcenter_labor": "Profit Center: Labor", - "profitcenter_part": "Profit Center: Part", - "prt_dsmk_m": "Line Discount/Markup $", - "prt_dsmk_p": "Line Discount/Markup %", - "status": "Status", - "tax_part": "Tax Part", - "total": "Total", - "unq_seq": "Seq #" - }, - "labels": { - "adjustmenttobeadded": "Adjustment to be added: {{adjustment}}", - "billref": "Latest Bill", - "convertedtolabor": "This line has been converted to labor. Ensure you adjust the profit center for the amount accordingly.", - "edit": "Edit Line", - "ioucreated": "IOU", - "new": "New Line", - "nostatus": "No Status", - "presets": "Jobline Presets" - }, - "successes": { - "created": "Job line created successfully.", - "saved": "Job line saved.", - "updated": "Job line updated successfully." - }, - "validations": { - "ahdetailonlyonuserdefinedtypes": "Detail line indicator can only be set for LA1, LA2, LA3, LA4, and LAU labor types.", - "hrsrequirediflbrtyp": "Labor hours are required if a labor type is selected. Clear the labor type if there are no labor hours.", - "requiredifparttype": "Required if a part type has been specified.", - "zeropriceexistingpart": "This line cannot have any price since it uses an existing part." - } - }, - "jobs": { - "actions": { - "addDocuments": "Add Job Documents", - "addNote": "Add Note", - "addtopartsqueue": "Add to Parts Queue", - "addtoproduction": "Add to Production", - "addtoscoreboard": "Add to Scoreboard", - "allocate": "Allocate", - "autoallocate": "Auto Allocate", - "changefilehandler": "Change Adjuster", - "changelaborrate": "Change Labor Rate", - "changestatus": "Change Status", - "changestimator": "Change Estimator", - "convert": "Convert", - "createiou": "Create IOU", - "deliver": "Deliver", - "dms": { - "addpayer": "Add Payer", - "createnewcustomer": "Create New Customer", - "findmakemodelcode": "Find Make/Model Code", - "getmakes": "Get Makes", - "labels": { - "refreshallocations": "Refresh this component to see the DMS allocations." - }, - "post": "Post", - "refetchmakesmodels": "Refetch Make and Model Codes", - "usegeneric": "Use Generic Customer", - "useselected": "Use Selected Customer" - }, - "dmsautoallocate": "DMS Auto Allocate", - "export": "Export", - "exportcustdata": "Export Customer Data", - "exportselected": "Export Selected", - "filterpartsonly": "Filter Parts Only", - "generatecsi": "Generate CSI & Copy Link", - "gotojob": "Go to Job", - "intake": "Intake", - "manualnew": "Create New Job Manually", - "mark": "Mark", - "markasexported": "Mark as Exported", - "markpstexempt": "Mark Job PST Exempt", - "markpstexemptconfirm": "Are you sure you want to do this? To undo this, you must manually update all PST rates.", - "postbills": "Post Bills", - "printCenter": "Print Center", - "recalculate": "Recalculate", - "reconcile": "Reconcile", - "removefromproduction": "Remove from Production", - "schedule": "Schedule", - "sendcsi": "Send CSI", - "sendpartspricechange": "Send Parts Price Change", - "sendtodms": "Send to DMS", - "sync": "Sync", - "taxprofileoverride": "Override Tax Profile with Shop Configuration", - "taxprofileoverride_confirm": "Are you sure you want to override the tax profile information? This cannot be undone without re-importing the job. ", - "uninvoice": "Uninvoice", - "unvoid": "Unvoid Job", - "viewchecklist": "View Checklists", - "viewdetail": "View Details" - }, - "errors": { - "addingtoproduction": "Error adding to production. {{error}}", - "cannotintake": "Intake cannot be completed for this Job. It has either already been completed or the job is already here.", - "closing": "Error closing Job. {{error}}", - "creating": "Error encountered while creating job. {{error}}", - "deleted": "Error deleting Job. {{error}}", - "exporting": "Error exporting Job. {{error}}", - "exporting-partner": "Unable to connect to partner application. Please ensure it is running and logged in.", - "invoicing": "Error invoicing Job. {{error}}", - "noaccess": "This Job does not exist or you do not have access to it.", - "nodamage": "No damage points on estimate.", - "nodates": "No dates specified for this Job.", - "nofinancial": "No financial data has been calculated yet for this job. Please save it again.", - "nojobselected": "No Job is selected.", - "noowner": "No owner associated.", - "novehicle": "No vehicle associated.", - "partspricechange": "Error sending parts price change. {{error}}.", - "saving": "Error encountered while saving record.", - "scanimport": "Error importing Job. {{message}}", - "totalscalc": "Error while calculating new Job totals.", - "updating": "Error while updating Job(s). {{error}}", - "validation": "Please ensure all fields are entered correctly.", - "validationtitle": "Validation Error", - "voiding": "Error voiding Job. {{error}}" - }, - "fields": { - "active_tasks": "Active Tasks", - "actual_completion": "Actual Completion", - "actual_delivery": "Actual Delivery", - "actual_in": "Actual In", - "adjustment_bottom_line": "Adjustments", - "adjustmenthours": "Adjustment Hours", - "alt_transport": "Alt. Trans.", - "area_of_damage_impact": { - "10": "Left Front Side", - "11": "Left Front Corner", - "12": "Front", - "13": "Rollover", - "14": "Unknown", - "15": "Total Loss", - "16": "Non-collision", - "25": "Hood", - "26": "Deck-lid", - "27": "Roof", - "28": "Undercarriage", - "34": "All Over", - "01": "Right Front Corner", - "02": "Right Front Side", - "03": "Right Side", - "04": "Right Rear Side", - "05": "Right Rear Corner", - "06": "Rear", - "07": "Left Rear Corner", - "08": "Left Rear Side", - "09": "Left Side" - }, - "auto_add_ats": "Automatically Add/Update ATS", - "ca_bc_pvrt": "PVRT", - "ca_customer_gst": "Customer Portion of GST", - "ca_gst_registrant": "GST Registrant", - "category": "Category", - "ccc": "CC Cleaning", - "ccd": "CC Damage Waiver", - "ccdr": "CC Daily Rate", - "ccf": "CC Refuel", - "ccm": "CC Mileage", - "cieca_id": "CIECA ID", - "cieca_pfl": { - "lbr_adjp": "Labor Adjustment", - "lbr_tax_in": "Tax Labor Indicator", - "lbr_taxp": "Labor Tax Rate", - "lbr_tx_in1": "Tax 1 Indicator", - "lbr_tx_in2": "Tax 2 Indicator", - "lbr_tx_in3": "Tax 3 Indicator", - "lbr_tx_in4": "Tax 4 Indicator", - "lbr_tx_in5": "Tax 5 Indicator" - }, - "cieca_pfo": { - "stor_t_in1": "Storage Tax 1 Indicator", - "stor_t_in2": "Storage Tax 2 Indicator", - "stor_t_in3": "Storage Tax 3 Indicator", - "stor_t_in4": "Storage Tax 4 Indicator", - "stor_t_in5": "Storage Tax 5 Indicator", - "tow_t_in1": "Tow Tax 1 Indicator", - "tow_t_in2": "Tow Tax 2 Indicator", - "tow_t_in3": "Tow Tax 3 Indicator", - "tow_t_in4": "Tow Tax 4 Indicator", - "tow_t_in5": "Tow Tax 5 Indicator" - }, - "claim_total": "Claim Total", - "class": "Class", - "clm_no": "Claim #", - "clm_total": "Claim Total", - "comment": "Comment", - "customerowing": "Customer Owing", - "date_estimated": "Date Estimated", - "date_exported": "Exported", - "date_invoiced": "Invoiced", - "date_last_contacted": "Last Contacted Date", - "date_lost_sale": "Lost Sale", - "date_next_contact": "Next Contact Date", - "date_open": "Open", - "date_rentalresp": "Shop Rental Responsibility Start", - "date_repairstarted": "Repairs Started", - "date_scheduled": "Scheduled", - "date_towin": "Towed In", - "date_void": "Void", - "ded_amt": "Deductible", - "ded_note": "Deductible Note", - "ded_status": "Deductible Status", - "depreciation_taxes": "Betterment/Depreciation/Taxes", - "dms": { - "address": "Customer Address", - "amount": "Amount", - "center": "Center", - "control_type": { - "account_number": "Account Number" - }, - "cost": "Cost", - "cost_dms_acctnumber": "Cost DMS Acct #", - "dms_make": "DMS Make", - "dms_model": "DMS Model", - "dms_model_override": "Override DMS Make/Model", - "dms_unsold": "New, Unsold Vehicle", - "dms_wip_acctnumber": "Cost WIP DMS Acct #", - "id": "DMS ID", - "inservicedate": "In Service Date", - "journal": "Journal #", - "lines": "Posting Lines", - "name1": "Customer Name", - "payer": { - "amount": "Amount", - "control_type": "Control Type", - "controlnumber": "Control Number", - "dms_acctnumber": "DMS Account #", - "name": "Payer Name" - }, - "sale": "Sale", - "sale_dms_acctnumber": "Sale DMS Acct #", - "story": "Story", - "vinowner": "VIN Owner" - }, - "dms_allocation": "DMS Allocation", - "driveable": "Driveable", - "employee_body": "Body", - "employee_csr": "Customer Service Rep.", - "employee_csr_writer": "Writer", - "employee_prep": "Prep", - "employee_refinish": "Refinish", - "est_addr1": "Estimator Address", - "est_co_nm": "Estimator Company", - "est_ct_fn": "Estimator First Name", - "est_ct_ln": "Estimator Last Name", - "est_ea": "Estimator Email", - "est_ph1": "Estimator Phone #", - "federal_tax_payable": "Federal Tax Payable", - "federal_tax_rate": "Federal Tax Rate", - "ins_addr1": "Insurance Co. Address", - "ins_city": "Insurance Co. City", - "ins_co_id": "Insurance Co. ID", - "ins_co_nm": "Insurance Company Name", - "ins_co_nm_short": "Ins. Co.", - "ins_ct_fn": "Adjuster First Name", - "ins_ct_ln": "Adjuster Last Name", - "ins_ea": "Adjuster Email", - "ins_ph1": "Adjuster Phone #", - "intake": { - "label": "Label", - "max": "Maximum", - "min": "Minimum", - "name": "Name", - "required": "Required?", - "type": "Type" - }, - "invoice_final_note": "Note to Display on Final Invoice", - "kmin": "Mileage In", - "kmout": "Mileage Out", - "la1": "LA1", - "la2": "LA2", - "la3": "LA3", - "la4": "LA4", - "laa": "Aluminum ", - "lab": "Body", - "labor_rate_desc": "Labor Rate Name", - "lad": "Diagnostic", - "lae": "Electrical", - "laf": "Frame", - "lag": "Glass", - "lam": "Mechanical", - "lar": "Refinish", - "las": "Structural", - "lau": "User Defined", - "local_tax_rate": "Local Tax Rate", - "loss_date": "Loss Date", - "loss_desc": "Loss Description", - "loss_of_use": "Loss of Use", - "lost_sale_reason": "Lost Sale Reason", - "ma2s": "2 Stage Paint", - "ma3s": "3 Stage Pain", - "mabl": "MABL?", - "macs": "MACS?", - "mahw": "Hazardous Waste", - "mapa": "Paint Materials", - "mash": "Shop Materials", - "matd": "Tire Disposal", - "materials": { - "MAPA": "Paint Materials", - "MASH": "Shop Materials", - "cal_maxdlr": "Threshhold", - "cal_opcode": "OP Codes", - "mat_adjp": "Material Adjustment", - "mat_taxp": "Material Tax Rate", - "mat_tx_in1": "Tax 1 Indicator", - "mat_tx_in2": "Tax 2 Indicator", - "mat_tx_in3": "Tax 3 Indicator", - "mat_tx_in4": "Tax 4 Indicator", - "mat_tx_in5": "Tax 5 Indicator", - "materials": "Profile - Materials", - "tax_ind": "Tax Indicator" - }, - "other_amount_payable": "Other Amount Payable", - "owner": "Owner", - "owner_owing": "Cust. Owes", - "ownr_ea": "Email", - "ownr_ph1": "Phone 1", - "ownr_ph2": "Phone 2", - "paa": "Aftermarket", - "pac": "Rechromed", - "pae": "Existing", - "pag": "Glass", - "pal": "LKQ", - "pam": "Remanufactured", - "pan": "OEM/New", - "pao": "Other", - "pap": "OEM Partial", - "par": "Re-cored", - "parts_tax_rates": { - "prt_discp": "Discount %", - "prt_mktyp": "Markup Type", - "prt_mkupp": "Markup %", - "prt_tax_in": "Tax Indicator", - "prt_tax_rt": "Part Tax Rate", - "prt_tx_in1": "Tax 1 Indicator", - "prt_tx_in2": "Tax 2 Indicator", - "prt_tx_in3": "Tax 3 Indicator", - "prt_tx_in4": "Tax 4 Indicator", - "prt_tx_in5": "Tax 5 Indicator", - "prt_tx_ty1": "Parts Tax Type 1", - "prt_type": "Part Type" - }, - "partsstatus": "Parts Status", - "pas": "Sublet", - "pay_date": "Pay Date", - "phoneshort": "PH", - "po_number": "PO Number", - "policy_no": "Policy #", - "ponumber": "PO Number", - "production_vars": { - "note": "Production Note" - }, - "qb_multiple_payers": { - "amount": "Amount", - "name": "Name" - }, - "queued_for_parts": "Queued for Parts", - "rate_ats": "ATS Rate", - "rate_la1": "LA1", - "rate_la2": "LA2", - "rate_la3": "LA3", - "rate_la4": "LA4", - "rate_laa": "Aluminum", - "rate_lab": "Body", - "rate_lad": "Diagnostic", - "rate_lae": "Electrical", - "rate_laf": "Frame", - "rate_lag": "Glass", - "rate_lam": "Mechanical", - "rate_lar": "Refinish", - "rate_las": "Structural", - "rate_lau": "User Defined", - "rate_ma2s": "2 Stage Paint", - "rate_ma3s": "3 Stage Paint", - "rate_mabl": "MABL??", - "rate_macs": "MACS??", - "rate_mahw": "Hazardous Waste", - "rate_mapa": "Paint Materials", - "rate_mash": "Shop Material", - "rate_matd": "Tire Disposal", - "referral_source_extra": "Other Referral Source", - "referral_source_other": "", - "referralsource": "Referral Source", - "regie_number": "Registration #", - "repairtotal": "Repair Total", - "ro_number": "RO #", - "scheduled_completion": "Scheduled Completion", - "scheduled_delivery": "Scheduled Delivery", - "scheduled_in": "Scheduled In", - "selling_dealer": "Selling Dealer", - "selling_dealer_contact": "Selling Dealer Contact", - "servicecar": "Service Car", - "servicing_dealer": "Servicing Dealer", - "servicing_dealer_contact": "Servicing Dealer Contact", - "special_coverage_policy": "Special Coverage Policy", - "specialcoveragepolicy": "Special Coverage Policy", - "state_tax_rate": "State Tax Rate", - "status": "Job Status", - "storage_payable": "Storage", - "tax_lbr_rt": "Labor Tax Rate", - "tax_levies_rt": "Levies Tax Rate", - "tax_paint_mat_rt": "Paint Material Tax Rate", - "tax_registration_number": "Tax Registration Number", - "tax_shop_mat_rt": "Shop Material Tax Rate", - "tax_str_rt": "Storage Tax Rate", - "tax_sub_rt": "Sublet Tax Rate", - "tax_tow_rt": "Towing Tax Rate", - "towin": "Tow In", - "towing_payable": "Towing Payable", - "unitnumber": "Unit #", - "updated_at": "Updated At", - "uploaded_by": "Uploaded By", - "vehicle": "Vehicle" - }, - "forms": { - "admindates": "Administrative Dates", - "appraiserinfo": "Estimator Info", - "claiminfo": "Claim Information", - "estdates": "Estimate Dates", - "laborrates": "Labor Rates", - "lossinfo": "Loss Information", - "other": "Other", - "repairdates": "Repair Dates", - "scheddates": "Schedule Dates" - }, - "labels": { - "accountsreceivable": "Accounts Receivable", - "act_price_ppc": "New Part Price", - "actual_completion_inferred": "$t(jobs.fields.actual_completion) inferred using $t(jobs.fields.scheduled_completion).", - "actual_delivery_inferred": "$t(jobs.fields.actual_delivery) inferred using $t(jobs.fields.scheduled_delivery).", - "actual_in_inferred": "$t(jobs.fields.actual_in) inferred using $t(jobs.fields.scheduled_in).", - "additionalpayeroverallocation": "You have allocated more than the sale of the Job to additional payers.", - "additionaltotal": "Additional Total", - "adjustmentrate": "Adjustment Rate", - "adjustments": "Adjustments", - "adminwarning": "Use the functionality on this page at your own risk. You are responsible for any and all changes to your data.", - "allocations": "Allocations", - "alreadyaddedtoscoreboard": "Job has already been added to scoreboard. Saving will update the previous entry.", - "alreadyclosed": "This Job has already been closed.", - "appointmentconfirmation": "Send confirmation to customer?", - "associationwarning": "Any changes to associations will require updating the data from the new parent record to the Job.", - "audit": "Audit Trail", - "available": "Available", - "availablejobs": "Available Jobs", - "ca_bc_pvrt": { - "days": "Days", - "rate": "PVRT Rate" - }, - "ca_gst_all_if_null": "If the Job is marked as a \"GST Registrant\" and this value is set to $0, the customer will be responsible for paying all of the GST by default. ", - "calc_repair_days": "Calculated Repair Days", - "calc_repair_days_tt": "This is the approximate number of days required to complete the repair according to the target touch time in your shop configuration (current set to {{target_touchtime}}).", - "calc_scheuled_completion": "Calculate Scheduled Completion", - "cards": { - "customer": "Customer Information", - "damage": "Area of Damage", - "dates": "Dates", - "documents": "Recent Documents", - "estimator": "Estimator", - "filehandler": "Adjuster", - "insurance": "Insurance Details", - "more": "More", - "notes": "Notes", - "parts": "Parts", - "totals": "Totals", - "vehicle": "Vehicle" - }, - "changeclass": "Changing the Job's class can have fundamental impacts to already exported accounting items. Are you sure you want to do this?", - "checklistcompletedby": "Checklist completed by {{by}} at {{at}}", - "checklistdocuments": "Checklist Documents", - "checklists": "Checklists", - "cieca_pfl": "Profile - Labor", - "cieca_pfo": "Profile - Other", - "cieca_pft": "Profile - Taxes", - "closeconfirm": "Are you sure you want to close this job? This cannot be easily undone.", - "closejob": "Close Job {{ro_number}}", - "closingperiod": "This Invoice Date is outside of the Closing Period.", - "contracts": "CC Contracts", - "convertedtolabor": "Labor Line Adjustments", - "cost": "Cost", - "cost_Additional": "Cost - Additional", - "cost_labor": "Cost - Labor", - "cost_parts": "Cost - Parts", - "cost_sublet": "Cost - Sublet", - "costs": "Costs", - "create": { - "jobinfo": "Job Info", - "newowner": "Create a new Owner instead. ", - "newvehicle": "Create a new Vehicle Instead", - "novehicle": "No vehicle (only for ROs to track parts/labor only work).", - "ownerinfo": "Owner Info", - "vehicleinfo": "Vehicle Info" - }, - "createiouwarning": "Are you sure you want to create an IOU for these lines? A new RO will be created based on those lines for this customer.", - "creating_new_job": "Creating new Job...", - "deductible": { - "stands": "Stands", - "waived": "Waived" - }, - "deleteconfirm": "Are you sure you want to delete this Job? This cannot be undone. ", - "deletedelivery": "Delete Delivery Checklist", - "deleteintake": "Delete Intake Checklist", - "deliverchecklist": "Deliver Checklist", - "difference": "Difference", - "diskscan": "Scan Disk for Estimates", - "dms": { - "apexported": "AP export completed. See logs for details.", - "damageto": "Damage to $t(jobs.fields.area_of_damage_impact.{{area_of_damage}}).", - "defaultstory": "B/S RO: {{ro_number}}. Owner: {{ownr_nm}}. Insurance Co: {{ins_co_nm}}. Claim/PO #: {{clm_po}}", - "disablebillwip": "Cost and WIP for bills has been ignored per shop configuration.", - "invoicedatefuture": "Invoice date must be today or in the future for CDK posting.", - "kmoutnotgreaterthankmin": "Mileage out must be greater than mileage in.", - "logs": "Logs", - "notallocated": "Not Allocated", - "postingform": "Posting Form", - "totalallocated": "Total Amount Allocated" - }, - "documents": "Documents", - "documents-images": "Images", - "documents-other": "Other Documents", - "duplicateconfirm": "Are you sure you want to duplicate this Job? Some elements of this Job will not be duplicated.", - "emailaudit": "Email Audit Trail", - "employeeassignments": "Employee Assignments", - "estimatelines": "Estimate Lines", - "estimator": "Estimator", - "existing_jobs": "Existing Jobs", - "federal_tax_amt": "Federal Taxes", - "gpdollars": "$ G.P.", - "gppercent": "% G.P.", - "hrs_claimed": "Hours Flagged", - "hrs_total": "Hours Total", - "importnote": "The Job was initially imported.", - "inproduction": "In Production", - "intakechecklist": "Intake Checklist", - "iou": "IOU", - "job": "Job Details", - "jobcosting": "Job Costing", - "jobtotals": "Job Totals", - "labor_hrs": "B/P/T Hrs", - "labor_rates_subtotal": "Labor Rates Subtotal", - "laborallocations": "Labor Allocations", - "labortotals": "Labor Totals", - "lines": "Estimate Lines", - "local_tax_amt": "Local Taxes", - "mapa": "Paint Materials", - "markforreexport": "Mark for Re-export", - "mash": "Shop Materials", - "masterbypass": "Master Bypass Password", - "materials": { - "mapa": "" - }, - "missingprofileinfo": "This job has missing tax profile info. To ensure correct totals calculations, re-import the job.", - "multipayers": "Additional Payers", - "net_repairs": "Net Repairs", - "notes": "Notes", - "othertotal": "Other Totals", - "outstanding_ar": "A balance is outstanding on this RO. Payments can still be entered when the job is closed. ", - "outstanding_credit_memos": "Outstanding credit memos have not been entered against this job. Credit Memos may still be posted once the job is closed.", - "outstanding_ppd": "There are outstanding PPDs that may not have been synced back to the estimate.", - "outstanding_reconciliation_discrep": "At least one discrepancy is not $0. This may indicate that this job is not properly reconciled and should not be closed.", - "outstanding_sublets": "There are sublet lines on the job which have not been marked as completed. ", - "outstandinghours": "There are outstanding hours on the job that have not been paid or have been overpaid.", - "override_header": "Override estimate header on import?", - "ownerassociation": "Owner Association", - "parts": "Parts", - "parts_lines": "Parts Lines", - "parts_received": "Parts Rec.", - "parts_tax_rates": "Parts Tax rates", - "partsfilter": "Parts Only", - "partssubletstotal": "Parts & Sublets Total", - "partstotal": "Parts Total (ex. Taxes)", - "performance": "Performance", - "pimraryamountpayable": "Total Primary Payable", - "plitooltips": { - "billtotal": "The total amount of all bill lines that have been posted against this RO (not including credits, taxes, or labor adjustments).", - "calculatedcreditsnotreceived": "The calculated credits not received is derived by subtracting the amount of credit memos entered from the retail total of returns created. This does not take into account whether the credit was marked as received. You can find more information here.", - "creditmemos": "The total retail amount of all returns created. This amount does not reflect credit memos that have been posted.", - "creditsnotreceived": "This total reflects the total retail of parts returns lines that have not been explicitly marked as returned when posting a credit memo. You can learn more about this here here. ", - "discrep1": "If the discrepancy is not $0, you may have one of the following:

\n\n
    \n
  • Too many bills/bill lines that have been posted against this RO. Check to make sure every bill posted on this RO is correctly posted and assigned.
  • \n
  • You do not have the latest supplement imported, or, a supplement must be submitted and then imported.
  • \n
  • You have posted a bill line to labor.
  • \n
\n
\nThere may be additional issues not listed above that prevent this Job from reconciling.", - "discrep2": "If the discrepancy is not $0, you may have one of the following:

\n\n
    \n
  • Used an incorrect rate when deducting from labor.
  • \n
  • An outstanding imbalance higher in the reconciliation process.
  • \n
\n
\nThere may be additional issues not listed above that prevent this Job from reconciling.", - "discrep3": "If the discrepancy is not $0, you may have one of the following:

\n\n
    \n
  • A parts order return has not been created.
  • \n
  • An outstanding imbalance higher in the reconciliation process.
  • \n
\n
\nThere may be additional issues not listed above that prevent this Job from reconciling.", - "laboradj": "The sum of all bill lines that deducted from labor hours, rather than part prices.", - "partstotal": "This is the total of all parts and sublet amounts on the vehicle (some of these may require an in-house invoice).
\nItems such as shop and paint materials, labor online lines, etc. are not included in this total.", - "totalreturns": "The total retail amount of returns created for this job." - }, - "ppc": "This line contains a part price change.", - "ppdnotexported": "PPDs not Exported", - "profileadjustments": "Profile Disc./Mkup", - "profitbypassrequired": "Minimum gross profit requirements have not been met.", - "profits": "Job Profits", - "prt_dsmk_total": "Line Item Adjustment", - "rates": "Rates", - "rates_subtotal": "All Rates Subtotal", - "reconciliation": { - "billlinestotal": "Bill Lines Total", - "byassoc": "By Line Association", - "byprice": "By Price", - "clear": "Clear All", - "discrepancy": "Discrepancy", - "joblinestotal": "Job Lines Total", - "multipleactprices": "${{act_price}} is the price for multiple job lines.", - "multiplebilllines": "{{line_desc}} has 2 or more bill lines associated to it.", - "multiplebillsforactprice": "Found more than 1 bill matching ${{act_price}} retail price.", - "removedpartsstrikethrough": "Strike through lines represent parts that have been removed from the estimate. They are included for completeness of reconciliation." - }, - "reconciliationheader": "Parts & Sublet Reconciliation", - "relatedros": "Related ROs", - "remove_from_ar": "Remove from AR", - "returntotals": "Return Totals", - "ro_guard": { - "enforce_ar": "AR collection enforced.", - "enforce_bills": "Bill discrepancy enforced.", - "enforce_cm": "Credit memo entry enforced.", - "enforce_labor": "Labor allocations enforced.", - "enforce_ppd": "PPD sync enforced.", - "enforce_profit": "Profit marginsenforced.", - "enforce_sublet": "Sublet completion enforced.", - "enforce_validation": "Master Bypass Required: {{message}}", - "enforced": "This check has been enforced by your shop manager. Enter the master bypass password to close the Job." - }, - "roguard": "RO Guard", - "roguardwarnings": "RO Guard Warnings", - "rosaletotal": "RO Parts Total", - "sale_additional": "Sales - Additional", - "sale_labor": "Sales - Labor", - "sale_parts": "Sales - Parts", - "sale_sublet": "Sales - Sublet", - "sales": "Sales", - "savebeforeconversion": "You have unsaved changes on the Job. Please save them before converting it. ", - "scheduledinchange": "The scheduled in is based off the latest appointment. To change this date, please schedule or reschedule the Job. ", - "specialcoveragepolicy": "Special Coverage Policy Applies", - "state_tax_amt": "Provincial/State Taxes", - "subletsnotcompleted": "Outstanding Sublets", - "subletstotal": "Sublets Total", - "subtotal": "Subtotal", - "supplementnote": "The Job had a supplement imported.", - "suspended": "SUSPENDED", - "suspense": "Suspense", - "tasks": "Tasks", - "threshhold": "Max Threshold: ${{amount}}", - "total_cost": "Total Cost", - "total_cust_payable": "Total Customer Amount Payable", - "total_repairs": "Total Repairs", - "total_sales": "Total Sales", - "total_sales_tax": "Total Sales Tax", - "totals": "Totals", - "unvoidnote": "This Job was unvoided.", - "update_scheduled_completion": "Update Scheduled Completion?", - "vehicle_info": "Vehicle", - "vehicleassociation": "Vehicle Association", - "viewallocations": "View Allocations", - "voidjob": "Are you sure you want to void this Job? This cannot be easily undone. ", - "voidnote": "This Job was voided." - }, - "successes": { - "addedtoproduction": "Job added to production board.", - "all_deleted": "{{count}} Jobs deleted successfully.", - "closed": "Job closed successfully.", - "converted": "Job converted successfully.", - "created": "Job created successfully. Click to view.", - "creatednoclick": "Job created successfully. ", - "delete": "Job deleted successfully.", - "deleted": "Job deleted successfully.", - "duplicated": "Job duplicated successfully. ", - "exported": "Job(s) exported successfully. ", - "invoiced": "Job closed and invoiced successfully.", - "ioucreated": "IOU created successfully. Click to see.", - "partsqueue": "Job added to parts queue.", - "save": "Job saved successfully.", - "savetitle": "Record saved successfully.", - "supplemented": "Job supplemented successfully. ", - "updated": "Job(s) updated successfully.", - "voided": "Job voided successfully." - } - }, - "landing": { - "bigfeature": { - "subtitle": "Rome Online is built using world class technology by experts in the collision repair industry. This translates to software that is tailor made for the unique challenges faced by repair facilities with no compromises. ", - "title": "Bringing the latest technology to the automotive repair industry. " - }, - "footer": { - "company": { - "about": "About Us", - "contact": "Contact", - "disclaimers": "Disclaimers", - "name": "Company", - "privacypolicy": "Privacy Policy" - }, - "io": { - "help": "Help", - "name": "Rome Online", - "status": "System Status" - }, - "slogan": "Rome Technologies. is a technology leader in the collision repair industry. We specialize in creating collision repair management systems and bodyshop management systems that lower cycle times and promote efficiency." - }, - "hero": { - "button": "Learn More", - "title": "Shop management reimagined." - }, - "labels": { - "features": "Features", - "managemyshop": "Sign In", - "pricing": "Pricing" - }, - "pricing": { - "basic": { - "name": "Basic", - "sub": "Best suited for shops looking to increase their volume." - }, - "essentials": { - "name": "Essentials", - "sub": "Best suited for small and low volume shops." - }, - "pricingtitle": "Features", - "pro": { - "name": "Pro", - "sub": "Empower your shop with the tools to operate at peak capacity." - }, - "title": "Features", - "unlimited": { - "name": "Unlimited", - "sub": "Everything you need and more for the high volume shop." - } - } - }, - "menus": { - "currentuser": { - "languageselector": "Language", - "profile": "Profile" - }, - "header": { - "accounting": "Accounting", - "accounting-payables": "Payables", - "accounting-payments": "Payments", - "accounting-receivables": "Receivables", - "activejobs": "Active Jobs", - "all_tasks": "All Tasks", - "alljobs": "All Jobs", - "allpayments": "All Payments", - "availablejobs": "Available Jobs", - "bills": "Bills", - "courtesycars": "Courtesy Cars", - "courtesycars-all": "All Courtesy Cars", - "courtesycars-contracts": "Contracts", - "courtesycars-newcontract": "New Contract", - "create_task": "Create Task", - "customers": "Customers", - "dashboard": "Dashboard", - "enterbills": "Enter Bills", - "entercardpayment": "New Card Charge", - "enterpayment": "Enter Payments", - "entertimeticket": "Enter Time Tickets", - "export": "Export", - "export-logs": "Export Logs", - "help": "Help", - "home": "Home", - "inventory": "Inventory", - "jobs": "Jobs", - "my_tasks": "My Tasks", - "newjob": "Create New Job", - "owners": "Owners", - "parts-queue": "Parts Queue", - "phonebook": "Phonebook", - "productionboard": "Production Board - Visual", - "productionlist": "Production Board - List", - "readyjobs": "Ready Jobs", - "recent": "Recent Items", - "reportcenter": "Report Center", - "rescueme": "Rescue me!", - "schedule": "Schedule", - "scoreboard": "Scoreboard", - "search": { - "bills": "Bills", - "jobs": "Jobs", - "owners": "Owners", - "payments": "Payments", - "phonebook": "Phonebook", - "vehicles": "Vehicles" - }, - "shiftclock": "Shift Clock", - "shop": "My Shop", - "shop_config": "Configuration", - "shop_csi": "CSI", - "shop_templates": "Templates", - "shop_vendors": "Vendors", - "tasks": "Tasks", - "temporarydocs": "Temporary Documents", - "timetickets": "Time Tickets", - "ttapprovals": "Time Ticket Approvals", - "vehicles": "Vehicles" - }, - "jobsactions": { - "admin": "Admin", - "cancelallappointments": "Cancel all appointments", - "closejob": "Close Job", - "deletejob": "Delete Job", - "duplicate": "Duplicate this Job", - "duplicatenolines": "Duplicate this Job without Repair Data", - "newcccontract": "Create Courtesy Car Contract", - "void": "Void Job" - }, - "jobsdetail": { - "claimdetail": "Claim Details", - "dates": "Dates", - "financials": "Financial Information", - "general": "General", - "insurance": "Insurance Information", - "labor": "Labor", - "lifecycle": "Lifecycle", - "parts": "Parts", - "partssublet": "Parts & Bills", - "rates": "Rates", - "repairdata": "Repair Data", - "totals": "Totals" - }, - "profilesidebar": { - "profile": "My Profile", - "shops": "My Shops" - }, - "tech": { - "assignedjobs": "Assigned Jobs", - "claimtask": "Flag Hours", - "dispatchedparts": "Dispatched Parts", - "home": "Home", - "jobclockin": "Job Clock In", - "jobclockout": "Job Clock Out", - "joblookup": "Job Lookup", - "login": "Login", - "logout": "Logout", - "productionboard": "Production Visual", - "productionlist": "Production List", - "shiftclockin": "Shift Clock" - } - }, - "messaging": { - "actions": { - "link": "Link to Job", - "new": "New Conversation" - }, - "errors": { - "invalidphone": "The phone number is invalid. Unable to open conversation. ", - "noattachedjobs": "No Jobs have been associated to this conversation. ", - "updatinglabel": "Error updating label. {{error}}" - }, - "labels": { - "addlabel": "Add a label to this conversation.", - "archive": "Archive", - "maxtenimages": "You can only select up to a maximum of 10 images at a time.", - "messaging": "Messaging", - "noallowtxt": "This customer has not indicated their permission to be messaged.", - "nojobs": "Not associated to any Job.", - "nopush": "Polling Mode Enabled", - "phonenumber": "Phone #", - "presets": "Presets", - "recentonly": "Only your most recent 50 conversations will be shown here. If you are looking for an older conversation, find the related contact and click their phone number to view the conversation.", - "selectmedia": "Select Media", - "sentby": "Sent by {{by}} at {{time}}", - "typeamessage": "Send a message...", - "unarchive": "Unarchive" - }, - "render": { - "conversation_list": "Conversation List" - } - }, - "notes": { - "actions": { - "actions": "Actions", - "deletenote": "Delete Note", - "edit": "Edit Note", - "new": "New Note", - "savetojobnotes": "Save to Job Notes" - }, - "errors": { - "inserting": "Error inserting note. {{error}}" - }, - "fields": { - "createdby": "Created By", - "critical": "Critical", - "private": "Private", - "text": "Contents", - "type": "Type", - "types": { - "customer": "Customer", - "general": "General", - "office": "Office", - "paint": "Paint", - "parts": "Parts", - "shop": "Shop", - "supplement": "Supplement" - }, - "updatedat": "Updated At" - }, - "labels": { - "addtorelatedro": "Add to Related ROs", - "newnoteplaceholder": "Add a note...", - "notetoadd": "Note to Add", - "systemnotes": "System Notes", - "usernotes": "User Notes" - }, - "successes": { - "create": "Note created successfully.", - "deleted": "Note deleted successfully.", - "updated": "Note updated successfully." - } - }, - "owner": { - "labels": { - "noownerinfo": "No owner information." - } - }, - "owners": { - "actions": { - "update": "Update Selected Records" - }, - "errors": { - "deleting": "Error deleting owner. {{error}}.", - "noaccess": "The record does not exist or you do not have access to it. ", - "saving": "Error saving owner. {{error}}.", - "selectexistingornew": "Select an existing owner record or create a new one. " - }, - "fields": { - "address": "Address", - "allow_text_message": "Permission to Text?", - "name": "Name", - "note": "Owner Note", - "ownr_addr1": "Address", - "ownr_addr2": "Address 2", - "ownr_city": "City", - "ownr_co_nm": "Owner Co. Name", - "ownr_ctry": "Country", - "ownr_ea": "Email", - "ownr_fn": "First Name", - "ownr_ln": "Last Name", - "ownr_ph1": "Phone 1", - "ownr_ph2": "Phone 2", - "ownr_st": "Province/State", - "ownr_title": "Title", - "ownr_zip": "Zip/Postal Code", - "preferred_contact": "Preferred Contact Method", - "tax_number": "Tax Number" - }, - "forms": { - "address": "Address", - "contact": "Contact Information", - "name": "Owner Details" - }, - "labels": { - "create_new": "Create a new owner record.", - "deleteconfirm": "Are you sure you want to delete this owner? This cannot be undone.", - "existing_owners": "Existing Owners", - "fromclaim": "Current Claim", - "fromowner": "Historical Owner Record", - "relatedjobs": "Related Jobs", - "updateowner": "Update Owner" - }, - "successes": { - "delete": "Owner deleted successfully.", - "save": "Owner saved successfully." - } - }, - "parts": { - "actions": { - "order": "Order Parts", - "orderinhouse": "Order as In House" - } - }, - "parts_dispatch": { - "actions": { - "accept": "Accept" - }, - "errors": { - "accepting": "Error accepting parts dispatch. {{error}}", - "creating": "Error dispatching parts. {{error}}" - }, - "fields": { - "number": "Number", - "percent_accepted": "% Accepted" - }, - "labels": { - "notyetdispatched": "This part has not been dispatched.", - "parts_dispatch": "Parts Dispatch" - } - }, - "parts_dispatch_lines": { - "fields": { - "accepted_at": "Accepted At" - } - }, - "parts_orders": { - "actions": { - "backordered": "Mark Backordered", - "receive": "Receive", - "receivebill": "Receive Bill" - }, - "errors": { - "associatedbills": "This parts order cannot", - "backordering": "Error backordering part {{message}}.", - "creating": "Error encountered when creating parts order. ", - "oec": "Error creating EMS files for parts order. {{error}}", - "saving": "Error saving parts order. {{error}}.", - "updating": "Error updating parts order/parts order line. {{error}}." - }, - "fields": { - "act_price": "Price", - "backordered_eta": "B.O. ETA", - "backordered_on": "B.O. On", - "cm_received": "CM Received?", - "comments": "Comments", - "cost": "Cost", - "db_price": "List Price", - "deliver_by": "Deliver By", - "job_line_id": "Job Line Id", - "line_desc": "Line Description", - "line_remarks": "Remarks", - "lineremarks": "Line Remarks", - "oem_partno": "Part #", - "order_date": "Order Date", - "order_number": "Order Number", - "orderedby": "Ordered By", - "part_type": "Type", - "quantity": "Qty.", - "return": "Return", - "status": "Status" - }, - "labels": { - "allpartsto": "All Parts Location", - "confirmdelete": "Are you sure you want to delete this item? It cannot be recovered. Job line statuses will not be updated and may require manual review. ", - "custompercent": "Custom %", - "discount": "Discount {{percent}}", - "email": "Send by Email", - "inthisorder": "Parts in this Order", - "is_quote": "Parts Quote?", - "mark_as_received": "Mark as Received?", - "newpartsorder": "New Parts Order", - "notyetordered": "This part has not yet been ordered.", - "oec": "Order via EMS", - "order_type": "Order Type", - "orderhistory": "Order History", - "parts_order": "Parts Order", - "parts_orders": "Parts Orders", - "print": "Show Printed Form", - "receive": "Receive Parts Order", - "removefrompartsqueue": "Unqueue from Parts Queue?", - "returnpartsorder": "Return Parts Order", - "sublet_order": "Sublet Order" - }, - "successes": { - "created": "Parts order created successfully. ", - "line_updated": "Parts return line updated.", - "received": "Parts order received.", - "return_created": "Parts return created successfully." - } - }, - "payments": { - "actions": { - "generatepaymentlink": "Generate Payment Link" - }, - "errors": { - "exporting": "Error exporting payment(s). {{error}}", - "exporting-partner": "Error exporting to partner. Please check the partner interaction log for more errors.", - "inserting": "Error inserting payment. {{error}}" - }, - "fields": { - "amount": "Amount", - "created_at": "Created At", - "date": "Payment Date", - "exportedat": "Exported At", - "memo": "Memo", - "payer": "Payer", - "paymentnum": "Payment Number", - "stripeid": "Stripe ID", - "transactionid": "Transaction ID", - "type": "Type" - }, - "labels": { - "balance": "Balance", - "ca_bc_etf_table": "ICBC EFT Table Converter", - "customer": "Customer", - "edit": "Edit Payment", - "electronicpayment": "Use Electronic Payment Processing?", - "external": "External", - "findermodal": "ICBC Payment Finder", - "insurance": "Insurance", - "markexported": "Mark Exported", - "markforreexport": "Mark for Re-export", - "new": "New Payment", - "signup": "Please contact support to sign up for electronic payments.", - "smspaymentreminder": "This is {{shopname}} reminding you about your balance of {{amount}}. To pay, click the following link {{payment_link}}.", - "title": "Payments", - "totalpayments": "Total Payments" - }, - "successes": { - "exported": "Payment(s) exported successfully.", - "markexported": "Payment(s) marked exported.", - "markreexported": "Payment marked for re-export successfully", - "payment": "Payment created successfully. ", - "paymentupdate": "Payment updated successfully. ", - "stripe": "Credit card transaction charged successfully." - } - }, - "phonebook": { - "actions": { - "new": "New Phonebook Entry" - }, - "errors": { - "adding": "Error adding phonebook entry. {{error}}", - "saving": "Error saving phonebook entry. {{error}}" - }, - "fields": { - "address1": "Street 1", - "address2": "Street 2", - "category": "Category", - "city": "City", - "company": "Company", - "country": "Country", - "email": "Email", - "fax": "Fax", - "firstname": "First Name", - "lastname": "Last Name", - "phone1": "Phone 1", - "phone2": "Phone 2", - "state": "Province/State" - }, - "labels": { - "noneselected": "No phone book entry selected. ", - "onenamerequired": "At least one name related field is required.", - "vendorcategory": "Vendor" - }, - "successes": { - "added": "Phonebook entry added successfully. ", - "deleted": "Phonebook entry deleted successfully. ", - "saved": "Phonebook entry saved successfully. " - } - }, - "printcenter": { - "appointments": { - "appointment_confirmation": "Appointment Confirmation" - }, - "bills": { - "inhouse_invoice": "In House Invoice" - }, - "courtesycarcontract": { - "courtesy_car_contract": "Courtesy Car Contract", - "courtesy_car_impound": "Impound Charges", - "courtesy_car_inventory": "Courtesy Car Inventory", - "courtesy_car_terms": "Courtesy Car Terms" - }, - "errors": { - "nocontexttype": "No context type set." - }, - "jobs": { - "3rdpartyfields": { - "addr1": "Address 1", - "addr2": "Address 2", - "addr3": "Address 3", - "attn": "Attention", - "city": "City", - "custgst": "Customer Portion of GST", - "ded_amt": "Deductible", - "depreciation": "Depreciation", - "other": "Other", - "ponumber": "PO Number", - "refnumber": "Reference Number", - "sendtype": "Send by", - "state": "Province/State", - "zip": "Postal Code/Zip" - }, - "3rdpartypayer": "Invoice to Third Party Payer", - "ab_proof_of_loss": "AB - Proof of Loss", - "appointment_confirmation": "Appointment Confirmation", - "appointment_reminder": "Appointment Reminder", - "casl_authorization": "CASL Authorization", - "committed_timetickets_ro": "Committed Time Tickets", - "coversheet_landscape": "Coversheet (Landscape)", - "coversheet_portrait": "Coversheet Portrait", - "csi_invitation": "CSI Invitation", - "csi_invitation_action": "CSI Invite", - "diagnostic_authorization": "Diagnostic Authorization", - "dms_posting_sheet": "DMS Posting Sheet", - "envelope_return_address": "#10 Envelope Return Address Label", - "estimate": "Estimate Only", - "estimate_detail": "Estimate Details", - "estimate_followup": "Estimate Followup", - "express_repair_checklist": "Express Repair Checklist", - "filing_coversheet_landscape": "Filing Coversheet (Landscape)", - "filing_coversheet_portrait": "Filing Coversheet (Portrait)", - "final_invoice": "Final Invoice", - "fippa_authorization": "FIPPA Authorization", - "folder_label_multiple": "Folder Label - Multi", - "glass_express_checklist": "Glass Express Checklist", - "guarantee": "Repair Guarantee", - "individual_job_note": "RO Job Note", - "invoice_customer_payable": "Invoice (Customer Payable)", - "invoice_total_payable": "Invoice (Total Payable)", - "iou_form": "IOU Form", - "job_costing_ro": "Job Costing", - "job_lifecycle_ro": "Job Lifecycle", - "job_notes": "Job Notes", - "job_tasks": "Job Tasks", - "key_tag": "Key Tag", - "labels": { - "count": "Count", - "labels": "Labels", - "position": "Starting Position" - }, - "lag_time_ro": "Lag Time", - "mechanical_authorization": "Mechanical Authorization", - "mpi_animal_checklist": "MPI - Animal Checklist", - "mpi_eglass_auth": "MPI - eGlass Auth", - "mpi_final_acct_sheet": "MPI - Final Accounting Sheet (Direct Repair)", - "mpi_final_repair_acct_sheet": "MPI - Final Accounting Sheet", - "paint_grid": "Paint Grid", - "parts_dispatch": "Parts Dispatch", - "parts_invoice_label_single": "Parts Label Single", - "parts_label_multiple": "Parts Label - Multi", - "parts_label_single": "Parts Label - Single", - "parts_list": "Parts List", - "parts_order": "Parts Order Confirmation", - "parts_order_confirmation": "", - "parts_order_history": "Parts Order History", - "parts_return_slip": "Parts Return Slip", - "payment_receipt": "Payment Receipt", - "payment_request": "Payment Request", - "payments_by_job": "Job Payments", - "purchases_by_ro_detail": "Purchases - Detail", - "purchases_by_ro_summary": "Purchases - Summary", - "qc_sheet": "Quality Control Sheet", - "rental_reservation": "Rental Reservation", - "ro_totals": "RO Totals", - "ro_with_description": "RO Summary with Descriptions", - "sgi_certificate_of_repairs": "SGI - Certificate of Repairs", - "sgi_windshield_auth": "SGI - Windshield Authorization", - "stolen_recovery_checklist": "Stolen Recovery Checklist", - "sublet_order": "Sublet Order", - "supplement_request": "Supplement Request", - "thank_you_ro": "Thank You Letter", - "thirdpartypayer": "Third Party Payer", - "timetickets_ro": "Time Tickets", - "vehicle_check_in": "Vehicle Intake", - "vehicle_delivery_check": "Vehicle Delivery Checklist", - "window_tag": "Window Tag", - "window_tag_sublet": "Window Tag - Sublet", - "work_authorization": "Work Authorization", - "worksheet_by_line_number": "Worksheet by Line Number", - "worksheet_sorted_by_operation": "Worksheet by Operation", - "worksheet_sorted_by_operation_no_hours": "Worksheet by Operation (No Hours)", - "worksheet_sorted_by_operation_part_type": "Worksheet by Operation & Part Type", - "worksheet_sorted_by_operation_type": "Worksheet by Operation Type", - "worksheet_sorted_by_team": "Worksheet by Team" - }, - "labels": { - "groups": { - "authorization": "Authorization", - "financial": "Financial", - "post": "Post-Production", - "pre": "Pre-Production", - "ro": "Repair Order", - "worksheet": "Worksheets" - }, - "misc": "Miscellaneous Documents", - "repairorder": "Repair Order Related", - "reportcentermodal": "Report Center", - "speedprint": "Speed Print", - "title": "Print Center" - }, - "payments": { - "ca_bc_etf_table": "ICBC EFT Table", - "exported_payroll": "Payroll Table" - }, - "special": { - "attendance_detail_csv": "Attendance Table" - }, - "subjects": { - "jobs": { - "individual_job_note": "Job Note RO: {{ro_number}}", - "parts_dispatch": "Parts Dispatch RO: {{ro_number}}", - "parts_order": "Parts Order PO: {{ro_number}} - {{name}}", - "parts_return_slip": "Parts Return PO: {{ro_number}} - {{name}}", - "sublet_order": "Sublet Order PO: {{ro_number}} - {{name}}" - } - }, - "vendors": { - "purchases_by_vendor_detailed": "Purchases by Vendor - Detailed", - "purchases_by_vendor_summary": "Purchases by Vendor - Summary" - } - }, - "production": { - "actions": { - "addcolumns": "Add Columns", - "bodypriority-clear": "Clear Body Priority", - "bodypriority-set": "Set Body Priority", - "detailpriority-clear": "Clear Detail Priority", - "detailpriority-set": "Set Detail Priority", - "paintpriority-clear": "Clear Paint Priority", - "paintpriority-set": "Set Paint Priority", - "remove": "Remove from Production", - "removecolumn": "Remove Column", - "saveconfig": "Save Configuration", - "suspend": "Suspend", - "unsuspend": "Unsuspend" - }, - "constants": { - "main_profile": "Default" - }, - "errors": { - "boardupdate": "Error encountered updating Job. {{message}}", - "name_exists": "A Profile with this name already exists. Please choose a different name.", - "name_required": "Profile name is required.", - "removing": "Error removing from production board. {{error}}", - "settings": "Error saving board settings: {{error}}" - }, - "labels": { - "actual_in": "Actual In", - "addnewprofile": "Add New Profile", - "alert": "Alert", - "alertoff": "Remove alert from Job", - "alerton": "Add alert to Job", - "alerts": "Alerts", - "ats": "Alternative Transportation", - "bodyhours": "B", - "bodypriority": "B/P", - "bodyshop": { - "labels": { - "qbo_departmentid": "QBO Department ID", - "qbo_usa": "QBO USA" - } - }, - "card_size": "Card Size", - "cardcolor": "Colored Cards", - "cardsettings": "Card Settings", - "clm_no": "Claim Number", - "comment": "Comment", - "compact": "Compact Cards", - "detailpriority": "D/P", - "employeeassignments": "Employee Assignments", - "employeesearch": "Employee Search", - "estimator": "Estimator", - "horizontal": "Horizontal", - "ins_co_nm": "Insurance Company Name", - "jobdetail": "Job Details", - "kiosk_mode": "Kiosk Mode", - "laborhrs": "Labor Hours", - "legend": "Legend:", - "model_info": "Vehicle Info", - "note": "Production Note", - "off": "Off", - "on": "On", - "orientation": "Board Orientation", - "ownr_nm": "Customer Name", - "paintpriority": "P/P", - "partsstatus": "Parts Status", - "production_note": "Production Note", - "refinishhours": "R", - "scheduled_completion": "Scheduled Completion", - "selectview": "Select a View", - "stickyheader": "Sticky Header (BETA)", - "sublets": "Sublets", - "subtotal": "Subtotal", - "tall": "Tall", - "tasks": "Tasks", - "totalhours": "Total Hrs ", - "touchtime": "T/T", - "vertical": "Vertical", - "viewname": "View Name", - "wide": "Wide" - }, - "options": { - "horizontal": "Horizontal", - "large": "Large", - "medium": "Medium", - "small": "Small", - "vertical": "Vertical" - }, - "settings": { - "board_settings": "Board Settings", - "filters": { - "md_estimators": "Estimators", - "md_ins_cos": "Insurance Companies" - }, - "filters_title": "Filters", - "information": "Information", - "layout": "Layout", - "statistics": { - "jobs_in_production": "Jobs in Production", - "tasks_in_production": "Tasks in Production", - "tasks_on_board": "Tasks on Board", - "tasks_in_view": "Tasks in View", - "total_amount_in_production": "Dollars in Production", - "total_amount_on_board": "Dollars on Board", - "total_amount_in_view": "Dollars in View", - "total_hours_in_production": "Hours in Production", - "total_hours_on_board": "Hours on Board", - "total_hours_in_view": "Hours in View", - "total_jobs_on_board": "Jobs on Board", - "total_jobs_in_view": "Jobs in View", - "total_lab_in_production": "Body Hours in Production", - "total_lab_on_board": "Body Hours on Board", - "total_lab_in_view": "Body Hours in View", - "total_lar_in_production": "Refinish Hours in Production", - "total_lar_on_board": "Refinish Hours on Board", - "total_lar_in_view": "Refinish Hours in View" - }, - "statistics_title": "Statistics" - }, - "statistics": { - "currency_symbol": "$", - "hours": "Hours", - "jobs": "Jobs", - "jobs_in_production": "Jobs in Production", - "tasks": "Tasks", - "tasks_in_production": "Tasks in Production", - "tasks_on_board": "Tasks on Board", - "tasks_in_view": "Tasks in View", - "total_amount_in_production": "Dollars in Production", - "total_amount_on_board": "Dollars on Board", - "total_amount_in_view": "Dollars in View", - "total_hours_in_production": "Hours in Production", - "total_hours_on_board": "Hours on Board", - "total_hours_in_view": "Hours in View", - "total_jobs_on_board": "Jobs on Board", - "total_jobs_in_view": "Jobs in View", - "total_lab_in_production": "Body Hours in Production", - "total_lab_on_board": "Body Hours on Board", - "total_lab_in_view": "Body Hours in View", - "total_lar_in_production": "Refinish Hours in Production", - "total_lar_on_board": "Refinish Hours on Board", - "total_lar_in_view": "Refinish Hours in View" - }, - "successes": { - "removed": "Job removed from production." - } - }, - "profile": { - "errors": { - "state": "Error reading page state. Please refresh." - }, - "labels": { - "activeshop": "Active Shop" - }, - "successes": { - "updated": "Profile updated successfully." - } - }, - "reportcenter": { - "actions": { - "generate": "Generate" - }, - "labels": { - "advanced_filters": "Advanced Filters and Sorters", - "advanced_filters_false": "False", - "advanced_filters_filter_field": "Field", - "advanced_filters_filter_operator": "Operator", - "advanced_filters_filter_value": "Value", - "advanced_filters_filters": "Filters", - "advanced_filters_hide": "Hide", - "advanced_filters_show": "Show", - "advanced_filters_sorter_direction": "Direction", - "advanced_filters_sorter_field": "Field", - "advanced_filters_sorters": "Sorters", - "advanced_filters_true": "True", - "dates": "Dates", - "employee": "Employee", - "filterson": "Filters on {{object}}: {{field}}", - "generateasemail": "Generate as Email?", - "groups": { - "customers": "Customers", - "jobs": "Jobs & Costing", - "payroll": "Payroll", - "purchases": "Purchases", - "sales": "Sales" - }, - "key": "Report", - "objects": { - "appointments": "Appointments", - "bills": "Bills", - "csi": "CSI", - "exportlogs": "Export Logs", - "jobs": "Jobs", - "parts_orders": "Parts Orders", - "payments": "Payments", - "scoreboard": "Scoreboard", - "tasks": "Tasks", - "timetickets": "Timetickets" - }, - "vendor": "Vendor" - }, - "templates": { - "adp_payroll_flat": "ADP Payroll - Flat Rate", - "adp_payroll_straight": "ADP Payroll - Straight Time", - "anticipated_revenue": "Anticipated Revenue", - "ar_aging": "AR Aging", - "attendance_detail": "Attendance (All Employees)", - "attendance_employee": "Employee Attendance", - "attendance_summary": "Attendance Summary (All Employees)", - "committed_timetickets": "Committed Time Tickets", - "committed_timetickets_employee": "Committed Employee Time Tickets", - "committed_timetickets_summary": "Committed Time Tickets Summary", - "credits_not_received_date": "Credits not Received by Date", - "credits_not_received_date_vendorid": "Credits not Received by Vendor", - "csi": "CSI Responses", - "customer_list": "Customer List", - "cycle_time_analysis": "Cycle Time Analysis", - "estimates_written_converted": "Estimates Written/Converted", - "estimator_detail": "Jobs by Estimator (Detail)", - "estimator_summary": "Jobs by Estimator (Summary)", - "export_payables": "Export Log - Payables", - "export_payments": "Export Log - Payments", - "export_receivables": "Export Log - Receivables", - "exported_gsr_by_ro": "Exported Gross Sales - Excel", - "exported_gsr_by_ro_labor": "Exported Gross Sales (Labor) - Excel", - "gsr_by_atp": "", - "gsr_by_ats": "Gross Sales by ATS", - "gsr_by_category": "Gross Sales by Category", - "gsr_by_csr": "Gross Sales by CSR", - "gsr_by_delivery_date": "Gross Sales by Delivery Date", - "gsr_by_estimator": "Gross Sales by Estimator", - "gsr_by_exported_date": "Exported Gross Sales", - "gsr_by_ins_co": "Gross Sales by Insurance Company", - "gsr_by_make": "Gross Sales by Vehicle Make", - "gsr_by_referral": "Gross Sales by Referral Source", - "gsr_by_ro": "Gross Sales by RO", - "gsr_labor_only": "Gross Sales - Labor Only", - "hours_sold_detail_closed": "Hours Sold Detail - Closed", - "hours_sold_detail_closed_csr": "Hours Sold Detail - Closed by CSR", - "hours_sold_detail_closed_estimator": "Hours Sold Detail - Closed by Estimator", - "hours_sold_detail_closed_ins_co": "Hours Sold Detail - Closed by Source", - "hours_sold_detail_closed_status": "Hours Sold Detail - Closed by Status", - "hours_sold_detail_open": "Hours Sold Detail - Open", - "hours_sold_detail_open_csr": "Hours Sold Detail - Open by CSR", - "hours_sold_detail_open_estimator": "Hours Sold Detail - Open by Estimator", - "hours_sold_detail_open_ins_co": "Hours Sold Detail - Open by Source", - "hours_sold_detail_open_status": "Hours Sold Detail - Open by Status", - "hours_sold_summary_closed": "Hours Sold Summary - Closed", - "hours_sold_summary_closed_csr": "Hours Sold Summary - Closed by CSR", - "hours_sold_summary_closed_estimator": "Hours Sold Summary - Closed by Estimator", - "hours_sold_summary_closed_ins_co": "Hours Sold Summary - Closed by Source", - "hours_sold_summary_closed_status": "Hours Sold Summary - Closed by Status", - "hours_sold_summary_open": "Hours Sold Summary - Open", - "hours_sold_summary_open_csr": "Hours Sold Summary - Open CSR", - "hours_sold_summary_open_estimator": "Hours Sold Summary - Open Estimator", - "hours_sold_summary_open_ins_co": "Hours Sold Summary - Open by Source", - "hours_sold_summary_open_status": "Hours Sold Summary - Open by Status", - "job_costing_ro_csr": "Job Costing by CSR", - "job_costing_ro_date_detail": "Job Costing by RO - Detail", - "job_costing_ro_date_summary": "Job Costing by RO - Summary", - "job_costing_ro_estimator": "Job Costing by Estimator", - "job_costing_ro_ins_co": "Job Costing by RO Source", - "job_lifecycle_date_detail": "Job Lifecycle by Date - Detail", - "job_lifecycle_date_summary": "Job Lifecycle by Date - Summary", - "jobs_completed_not_invoiced": "Jobs Completed not Invoiced", - "jobs_invoiced_not_exported": "Jobs Invoiced not Exported", - "jobs_reconcile": "Parts/Sublet/Labor Reconciliation", - "jobs_scheduled_completion": "Jobs Scheduled Completion", - "lag_time": "Lag Time", - "load_level": "Load Level", - "lost_sales": "Lost Sales", - "open_orders": "Open Orders by Date", - "open_orders_csr": "Open Orders by CSR", - "open_orders_estimator": "Open Orders by Estimator", - "open_orders_excel": "Open Orders - Excel", - "open_orders_ins_co": "Open Orders by Insurance Company", - "open_orders_referral": "Open Orders by Referral Source", - "open_orders_specific_csr": "Open Orders filtered by CSR", - "open_orders_status": "Open Orders by Status", - "parts_backorder": "IOU Parts List", - "parts_not_recieved": "Parts Not Received", - "parts_not_recieved_vendor": "Parts Not Received by Vendor", - "parts_received_not_scheduled": "Parts Received for Jobs Not Scheduled", - "payments_by_date": "Payments by Date", - "payments_by_date_payment": "Payments by Date and Payment Type", - "payments_by_date_type": "Payments by Date and Customer Type", - "production_by_category": "Production by Category", - "production_by_category_one": "Production filtered by Category", - "production_by_csr": "Production by CSR", - "production_by_last_name": "Production by Last Name", - "production_by_repair_status": "Production by Status", - "production_by_repair_status_one": "Production filtered by Status", - "production_by_ro": "Production by RO", - "production_by_target_date": "Production by Target Date", - "production_by_technician": "Production by Technician", - "production_by_technician_one": "Production filtered by Technician", - "production_not_production_status": "Production not in Production Status", - "production_over_time": "Production Level over Time", - "psr_by_make": "Percent of Sales by Vehicle Make", - "purchase_return_ratio_grouped_by_vendor_detail": "Purchase & Return Ratio by Vendor (Detail)", - "purchase_return_ratio_grouped_by_vendor_summary": "Purchase & Return Ratio by Vendor (Summary)", - "purchases_by_cost_center_detail": "Purchases by Cost Center (Detail)", - "purchases_by_cost_center_summary": "Purchases by Cost Center (Summary)", - "purchases_by_date_range_detail": "Purchases by Date - Detail", - "purchases_by_date_range_summary": "Purchases by Date - Summary", - "purchases_by_ro_detail_date": "Purchases by RO - Detail", - "purchases_by_ro_summary_date": "Purchases by RO - Summary", - "purchases_by_vendor_detailed_date_range": "Purchases By Vendor - Detailed", - "purchases_by_vendor_summary_date_range": "Purchases by Vendor - Summary", - "purchases_grouped_by_vendor_detailed": "Purchases Grouped by Vendor - Detailed", - "purchases_grouped_by_vendor_summary": "Purchases Grouped by Vendor - Summary", - "returns_grouped_by_vendor_detailed": "Returns Grouped by Vendor - Detailed", - "returns_grouped_by_vendor_summary": "Returns Grouped by Vendor - Summary", - "schedule": "Appointment Schedule", - "scheduled_parts_list": "Parts for Jobs Scheduled In", - "scoreboard_detail": "Scoreboard Detail", - "scoreboard_summary": "Scoreboard Summary", - "supplement_ratio_ins_co": "Supplement Ratio by Source", - "tasks_date": "Tasks by Date", - "tasks_date_employee": "Employee Tasks by Date", - "thank_you_date": "Thank You Letters", - "timetickets": "Time Tickets", - "timetickets_employee": "Employee Time Tickets", - "timetickets_summary": "Time Tickets Summary", - "unclaimed_hrs": "Unflagged Hours", - "void_ros": "Void ROs", - "work_in_progress_committed_labour": "Work in Progress - Committed Labor", - "work_in_progress_jobs": "Work in Progress - Jobs", - "work_in_progress_labour": "Work in Progress - Labor", - "work_in_progress_payables": "Work in Progress - Payables" - } - }, - "schedule": { - "labels": { - "atssummary": "ATS Summary", - "employeevacation": "Employee Vacations", - "estimators": "Filter by Writer/Customer Rep.", - "ins_co_nm_filter": "Filter by Insurance Company", - "intake": "Intake Events", - "manual": "Manual Events", - "manualevent": "Add Manual Event" - } - }, - "scoreboard": { - "actions": { - "edit": "Edit" - }, - "errors": { - "adding": "Error adding Job to Scoreboard. {{message}}", - "removing": "Error removing Job from Scoreboard. {{message}}", - "updating": "Error updating Scoreboard. {{message}}" - }, - "fields": { - "bodyhrs": "Body Hours", - "date": "Date", - "painthrs": "Paint Hours" - }, - "labels": { - "allemployeetimetickets": "All Employee Time Tickets", - "asoftodaytarget": "As of Today", - "body": "Body", - "bodyabbrev": "B", - "bodycharttitle": "Body Targets vs Actual", - "calendarperiod": "Periods based on calendar weeks/months.", - "combinedcharttitle": "Combined Targets vs Actual", - "dailyactual": "Actual (D)", - "dailytarget": "Daily", - "efficiencyoverperiod": "Efficiency over Selected Dates", - "entries": "Scoreboard Entries", - "jobs": "Jobs", - "jobscompletednotinvoiced": "Completed Not Invoiced", - "lastmonth": "Last Month", - "lastweek": "Last Week", - "monthlytarget": "Monthly", - "priorweek": "Prior Week", - "productivestatistics": "Productive Hours Statistics", - "productivetimeticketsoverdate": "Productive Hours over Selected Dates", - "refinish": "Refinish", - "refinishabbrev": "R", - "refinishcharttitle": "Refinish Targets vs Actual", - "targets": "Targets", - "thismonth": "This Month", - "thisweek": "This Week", - "timetickets": "Time Tickets", - "timeticketsemployee": "Time Tickets by Employee", - "todateactual": "Actual (MTD)", - "total": "Total", - "totalhrs": "Total Hours", - "totaloverperiod": "Total over Selected Dates", - "weeklyactual": "Actual (W)", - "weeklytarget": "Weekly", - "workingdays": "Working Days / Month" - }, - "successes": { - "added": "Job added to scoreboard.", - "removed": "Job removed from scoreboard.", - "updated": "Scoreboard updated." - } - }, - "tasks": { - "actions": { - "edit": "Edit Task", - "new": "New Task" - }, - "buttons": { - "allTasks": "All", - "complete": "Toggle Complete", - "create": "Create Task", - "delete": "Toggle Delete", - "edit": "Edit", - "myTasks": "Mine", - "refresh": "Refresh" - }, - "date_presets": { - "completion": "Completion", - "day": "Day", - "days": "Days", - "delivery": "Delivery", - "next_week": "Next Week", - "one_month": "One Month", - "three_months": "Three Months", - "three_weeks": "Three Weeks", - "today": "Today", - "tomorrow": "Tomorrow", - "two_weeks": "Two Weeks" - }, - "failures": { - "completed": "Failed to toggle Task completion.", - "created": "Failed to create Task.", - "deleted": "Failed to toggle Task deletion.", - "updated": "Failed to update Task." - }, - "fields": { - "actions": "Actions", - "assigned_to": "Assigned To", - "bill": "Bill", - "billid": "Bill", - "completed": "Completed", - "created_at": "Created At", - "created_by": "Created By", - "description": "Description", - "due_date": "Due Date", - "job": { - "ro_number": "RO #" - }, - "jobid": "Job", - "jobline": "Job Line", - "joblineid": "Job Line", - "parts_order": "Parts Order", - "partsorderid": "Parts Order", - "priorities": { - "high": "High", - "low": "Low", - "medium": "Medium" - }, - "priority": "Priority", - "related_items": "Related Items", - "remind_at": "Remind At", - "title": "Title" - }, - "placeholders": { - "assigned_to": "Select an Employee", - "billid": "Select a Bill", - "description": "Enter a description", - "jobid": "Select a Job", - "joblineid": "Select a Job Line", - "partsorderid": "Select a Parts Order" - }, - "successes": { - "completed": "Toggled Task completion successfully.", - "created": "Task created successfully.", - "deleted": "Toggled Task deletion successfully.", - "updated": "Task updated successfully." - }, - "titles": { - "all_tasks": "All Tasks", - "completed": "Completed Tasks", - "deleted": "Deleted Tasks", - "job_tasks": "Job Tasks", - "mine": "My Tasks", - "my_tasks": "My Tasks" - }, - "validation": { - "due_at_error_message": "The due date must be in the future!", - "remind_at_error_message": "The reminder date and time must be at least 30 minutes in the future!" - } - }, - "tech": { - "fields": { - "employeeid": "Employee ID", - "pin": "PIN" - }, - "labels": { - "loggedin": "Logged in as {{name}}", - "notloggedin": "Not logged in." - } - }, - "templates": { - "errors": { - "updating": "Error updating template {{error}}." - }, - "successes": { - "updated": "Template updated successfully." - } - }, - "timetickets": { - "actions": { - "claimtasks": "Flag Hours", - "clockin": "Clock In", - "clockout": "Clock Out", - "commit": "Commit Tickets ({{count}})", - "commitone": "Commit", - "enter": "Enter New Time Ticket", - "payall": "Pay All", - "printemployee": "Print Time Tickets", - "uncommit": "Uncommit" - }, - "errors": { - "clockingin": "Error while clocking in. {{message}}", - "clockingout": "Error while clocking out. {{message}}", - "creating": "Error creating time ticket. {{message}}", - "deleting": "Error deleting time ticket. {{message}}", - "noemployeeforuser": "Unable to use Shift Clock", - "noemployeeforuser_sub": "An employee record has not been created for this user. Please create one before using the shift clock. ", - "payall": "Error flagging hours. {{error}}", - "shiftalreadyclockedon": "You are already clocked onto a shift. Unable to create shift entry." - }, - "fields": { - "actualhrs": "Actual Hours", - "ciecacode": "CIECA Code", - "clockhours": "Clock Hours", - "clockoff": "Clock Off", - "clockon": "Clocked In", - "committed": "Committed", - "committed_at": "Committed At", - "cost_center": "Cost Center", - "created_by": "Created By", - "date": "Ticket Date", - "efficiency": "Efficiency", - "employee": "Employee", - "employee_team": "Employee Team", - "flat_rate": "Flat Rate?", - "memo": "Memo", - "productivehrs": "Productive Hours", - "ro_number": "Job to Post Against", - "task_name": "Task" - }, - "labels": { - "alreadyclockedon": "You are already clocked in to the following Job(s):", - "ambreak": "AM Break", - "amshift": "AM Shift", - "claimtaskpreview": "Flagged Hours Preview", - "clockhours": "Shift Clock Hours Summary", - "clockintojob": "Clock In to Job", - "deleteconfirm": "Are you sure you want to delete this time ticket? This cannot be undone.", - "edit": "Edit Time Ticket", - "efficiency": "Efficiency", - "flat_rate": "Flat Rate", - "jobhours": "Job Related Time Tickets Summary", - "lunch": "Lunch", - "new": "New Time Ticket", - "payrollclaimedtasks": "These time tickets will be automatically entered to the system as a part of claiming this task. These numbers are calculated using the jobs assigned lines. If lines are unassigned, they will be excluded from created tickets.", - "pmbreak": "PM Break", - "pmshift": "PM Shift", - "shift": "Shift", - "shiftalreadyclockedon": "Active Shift Time Tickets", - "straight_time": "Straight Time", - "task": "Task", - "timetickets": "Time Tickets", - "unassigned": "Unassigned", - "zeroactualnegativeprod": "Actual hours must be 0 if entering negative productive hours." - }, - "successes": { - "clockedin": "Clocked in successfully.", - "clockedout": "Clocked out successfully.", - "committed": "Time Tickets Committed Successfully", - "created": "Time ticket entered successfully.", - "deleted": "Time ticket deleted successfully.", - "payall": "All hours paid out successfully." - }, - "validation": { - "clockoffmustbeafterclockon": "Clock off time must be the same or after clock in time.", - "clockoffwithoutclockon": "Clock off time cannot be set without a clock in time.", - "hoursenteredmorethanavailable": "The number of hours entered is more than what is available for this cost center.", - "unassignedlines": "There are currently {{unassignedHours}} hours of repair lines that are unassigned. These hours are not including in the above calculations and must be paid manually." - } - }, - "titles": { - "accounting-payables": "Payables | {{app}}", - "accounting-payments": "Payments | {{app}}", - "accounting-receivables": "Receivables | {{app}}", - "all_tasks": "All Tasks", - "app": "", - "bc": { - "accounting-payables": "Payables", - "accounting-payments": "Payments", - "accounting-receivables": "Receivables", - "all_tasks": "All Tasks", - "availablejobs": "Available Jobs", - "bills-list": "Bills", - "contracts": "Contracts", - "contracts-create": "New Contract", - "contracts-detail": "Contract #{{number}}", - "courtesycars": "Courtesy Cars", - "courtesycars-detail": "Courtesy Car {{number}}", - "courtesycars-new": "New Courtesy Car", - "dashboard": "Dashboard", - "dms": "DMS Export", - "export-logs": "Export Logs", - "inventory": "Inventory", - "jobs": "Jobs", - "jobs-active": "Active Jobs", - "jobs-admin": "Admin", - "jobs-all": "All Jobs", - "jobs-checklist": "Checklist", - "jobs-close": "Close Job", - "jobs-deliver": "Deliver Job", - "jobs-detail": "Job {{number}}", - "jobs-intake": "Intake", - "jobs-new": "Create a New Job", - "jobs-ready": "Ready Jobs", - "my_tasks": "My Tasks", - "owner-detail": "{{name}}", - "owners": "Owners", - "parts-queue": "Parts Queue", - "payments-all": "All Payments", - "phonebook": "Phonebook", - "productionboard": "Production Board - Visual", - "productionlist": "Production Board - List", - "profile": "My Profile", - "schedule": "Schedule", - "scoreboard": "Scoreboard", - "shop": "Manage my Shop ({{shopname}})", - "shop-csi": "CSI Responses", - "shop-templates": "Shop Templates", - "shop-vendors": "Vendors", - "tasks": "Tasks", - "temporarydocs": "Temporary Documents", - "timetickets": "Time Tickets", - "ttapprovals": "Time Ticket Approvals", - "vehicle-details": "Vehicle: {{vehicle}}", - "vehicles": "Vehicles" - }, - "bills-list": "Bills | {{app}}", - "contracts": "Courtesy Car Contracts | {{app}}", - "contracts-create": "New Contract | {{app}}", - "contracts-detail": "Contract {{id}} | {{app}}", - "courtesycars": "Courtesy Cars | {{app}}", - "courtesycars-create": "New Courtesy Car | {{app}}", - "courtesycars-detail": "Courtesy Car {{id}} | {{app}}", - "dashboard": "Dashboard | {{app}}", - "dms": "DMS Export | {{app}}", - "export-logs": "Export Logs | {{app}}", - "imexonline": "ImEX Online", - "inventory": "Inventory | {{app}}", - "jobs": "Active Jobs | {{app}}", - "jobs-admin": "Job {{ro_number}} - Admin | {{app}}", - "jobs-all": "All Jobs | {{app}}", - "jobs-checklist": "Job Checklist | {{app}}", - "jobs-close": "Close Job {{number}} | {{app}}", - "jobs-create": "Create a New Job | {{app}}", - "jobs-deliver": "Deliver Job | {{app}}", - "jobs-intake": "Intake | {{app}}", - "jobsavailable": "Available Jobs | {{app}}", - "jobsdetail": "Job {{ro_number}} | {{app}}", - "jobsdocuments": "Job Documents {{ro_number}} | {{app}}", - "manageroot": "Home | {{app}}", - "my_tasks": "My Tasks", - "owners": "All Owners | {{app}}", - "owners-detail": "{{name}} | {{app}}", - "parts-queue": "Parts Queue | {{app}}", - "payments-all": "Payments | {{app}}", - "phonebook": "Phonebook | {{app}}", - "productionboard": "Production Board - Visual | {{app}}", - "productionlist": "Production Board - List | {{app}}", - "profile": "My Profile | {{app}}", - "promanager": "ProManager", - "readyjobs": "Ready Jobs | {{app}}", - "resetpassword": "Reset Password", - "resetpasswordvalidate": "Enter New Password", - "romeonline": "Rome Online", - "schedule": "Schedule | {{app}}", - "scoreboard": "Scoreboard | {{app}}", - "shop": "My Shop | {{app}}", - "shop-csi": "CSI Responses | {{app}}", - "shop-templates": "Shop Templates | {{app}}", - "shop_vendors": "Vendors | {{app}}", - "tasks": "Tasks", - "techconsole": "Technician Console | {{app}}", - "techjobclock": "Technician Job Clock | {{app}}", - "techjoblookup": "Technician Job Lookup | {{app}}", - "techshiftclock": "Technician Shift Clock | {{app}}", - "temporarydocs": "Temporary Documents | {{app}}", - "timetickets": "Time Tickets | {{app}}", - "ttapprovals": "Time Ticket Approvals | {{app}}", - "vehicledetail": "Vehicle Details {{vehicle}} | {{app}}", - "vehicles": "All Vehicles | {{app}}" - }, - "trello": { - "labels": { - "add_card": "Add Card", - "add_lane": "Add Lane", - "cancel": "Cancel", - "delete_lane": "Delete Lane", - "description": "Description", - "label": "Label", - "lane_actions": "Lane Actions", - "title": "Title" - } - }, - "tt_approvals": { - "actions": { - "approveselected": "Approve Selected" - }, - "labels": { - "approval_queue_in_use": "Time tickets will be added to the approval queue.", - "calculate": "Calculate" - } - }, - "user": { - "actions": { - "changepassword": "Change Password", - "signout": "Sign Out", - "updateprofile": "Update Profile" - }, - "errors": { - "updating": "Error updating user or association {{message}}" - }, - "fields": { - "authlevel": "Authorization Level", - "displayname": "Display Name", - "email": "Email", - "photourl": "Avatar URL" - }, - "labels": { - "actions": "Actions", - "changepassword": "Change Password", - "profileinfo": "Profile Info" - }, - "successess": { - "passwordchanged": "Password changed successfully. " - } - }, - "users": { - "errors": { - "signinerror": { - "auth/user-disabled": "User account disabled. ", - "auth/user-not-found": "A user with this email does not exist.", - "auth/wrong-password": "The email and password combination you provided is incorrect." - } - } - }, - "vehicles": { - "errors": { - "deleting": "Error deleting vehicle. {{error}}.", - "noaccess": "The vehicle does not exist or you do not have access to it.", - "selectexistingornew": "Select an existing vehicle record or create a new one. ", - "validation": "Please ensure all fields are entered correctly.", - "validationtitle": "Validation Error" - }, - "fields": { - "description": "Vehicle Description", - "notes": "Vehicle Notes", - "plate_no": "License Plate", - "plate_st": "Plate Jurisdiction", - "trim_color": "Trim Color", - "v_bstyle": "Body Style", - "v_color": "Color", - "v_cond": "Condition", - "v_engine": "Engine", - "v_make_desc": "Make", - "v_makecode": "Make Code", - "v_mldgcode": "Molding Code", - "v_model_desc": "Model", - "v_model_yr": "Year", - "v_options": "Options", - "v_paint_codes": "Paint Codes {{number}}", - "v_prod_dt": "Production Date", - "v_stage": "Stage", - "v_tone": "Tone", - "v_trimcode": "Trim Code", - "v_type": "Type", - "v_vin": "V.I.N." - }, - "forms": { - "detail": "Vehicle Details", - "misc": "Miscellaneous", - "registration": "Registration" - }, - "labels": { - "deleteconfirm": "Are you sure you want to delete this vehicle? This cannot be undone.", - "fromvehicle": "Historical Vehicle Record", - "novehinfo": "No Vehicle Information", - "relatedjobs": "Related Jobs", - "updatevehicle": "Update Vehicle Information" - }, - "successes": { - "delete": "Vehicle deleted successfully.", - "save": "Vehicle saved successfully." - } - }, - "vendors": { - "actions": { - "addtophonebook": "Add to Phonebook", - "new": "New Vendor", - "newpreferredmake": "New Preferred Make" - }, - "errors": { - "deleting": "Error encountered while deleting vendor. ", - "saving": "Error encountered while saving vendor. " - }, - "fields": { - "active": "Active", - "am": "Aftermarket", - "city": "City", - "cost_center": "Cost Center", - "country": "Country", - "discount": "Discount % (as decimal)", - "display_name": "Display Name", - "dmsid": "DMS ID", - "due_date": "Payment Due Date (# of days)", - "email": "Contact Email", - "favorite": "Favorite?", - "lkq": "LKQ", - "make": "Make", - "name": "Vendor Name", - "oem": "OEM", - "phone": "Phone", - "prompt_discount": "Prompt Discount %", - "state": "Province/State", - "street1": "Street", - "street2": "Address 2", - "taxid": "Tax ID", - "terms": "Payment Terms", - "zip": "Zip/Postal Code" - }, - "labels": { - "noneselected": "No vendor is selected.", - "preferredmakes": "Preferred Makes for Vendor", - "search": "Type a Vendor's Name" - }, - "successes": { - "deleted": "Vendor deleted successfully. ", - "saved": "Vendor saved successfully." - }, - "validation": { - "unique_vendor_name": "You must enter a unique vendor name." - } - } - } + "translation": { + "allocations": { + "actions": { + "assign": "Assign" + }, + "errors": { + "deleting": "Error encountered while deleting allocation. {{message}}", + "saving": "Error while allocating. {{message}}", + "validation": "Please ensure all fields are entered correctly. " + }, + "fields": { + "employee": "Allocated To" + }, + "successes": { + "deleted": "Allocation deleted successfully.", + "save": "Allocated successfully. " + } + }, + "appointments": { + "actions": { + "block": "Block Day", + "calculate": "Calculate SMART Dates", + "cancel": "Cancel Appointment", + "intake": "Intake", + "new": "New Appointment", + "preview": "Preview", + "reschedule": "Reschedule", + "sendreminder": "Send Reminder", + "unblock": "Unblock", + "viewjob": "View Job" + }, + "errors": { + "blocking": "Error creating block {{message}}.", + "canceling": "Error canceling appointment. {{message}}", + "saving": "Error scheduling appointment. {{message}}" + }, + "fields": { + "alt_transport": "Alt. Trans.", + "color": "Appointment Color", + "end": "End", + "note": "Note", + "start": "Start", + "time": "Appointment Time", + "title": "Title" + }, + "labels": { + "arrivedon": "Arrived on: ", + "arrivingjobs": "Arriving Jobs", + "blocked": "Blocked", + "cancelledappointment": "Canceled appointment for: ", + "completingjobs": "Completing Jobs", + "dataconsistency": "<0>{{ro_number}} has a data consistency issue. It may have been excluded for scheduling purposes. CODE: {{code}}.", + "expectedjobs": "Expected Jobs in Production: ", + "expectedprodhrs": "Expected Production Hours:", + "history": "History", + "inproduction": "Jobs In Production", + "manualevent": "Add Manual Appointment", + "noarrivingjobs": "No Jobs are arriving.", + "nocompletingjobs": "No Jobs scheduled for completion.", + "nodateselected": "No date has been selected.", + "priorappointments": "Previous Appointments", + "reminder": "This is {{shopname}} reminding you about an appointment on {{date}} at {{time}}. Please let us know if you are not able to make the appointment. We look forward to seeing you soon. ", + "scheduledfor": "Scheduled appointment for: ", + "severalerrorsfound": "Several Jobs have issues which may prevent accurate smart scheduling. Click to expand.", + "smartscheduling": "Smart Scheduling", + "smspaymentreminder": "This is {{shopname}} reminding you about your remaining balance of {{amount}}. To pay for the said balance click the link {{payment_link}}.", + "suggesteddates": "Suggested Dates" + }, + "successes": { + "canceled": "Appointment canceled successfully.", + "created": "Appointment scheduled successfully.", + "saved": "Appointment saved successfully." + } + }, + "associations": { + "actions": { + "activate": "Activate" + }, + "fields": { + "active": "Active?", + "shopname": "Shop Name" + }, + "labels": { + "actions": "Actions" + } + }, + "audit": { + "fields": { + "cc": "CC", + "contents": "Contents", + "created": "Time", + "operation": "Operation", + "status": "Status", + "subject": "Subject", + "to": "To", + "useremail": "User", + "values": "Values" + } + }, + "audit_trail": { + "messages": { + "admin_job_remove_from_ar": "ADMIN: Remove from AR updated to: {{status}}", + "admin_jobmarkexported": "ADMIN: Job marked as exported.", + "admin_jobmarkforreexport": "ADMIN: Job marked for re-export.", + "admin_jobuninvoice": "ADMIN: Job has been uninvoiced.", + "admin_jobunvoid": "ADMIN: Job has been unvoided.", + "alerttoggle": "Alert Toggle set to {{status}}", + "appointmentcancel": "Appointment canceled. Lost Reason: {{lost_sale_reason}}.", + "appointmentinsert": "Appointment created. Appointment Date: {{start}}.", + "assignedlinehours": "Assigned job lines totaling {{hours}} units to {{team}}.", + "billdeleted": "Bill with invoice number {{invoice_number}} deleted.", + "billposted": "Bill with invoice number {{invoice_number}} posted.", + "billupdated": "Bill with invoice number {{invoice_number}} updated.", + "failedpayment": "Failed payment attempt.", + "jobassignmentchange": "Employee {{name}} assigned to {{operation}}", + "jobassignmentremoved": "Employee assignment removed for {{operation}}", + "jobchecklist": "Checklist type \"{{type}}\" completed. In production set to {{inproduction}}. Status set to {{status}}.", + "jobclosedwithbypass": "Job was invoiced using the master bypass password. ", + "jobconverted": "Job converted and assigned number {{ro_number}}.", + "jobdelivery": "Job intake completed. Status set to {{status}}. Actual completion is {{actual_completion}}.", + "jobexported": "Job has been exported", + "jobfieldchanged": "Job field $t(jobs.fields.{{field}}) changed to {{value}}.", + "jobimported": "Job imported.", + "jobinproductionchange": "Job production status set to {{inproduction}}", + "jobintake": "Job intake completed. Status set to {{status}}. Scheduled completion is {{scheduled_completion}}.", + "jobinvoiced": "Job has been invoiced.", + "jobioucreated": "IOU Created.", + "jobmodifylbradj": "Labor adjustments modified {{mod_lbr_ty}} / {{hours}}.", + "jobnoteadded": "Note added to Job.", + "jobnotedeleted": "Note deleted from Job.", + "jobnoteupdated": "Note updated on Job.", + "jobspartsorder": "Parts order {{order_number}} added to Job.", + "jobspartsreturn": "Parts return {{order_number}} added to Job.", + "jobstatuschange": "Job status changed to {{status}}.", + "jobsupplement": "Job supplement imported.", + "jobsuspend": "Suspend Toggle set to {{status}}", + "jobvoid": "Job has been voided.", + "tasks_completed": "Task '{{title}}' completed by {{completedBy}}", + "tasks_created": "Task '{{title}}' created by {{createdBy}}", + "tasks_deleted": "Task '{{title}}' deleted by {{deletedBy}}", + "tasks_uncompleted": "Task '{{title}}' uncompleted by {{uncompletedBy}}", + "tasks_undeleted": "Task '{{title}}' undeleted by {{undeletedBy}}", + "tasks_updated": "Task '{{title}}' updated by {{updatedBy}}" + } + }, + "billlines": { + "actions": { + "newline": "New Line" + }, + "fields": { + "actual_cost": "Actual Cost", + "actual_price": "Retail", + "cost_center": "Cost Center", + "federal_tax_applicable": "Fed. Tax?", + "jobline": "Job Line", + "line_desc": "Line Description", + "local_tax_applicable": "Loc. Tax?", + "location": "Location", + "quantity": "Quantity", + "state_tax_applicable": "St. Tax?" + }, + "labels": { + "deductedfromlbr": "Deduct from Labor?", + "entered": "Entered", + "from": "From", + "mod_lbr_adjustment": "Adjustment Units", + "other": "-- Not On Estimate --", + "reconciled": "Reconciled!", + "unreconciled": "Unreconciled" + }, + "validation": { + "atleastone": "At least one bill line must be entered." + } + }, + "bills": { + "actions": { + "deductallhours": "Deduct all", + "edit": "Edit", + "receive": "Receive Part", + "return": "Return Items" + }, + "errors": { + "creating": "Error adding bill. {{error}}", + "deleting": "Error deleting bill. {{error}}", + "existinginventoryline": "This bill cannot be deleted as it is tied to items in inventory.", + "exporting": "Error exporting payable(s). {{error}}", + "exporting-partner": "Unable to connect to partner application. Please ensure it is running and logged in.", + "invalidro": "Not a valid RO.", + "invalidvendor": "Not a valid vendor.", + "validation": "Please ensure all fields are entered correctly. " + }, + "fields": { + "allpartslocation": "Parts Bin", + "date": "Bill Date", + "exported": "Exported", + "federal_tax_rate": "Federal Tax Rate", + "invoice_number": "Invoice Number", + "is_credit_memo": "Credit Memo?", + "is_credit_memo_short": "CM", + "local_tax_rate": "Local Tax Rate", + "ro_number": "RO Number", + "state_tax_rate": "State Tax Rate", + "total": "Bill Total", + "vendor": "Vendor", + "vendorname": "Vendor Name" + }, + "labels": { + "actions": "Actions", + "bill_lines": "Bill Lines", + "bill_total": "Bill Total Amount", + "billcmtotal": "Credit Memos", + "bills": "Bills", + "calculatedcreditsnotreceived": "Calculated CNR", + "creditsnotreceived": "Credits Not Marked Received", + "creditsreceived": "Credits Received", + "dedfromlbr": "Labor Adjustments", + "deleteconfirm": "Are you sure you want to delete this bill? It cannot be undone. If this bill has deductions from labors, manual changes may be required.", + "discrepancy": "Discrepancy", + "discrepwithcms": "Discrepancy including Credit Memos", + "discrepwithlbradj": "Discrepancy including Lbr. Adj.", + "editadjwarning": "This bill had lines which resulted in labor adjustments. Manual correction to adjustments may be required.", + "entered_total": "Total of Entered Lines", + "enteringcreditmemo": "You are entering a credit memo. Please ensure you are also entering positive values.", + "federal_tax": "Federal Tax", + "federal_tax_exempt": "Federal Tax Exempt?", + "generatepartslabel": "Generate Parts Labels after Saving?", + "iouexists": "An IOU exists that is associated to this RO.", + "local_tax": "Local Tax", + "markexported": "Mark Exported", + "markforreexport": "Mark for Re-export", + "new": "New Bill", + "nobilllines": "", + "noneselected": "No bill selected.", + "onlycmforinvoiced": "Only credit memos can be entered for any Job that has been invoiced, exported, or voided.", + "printlabels": "Print Labels", + "retailtotal": "Bills Retail Total", + "returnfrombill": "Return From Bill", + "savewithdiscrepancy": "You are about to save this bill with a discrepancy. The system will continue to use the calculated amount using the bill lines. Press cancel to return to the bill.", + "state_tax": "State Tax", + "subtotal": "Subtotal", + "totalreturns": "Total Returns" + }, + "successes": { + "created": "Invoice added successfully.", + "deleted": "Bill deleted successfully.", + "exported": "Bill(s) exported successfully.", + "markexported": "Bill marked as exported.", + "reexport": "Bill marked for re-export." + }, + "validation": { + "closingperiod": "This Bill Date is outside of the Closing Period.", + "inventoryquantity": "Quantity must be greater than or equal to what has been added to inventory ({{number}}).", + "manualinhouse": "Manual posting to the in house vendor is restricted. ", + "unique_invoice_number": "This invoice number has already been entered for this vendor." + } + }, + "bodyshop": { + "actions": { + "add_task_preset": "Add Task Preset", + "addapptcolor": "Add Appointment Color", + "addbucket": "Add Definition", + "addpartslocation": "Add Parts Location", + "addpartsrule": "Add Parts Scan Rule", + "addspeedprint": "Add Speed Print", + "addtemplate": "Add Template", + "newlaborrate": "New Labor Rate", + "newsalestaxcode": "New Sales Tax Code", + "newstatus": "Add Status", + "testrender": "Test Render" + }, + "errors": { + "creatingdefaultview": "Error creating default view.", + "loading": "Unable to load shop details. Please call technical support.", + "saving": "Error encountered while saving. {{message}}" + }, + "fields": { + "ReceivableCustomField": "QBO Receivable Custom Field {{number}}", + "address1": "Address 1", + "address2": "Address 2", + "appt_alt_transport": "Appointment Alternative Transportation Options", + "appt_colors": { + "color": "Color", + "label": "Label" + }, + "appt_length": "Default Appointment Length", + "attach_pdf_to_email": "Attach PDF copy to sent emails?", + "batchid": "ADP Batch ID", + "bill_allow_post_to_closed": "Allow Bills to be posted to Closed Jobs", + "bill_federal_tax_rate": "Bills - Federal Tax Rate %", + "bill_local_tax_rate": "Bill - Local Tax Rate %", + "bill_state_tax_rate": "Bill - Provincial/State Tax Rate %", + "city": "City", + "closingperiod": "Closing Period", + "companycode": "ADP Company Code", + "country": "Country", + "dailybodytarget": "Scoreboard - Daily Body Target", + "dailypainttarget": "Scoreboard - Daily Paint Target", + "default_adjustment_rate": "Default Labor Deduction Adjustment Rate", + "deliver": { + "require_actual_delivery_date": "Require Actual Delivery", + "templates": "Delivery Templates" + }, + "dms": { + "apcontrol": "AP Control Number", + "appostingaccount": "AP Posting Account", + "cashierid": "Cashier ID", + "default_journal": "Default Journal", + "disablebillwip": "Disable bill WIP for A/P Posting", + "disablecontactvehiclecreation": "Disable Contact & Vehicle Updates/Creation", + "dms_acctnumber": "DMS Account #", + "dms_control_override": "Static Control # Override", + "dms_wip_acctnumber": "DMS W.I.P. Account #", + "generic_customer_number": "Generic Customer Number", + "itc_federal": "Federal Tax is ITC?", + "itc_local": "Local Tax is ITC?", + "itc_state": "State Tax is ITC?", + "mappingname": "DMS Mapping Name", + "sendmaterialscosting": "Materials Cost as % of Sale", + "srcco": "Source Company #/Dealer #" + }, + "email": "General Shop Email", + "enforce_class": "Enforce Class on Conversion?", + "enforce_conversion_category": "Enforce Category on Conversion?", + "enforce_conversion_csr": "Enforce CSR on Conversion?", + "enforce_referral": "Enforce Referrals", + "federal_tax_id": "Federal Tax ID (GST/HST)", + "ignoreblockeddays": "Scoreboard - Ignore Blocked Days", + "inhousevendorid": "In House Vendor ID", + "insurance_vendor_id": "Insurance Vendor ID", + "intake": { + "next_contact_hours": "Automatic Next Contact Date - Hours from Intake", + "templates": "Intake Templates" + }, + "intellipay_config": { + "cash_discount_percentage": "Cash Discount %", + "enable_cash_discount": "Enable Cash Discounting" + }, + "invoice_federal_tax_rate": "Invoices - Federal Tax Rate", + "invoice_local_tax_rate": "Invoices - Local Tax Rate", + "invoice_state_tax_rate": "Invoices - State Tax Rate", + "jc_hourly_rates": { + "mapa": "Job Costing - Paint Materials Hourly Cost Rate", + "mash": "Job Costing - Shop Materials Hourly Cost Rate" + }, + "last_name_first": "Display Owner Info as , ", + "lastnumberworkingdays": "Scoreboard - Last Number of Working Days", + "localmediaserverhttp": "Local Media Server - HTTP Path", + "localmediaservernetwork": "Local Media Server - Network Path", + "localmediatoken": "Local Media Server - Token", + "logo_img_footer_margin": "Footer Margin (px)", + "logo_img_header_margin": "Header Margin (px)", + "logo_img_path": "Shop Logo", + "logo_img_path_height": "Logo Image Height", + "logo_img_path_width": "Logo Image Width", + "md_categories": "Categories", + "md_ccc_rates": "Courtesy Car Contract Rate Presets", + "md_classes": "Classes", + "md_ded_notes": "Deductible Notes", + "md_email_cc": "Auto Email CC: $t(printcenter.subjects.jobs.{{template}})", + "md_from_emails": "Additional From Emails", + "md_functionality_toggles": { + "parts_queue_toggle": "Auto Add Imported/Supplemented Jobs to Parts Queue" + }, + "md_hour_split": { + "paint": "Paint Hour Split", + "prep": "Prep Hour Split" + }, + "md_ins_co": { + "city": "City", + "name": "Insurance Company Name", + "private": "Private", + "state": "Province/State", + "street1": "Street 1", + "street2": "Street 2", + "zip": "Zip/Postal Code" + }, + "md_jobline_presets": "Jobline Presets", + "md_lost_sale_reasons": "Lost Sale Reasons", + "md_parts_order_comment": "Parts Orders Comments", + "md_parts_scan": { + "expression": "RegEX Expression", + "flags": "Flags" + }, + "md_payment_types": "Payment Types", + "md_referral_sources": "Referral Sources", + "md_ro_guard": { + "enabled": "RO Guard Enabled?", + "enforce_ar": "Enforce AR Balance", + "enforce_bills": "Enforce Bill Discrepancy", + "enforce_cm": "Enforce Credit Memo Entry", + "enforce_labor": "Enforce Labor Allocation", + "enforce_ppd": "Enforce PPD Sync", + "enforce_profit": "Enforce Profit Requirement", + "enforce_sublet": "Enforce Sublet Completion", + "masterbypass": "Master Bypass Password (not encrypted)", + "totalgppercent_minimum": "Minimum Total Gross Profit %" + }, + "md_tasks_presets": { + "enable_tasks": "Enable Hour Flagging", + "hourstype": "Hour Types", + "memo": "Time Ticket Memo", + "name": "Preset Name", + "nextstatus": "Next Status", + "percent": "Percent", + "use_approvals": "Use Time Ticket Approval Queue" + }, + "messaginglabel": "Messaging Preset Label", + "messagingtext": "Messaging Preset Text", + "noteslabel": "Note Label", + "notestext": "Note Text", + "partslocation": "Parts Location", + "phone": "Phone", + "prodtargethrs": "Production Target Hours", + "rbac": { + "accounting": { + "exportlog": "Accounting -> Export Log", + "payables": "Accounting -> Payables", + "payments": "Accounting -> Payments", + "receivables": "Accounting -> Receivables" + }, + "bills": { + "delete": "Bills -> Delete", + "enter": "Bills -> Enter", + "list": "Bills -> List", + "reexport": "Bills -> Re-export", + "view": "Bills -> View" + }, + "contracts": { + "create": "Contracts -> Create", + "detail": "Contracts -> Detail", + "list": "Contracts -> List" + }, + "courtesycar": { + "create": "Courtesy Car -> Create", + "detail": "Courtesy Car -> Detail", + "list": "Courtesy Car -> List" + }, + "csi": { + "export": "CSI -> Export", + "page": "CSI -> Page" + }, + "employee_teams": { + "page": "Employee Teams -> List" + }, + "employees": { + "page": "Employees -> List" + }, + "inventory": { + "delete": "Inventory -> Delete", + "list": "Inventory -> List" + }, + "jobs": { + "admin": "Jobs -> Admin", + "available-list": "Jobs -> Available List", + "checklist-view": "Jobs -> Checklist View", + "close": "Jobs -> Close", + "create": "Jobs -> Create", + "deliver": "Jobs -> Deliver", + "detail": "Jobs -> Detail", + "intake": "Jobs -> Intake", + "list-active": "Jobs -> List Active", + "list-all": "Jobs -> List All", + "list-ready": "Jobs -> List Ready", + "partsqueue": "Jobs -> Parts Queue", + "void": "Jobs -> Void" + }, + "owners": { + "detail": "Owners -> Detail", + "list": "Owners -> List" + }, + "payments": { + "enter": "Payments -> Enter", + "list": "Payments -> List" + }, + "phonebook": { + "edit": "Phonebook -> Edit", + "view": "Phonebook -> View" + }, + "production": { + "board": "Production -> Board", + "list": "Production -> List" + }, + "schedule": { + "view": "Schedule -> View" + }, + "scoreboard": { + "view": "Scoreboard -> View" + }, + "shiftclock": { + "view": "Shift Clock -> View" + }, + "shop": { + "config": "Shop -> Config", + "dashboard": "Shop -> Dashboard", + "rbac": "Shop -> RBAC", + "reportcenter": "Shop -> Report Center", + "templates": "Shop -> Templates", + "vendors": "Shop -> Vendors" + }, + "temporarydocs": { + "view": "Temporary Docs -> View" + }, + "timetickets": { + "edit": "Time Tickets -> Edit", + "editcommitted": "Time Tickets -> Edit Committed", + "enter": "Time Tickets -> Enter", + "list": "Time Tickets -> List", + "shiftedit": "Time Tickets -> Shift Edit" + }, + "ttapprovals": { + "approve": "Time Ticket Approval -> Approve", + "view": "Time Ticket Approval -> View" + }, + "users": { + "editaccess": "Users -> Edit access" + } + }, + "responsibilitycenter": "Responsibility Center", + "responsibilitycenter_accountdesc": "Account Description", + "responsibilitycenter_accountitem": "Item", + "responsibilitycenter_accountname": "Account Name", + "responsibilitycenter_accountnumber": "Account Number", + "responsibilitycenter_rate": "Rate", + "responsibilitycenter_tax_rate": "Tax {{typeNum}} Tier {{typeNumIterator}} Rate", + "responsibilitycenter_tax_sur": "Tax {{typeNum}} Tier {{typeNumIterator}} Surcharge", + "responsibilitycenter_tax_thres": "Tax {{typeNum}} Tier {{typeNumIterator}} Threshold", + "responsibilitycenter_tax_tier": "Tax {{typeNum}} Tier {{typeNumIterator}}", + "responsibilitycenter_tax_type": "Tax {{typeNum}} Type", + "responsibilitycenters": { + "ap": "Accounts Payable", + "ar": "Accounts Receivable", + "ats": "ATS", + "federal_tax": "Federal Tax", + "federal_tax_itc": "Federal Tax Credit", + "gst_override": "GST Override Account #", + "invoiceexemptcode": "QuickBooks US - Invoice Tax Exempt Code", + "itemexemptcode": "QuickBooks US - Line Item Tax Exempt Code", + "la1": "LA1", + "la2": "LA2", + "la3": "LA3", + "la4": "LA4", + "laa": "Aluminum", + "lab": "Body", + "lad": "Diagnostic", + "lae": "Electrical", + "laf": "Frame", + "lag": "Glass", + "lam": "Mechanical", + "lar": "Refinish", + "las": "Structural", + "lau": "User Defined", + "local_tax": "Local Tax", + "mapa": "Paint Materials", + "mash": "Shop Materials", + "paa": "Aftermarket", + "pac": "Chrome", + "pag": "Glass", + "pal": "LKQ", + "pam": "Remanufactured", + "pan": "OEM", + "pao": "Other", + "pap": "OEM Partial", + "par": "Recored", + "pas": "Sublet", + "pasl": "Sublet (L)", + "refund": "Refund", + "sales_tax_codes": { + "code": "Code", + "description": "Description", + "federal": "Federal Tax Applies", + "local": "Local Tax Applies", + "state": "State Tax Applies" + }, + "state_tax": "State Tax", + "tow": "Towing" + }, + "schedule_end_time": "Schedule Ending Time", + "schedule_start_time": "Schedule Starting Time", + "shopname": "Shop Name", + "speedprint": { + "id": "Id", + "label": "Label", + "templates": "Templates" + }, + "ss_configuration": { + "dailyhrslimit": "Daily Incoming Hours Limit" + }, + "ssbuckets": { + "color": "Job Color", + "gte": "Greater Than/Equal to (hrs)", + "id": "ID", + "label": "Label", + "lt": "Less than (hrs)", + "target": "Target (count)" + }, + "state": "Province/State", + "state_tax_id": "State Tax ID", + "status": "Status Label", + "statuses": { + "active_statuses": "Active Statuses (Filtering for Active Jobs throughout system)", + "additional_board_statuses": "Additional Status to Display on Production Board", + "color": "Color", + "default_arrived": "Default Arrived Status (Transition to Production)", + "default_bo": "Default Backordered Status", + "default_canceled": "Default Canceled Status", + "default_completed": "Default Completed Status", + "default_delivered": "Default Delivered Status (Transition to Post-Production)", + "default_exported": "Default Exported Status", + "default_imported": "Default Imported Status", + "default_invoiced": "Default Invoiced Status", + "default_ordered": "Default Ordered Status", + "default_quote": "Default Quote Status", + "default_received": "Default Received Status", + "default_returned": "Default Returned", + "default_scheduled": "Default Scheduled Status", + "default_void": "Default Void", + "open_statuses": "Open Statuses", + "post_production_statuses": "Post-Production Statuses", + "pre_production_statuses": "Pre-Production Statuses", + "production_colors": "Production Status Colors", + "production_statuses": "Production Statuses", + "ready_statuses": "Ready Statuses" + }, + "target_touchtime": "Target Touch Time", + "timezone": "Timezone", + "tt_allow_post_to_invoiced": "Allow Time Tickets to be posted to Invoiced & Exported Jobs", + "tt_enforce_hours_for_tech_console": "Restrict Claimable hours from Tech Console", + "use_fippa": "Conceal Customer Information on Generated Documents?", + "use_paint_scale_data": "Use Paint Scale Data for Job Costing?", + "uselocalmediaserver": "Use Local Media Server?", + "website": "Website", + "zip_post": "Zip/Postal Code" + }, + "labels": { + "2tiername": "Name => RO", + "2tiersetup": "2 Tier Setup", + "2tiersource": "Source => RO", + "accountingsetup": "Accounting Setup", + "accountingtiers": "Number of Tiers to Use for Export", + "alljobstatuses": "All Job Statuses", + "allopenjobstatuses": "All Open Job Statuses", + "apptcolors": "Appointment Colors", + "businessinformation": "Business Information", + "checklists": "Checklists", + "csiq": "CSI Questions", + "customtemplates": "Custom Templates", + "defaultcostsmapping": "Default Costs Mapping", + "defaultprofitsmapping": "Default Profits Mapping", + "deliverchecklist": "Delivery Checklist", + "dms": { + "cdk": { + "controllist": "Control Number List", + "payers": "Payers" + }, + "cdk_dealerid": "CDK Dealer ID", + "costsmapping": "Costs Mapping", + "dms_allocations": "DMS Allocations", + "pbs_serialnumber": "PBS Serial Number", + "profitsmapping": "Profits Mapping", + "title": "DMS" + }, + "emaillater": "Email Later", + "employee_teams": "Employee Teams", + "employees": "Employees", + "estimators": "Estimators", + "filehandlers": "Adjusters", + "insurancecos": "Insurance Companies", + "intakechecklist": "Intake Checklist", + "intellipay": "IntelliPay", + "intellipay_cash_discount": "Please ensure that cash discounting has been enabled on your merchant account. Reach out to IntelliPay Support if you need assistance. ", + "jobstatuses": "Job Statuses", + "laborrates": "Labor Rates", + "licensing": "Licensing", + "md_parts_scan": "Parts Scan Rules", + "md_ro_guard": "RO Guard", + "md_tasks_presets": "Tasks Presets", + "md_to_emails": "Preset To Emails", + "md_to_emails_emails": "Emails", + "messagingpresets": "Messaging Presets", + "notemplatesavailable": "No templates available to add.", + "notespresets": "Notes Presets", + "orderstatuses": "Order Statuses", + "partslocations": "Parts Locations", + "partsscan": "Critical Parts Scanning", + "printlater": "Print Later", + "qbo": "Use QuickBooks Online?", + "qbo_departmentid": "QBO Department ID", + "qbo_usa": "QBO USA Compatibility", + "rbac": "Role Based Access Control", + "responsibilitycenters": { + "costs": "Cost Centers", + "profits": "Profit Centers", + "sales_tax_codes": "Sales Tax Codes", + "tax_accounts": "Tax Accounts", + "title": "Responsibility Centers", + "ttl_adjustment": "Subtotal Adjustment Account", + "ttl_tax_adjustment": "Tax Adjustment Account" + }, + "roguard": { + "title": "RO Guard" + }, + "scheduling": "SMART Scheduling", + "scoreboardsetup": "Scoreboard Setup", + "shopinfo": "Shop Information", + "speedprint": "Speed Print Configuration", + "ssbuckets": "Job Size Definitions", + "systemsettings": "System Settings", + "task-presets": "Task Presets", + "workingdays": "Working Days" + }, + "successes": { + "areyousure": "Are you sure you want to continue?", + "defaultviewcreated": "Default view created successfully.", + "save": "Shop configuration saved successfully. ", + "unsavedchanges": "Unsaved changes will be lost. Are you sure you want to continue?" + }, + "validation": { + "centermustexist": "The chosen responsibility center does not exist.", + "larsplit": "Refinish hour split must add up to 1.", + "useremailmustexist": "This email is not a valid user." + } + }, + "checklist": { + "actions": { + "printall": "Print All Documents" + }, + "errors": { + "complete": "Error during Job checklist completion. {{error}}", + "nochecklist": "No checklist has been configured for your shop. " + }, + "labels": { + "addtoproduction": "Add Job to Production?", + "allow_text_message": "Permission to Text?", + "checklist": "Checklist", + "printpack": "Job Intake Print Pack", + "removefromproduction": "Remove Job from Production?" + }, + "successes": { + "completed": "Job checklist completed." + } + }, + "contracts": { + "actions": { + "changerate": "Change Contract Rates", + "convertoro": "Convert to RO", + "decodelicense": "Decode License", + "find": "Find Contract", + "printcontract": "Print Contract", + "senddltoform": "Insert Driver's License Information" + }, + "errors": { + "fetchingjobinfo": "Error fetching Job Info. {{error}}.", + "returning": "Error returning Courtesy Car. {{error}}", + "saving": "Error saving Contract. {{error}}", + "selectjobandcar": "Please ensure both a car and job are selected." + }, + "fields": { + "actax": "A/C Tax", + "actualreturn": "Actual Return Date", + "agreementnumber": "Agreement Number", + "cc_cardholder": "Cardholder Name", + "cc_expiry": "Credit Card Expiry Date", + "cc_num": "Credit Card Number", + "cleanupcharge": "Clean Up Charge", + "coverage": "Coverage", + "dailyfreekm": "Daily Free Mileage", + "dailyrate": "Daily Rate", + "damage": "Existing Damage", + "damagewaiver": "Damage Waiver", + "driver": "Driver", + "driver_addr1": "Driver Address 1", + "driver_addr2": "Driver Address 2", + "driver_city": "Driver City", + "driver_dlexpiry": "Driver's License Expiration Date", + "driver_dlnumber": "Driver's License Number", + "driver_dlst": "Driver's License Province/State", + "driver_dob": "Driver's DOB", + "driver_fn": "Driver's First Name", + "driver_ln": "Driver's Last Name", + "driver_ph1": "Driver's Phone", + "driver_state": "Driver's Province/State ", + "driver_zip": "Driver's Postal/ZIP Code", + "excesskmrate": "Excess Mileage", + "federaltax": "Federal Taxes", + "fuelin": "Fuel In", + "fuelout": "Fuel Out", + "kmend": "Mileage End", + "kmstart": "Mileage Start", + "length": "Length", + "localtax": "Local Taxes", + "refuelcharge": "Refuel Charge (per liter/gallon)", + "scheduledreturn": "Scheduled Return", + "start": "Contract Start", + "statetax": "Provincial/State Taxes", + "status": "Status" + }, + "labels": { + "agreement": "Agreement {{agreement_num}} - {{status}}", + "availablecars": "Available Cars", + "cardueforservice": "The courtesy car is due for servicing.", + "convertform": { + "applycleanupcharge": "Apply cleanup charge?", + "refuelqty": "Refuel qty.?" + }, + "correctdataonform": "Please review the information above. If any of it is not correct, you can fix it later.", + "dateinpast": "Date is in the past.", + "dlexpirebeforereturn": "The driver's license expires before the car is expected to return. ", + "driverinformation": "Driver's Information", + "findcontract": "Find Contract", + "findermodal": "Contract Finder", + "insuranceexpired": "The courtesy car insurance expires before the car is expected to return.", + "noteconvertedfrom": "R.O. created from converted Courtesy Car Contract {{agreementnumber}}.", + "populatefromjob": "Populate from Job", + "rates": "Contract Rates", + "time": "Time", + "vehicle": "Vehicle", + "waitingforscan": "Please scan driver's license barcode..." + }, + "status": { + "new": "New Contract", + "out": "Out", + "returned": "Returned" + }, + "successes": { + "saved": "Contract saved successfully. " + } + }, + "courtesycars": { + "actions": { + "new": "New Courtesy Car", + "return": "Return Car" + }, + "errors": { + "saving": "Error saving Courtesy Car. {{error}}" + }, + "fields": { + "color": "Color", + "dailycost": "Daily Cost to Rent", + "damage": "Damage", + "fleetnumber": "Fleet Number", + "fuel": "Fuel Level", + "insuranceexpires": "Insurance Expires On", + "leaseenddate": "Lease Ends On", + "make": "Make", + "mileage": "Mileage", + "model": "Model", + "nextservicedate": "Next Service Date", + "nextservicekm": "Next Service KMs", + "notes": "Notes", + "plate": "Plate Number", + "purchasedate": "Purchase Date", + "readiness": "Readiness", + "registrationexpires": "Registration Expires On", + "serviceenddate": "Usage End Date", + "servicestartdate": "Usage Start Date", + "status": "Status", + "vin": "VIN", + "year": "Year" + }, + "labels": { + "courtesycar": "Courtesy Car", + "fuel": { + "12": "1/2", + "14": "1/4", + "18": "1/8", + "34": "3/4", + "38": "3/8", + "58": "5/8", + "78": "7/8", + "empty": "Empty", + "full": "Full" + }, + "outwith": "Out With", + "return": "Return Courtesy Car", + "status": "Status", + "uniquefleet": "Enter a unique fleet number.", + "usage": "Usage", + "vehicle": "Vehicle Description" + }, + "readiness": { + "notready": "Not Ready", + "ready": "Ready" + }, + "status": { + "in": "Available", + "inservice": "Service/Maintenance", + "leasereturn": "Lease Returned", + "out": "Rented", + "sold": "Sold", + "unavailable": "Unavailable" + }, + "successes": { + "saved": "Courtesy Car saved successfully." + } + }, + "csi": { + "actions": { + "activate": "Activate" + }, + "errors": { + "creating": "Error creating survey {{message}}", + "notconfigured": "You do not have any current CSI Question Sets configured.", + "notfoundsubtitle": "We were unable to find a survey using the link you provided. Please ensure the URL is correct or reach out to your shop for more help.", + "notfoundtitle": "No survey found.", + "surveycompletesubtitle": "", + "surveycompletetitle": "" + }, + "fields": { + "completedon": "Completed On", + "created_at": "Created At", + "surveyid": "", + "validuntil": "" + }, + "labels": { + "copyright": "", + "greeting": "", + "intro": "", + "nologgedinuser": "Please log out of {{app}}", + "nologgedinuser_sub": "Users of {{app}} cannot complete CSI surveys while logged in. Please log out and try again.", + "noneselected": "No response selected.", + "title": "Customer Satisfaction Survey" + }, + "successes": { + "created": "CSI created successfully. ", + "submitted": "Your responses have been submitted successfully.", + "submittedsub": "Your input is highly appreciated." + } + }, + "dashboard": { + "actions": { + "addcomponent": "Add Component" + }, + "errors": { + "refreshrequired": "You must refresh the dashboard data to see this component.", + "updatinglayout": "Error saving updated layout {{message}}" + }, + "labels": { + "bodyhrs": "Body Hrs", + "dollarsinproduction": "Dollars in Production", + "phone": "Phone", + "prodhrs": "Production Hrs", + "refhrs": "Refinish Hrs" + }, + "titles": { + "joblifecycle": "Job Life Cycles", + "labhours": "Total Body Hours", + "larhours": "Total Refinish Hours", + "monthlyemployeeefficiency": "Monthly Employee Efficiency", + "monthlyjobcosting": "Monthly Job Costing ", + "monthlylaborsales": "Monthly Labor Sales", + "monthlypartssales": "Monthly Parts Sales", + "monthlyrevenuegraph": "Monthly Revenue Graph", + "prodhrssummary": "Production Hours Summary", + "productiondollars": "Total Dollars in Production", + "productionhours": "Total Hours in Production", + "projectedmonthlysales": "Projected Monthly Sales", + "scheduledindate": "Sheduled In Today: {{date}}", + "scheduledintoday": "Sheduled In Today", + "scheduledoutdate": "Sheduled Out Today: {{date}}", + "scheduledouttoday": "Sheduled Out Today", + "tasks": "Tasks" + } + }, + "dms": { + "errors": { + "alreadyexported": "This job has already been sent to the DMS. If you need to resend it, please use admin permissions to mark the job for re-export." + }, + "labels": { + "refreshallocations": "Refresh to see DMS Allocataions." + } + }, + "documents": { + "actions": { + "delete": "Delete Selected Documents", + "download": "Download Selected Images", + "reassign": "Reassign to another Job", + "selectallimages": "Select All Images", + "selectallotherdocuments": "Select All Other Documents" + }, + "errors": { + "deletes3": "Error deleting document from storage. ", + "deleting": "Error deleting documents {{error}}", + "deleting_cloudinary": "Error deleting document from storage. {{message}}", + "getpresignurl": "Error obtaining presigned URL for document. {{message}}", + "insert": "Unable to upload file. {{message}}", + "nodocuments": "There are no documents.", + "updating": "Error updating document. {{error}}" + }, + "labels": { + "confirmdelete": "Are you sure you want to delete these documents. This CANNOT be undone.", + "doctype": "Document Type", + "newjobid": "Assign to Job", + "openinexplorer": "Open in Explorer", + "optimizedimage": "The below image is optimized. Click on the picture below to open in a new window and view it full size, or open it in explorer.", + "reassign_limitexceeded": "Reassigning all selected documents will exceed the job storage limit for your shop. ", + "reassign_limitexceeded_title": "Unable to reassign document(s)", + "storageexceeded": "You've exceeded your storage limit for this job. Please remove documents, or increase your storage plan.", + "storageexceeded_title": "Storage Limit Exceeded", + "upload": "Upload", + "upload_limitexceeded": "Uploading all selected documents will exceed the job storage limit for your shop. ", + "upload_limitexceeded_title": "Unable to upload document(s)", + "uploading": "Uploading...", + "usage": "of Job storage used. ({{used}} / {{total}})" + }, + "successes": { + "delete": "Document(s) deleted successfully.", + "edituploaded": "Edited document uploaded successfully. Please close this window and refresh the documents list.", + "insert": "Uploaded document successfully. ", + "updated": "Document updated successfully. " + } + }, + "emails": { + "errors": { + "notsent": "Email not sent. Error encountered while sending {{message}}" + }, + "fields": { + "cc": "CC", + "from": "From", + "subject": "Subject", + "to": "To" + }, + "labels": { + "attachments": "Attachments", + "documents": "Documents", + "emailpreview": "Email Preview", + "generatingemail": "Generating email...", + "pdfcopywillbeattached": "A PDF copy of this email will be attached when it is sent.", + "preview": "Email Preview" + }, + "successes": { + "sent": "Email sent successfully." + } + }, + "employee_teams": { + "actions": { + "new": "New Team", + "newmember": "New Team Member" + }, + "fields": { + "active": "Active", + "employeeid": "Employee", + "max_load": "Max Load", + "name": "Team Name", + "percentage": "Percent" + } + }, + "employees": { + "actions": { + "addvacation": "Add Vacation", + "new": "New Employee", + "newrate": "New Rate" + }, + "errors": { + "delete": "Error encountered while deleting employee. {{message}}", + "save": "Error encountered saving employee. {{message}}", + "validation": "Please check all fields.", + "validationtitle": "Unable to save employee." + }, + "fields": { + "active": "Active?", + "base_rate": "Base Rate", + "cost_center": "Cost Center", + "employee_number": "Employee Number", + "external_id": "External Employee ID", + "first_name": "First Name", + "flat_rate": "Flat Rate (Disabled is Straight Time)", + "hire_date": "Hire Date", + "last_name": "Last Name", + "pin": "Tech Console PIN", + "rate": "Rate", + "termination_date": "Termination Date", + "user_email": "User Email", + "vacation": { + "end": "Vacation End", + "length": "Vacation Length", + "start": "Vacation Start" + } + }, + "labels": { + "actions": "Actions", + "active": "Active", + "endmustbeafterstart": "End date must be after start date.", + "flat_rate": "Flat Rate", + "inactive": "Inactive", + "name": "Name", + "rate_type": "Rate Type", + "status": "Status", + "straight_time": "Straight Time" + }, + "successes": { + "delete": "Employee deleted successfully.", + "save": "Employee saved successfully.", + "vacationadded": "Employee vacation added." + }, + "validation": { + "unique_employee_number": "You must enter a unique employee number." + } + }, + "eula": { + "buttons": { + "accept": "Accept EULA" + }, + "content": { + "never_scrolled": "You must scroll to the bottom of the Terms and Conditions before accepting." + }, + "errors": { + "acceptance": { + "description": "Something went wrong while accepting the EULA. Please try again.", + "message": "Eula Acceptance Error" + } + }, + "labels": { + "accepted_terms": "I accept the terms and conditions of this agreement.", + "address": "Address", + "business_name": "Legal Business Name", + "date_accepted": "Date Accepted", + "first_name": "First Name", + "last_name": "Last Name", + "phone_number": "Phone Number" + }, + "messages": { + "accepted_terms": "Please accept the terms and conditions of this agreement.", + "business_name": "Please enter your legal business name.", + "date_accepted": "Please enter Today's Date.", + "first_name": "Please enter your first name.", + "last_name": "Please enter your last name.", + "phone_number": "Please enter your phone number." + }, + "titles": { + "modal": "Terms and Conditions", + "upper_card": "Acknowledgement" + } + }, + "exportlogs": { + "fields": { + "createdat": "Created At" + }, + "labels": { + "attempts": "Export Attempts", + "priorsuccesfulexport": "This record has previously been exported successfully. Please make sure it has already been deleted in the target system." + } + }, + "general": { + "actions": { + "add": "Add", + "autoupdate": "{{app}} will automatically update in {{time}} seconds. Please save all changes.", + "calculate": "Calculate", + "cancel": "Cancel", + "clear": "Clear", + "close": "Close", + "copied": "Copied!", + "copylink": "Copy Link", + "create": "Create", + "defaults": "Defaults", + "delay": "Delay Update (5 mins)", + "delete": "Delete", + "deleteall": "Delete All", + "deselectall": "Deselect All", + "download": "Download", + "edit": "Edit", + "login": "Login", + "next": "Next", + "previous": "Previous", + "print": "Print", + "refresh": "Refresh", + "remove": "Remove", + "remove_alert": "Are you sure you want to dismiss the alert?", + "reset": "Reset your changes.", + "resetpassword": "Reset Password", + "save": "Save", + "saveandnew": "Save and New", + "saveas": "Save As", + "selectall": "Select All", + "send": "Send", + "sendbysms": "Send by SMS", + "senderrortosupport": "Send Error to Support", + "submit": "Submit", + "tryagain": "Try Again", + "view": "View", + "viewreleasenotes": "See What's Changed" + }, + "errors": { + "fcm": "You must allow notification permissions to have real time messaging. Click to try again.", + "notfound": "No record was found.", + "sizelimit": "The selected items exceed the size limit." + }, + "itemtypes": { + "contract": "CC Contract", + "courtesycar": "Courtesy Car", + "job": "Job", + "owner": "Owner", + "vehicle": "Vehicle" + }, + "labels": { + "actions": "Actions", + "areyousure": "Are you sure?", + "barcode": "Barcode", + "cancel": "Are you sure you want to cancel? Your changes will not be saved.", + "clear": "Clear", + "confirmpassword": "Confirm Password", + "created_at": "Created At", + "date": "Select Date", + "datetime": "Select Date & Time", + "email": "Email", + "errors": "Errors", + "excel": "Excel", + "exceptiontitle": "An error has occurred.", + "friday": "Friday", + "globalsearch": "Global Search", + "help": "Help", + "hours": "hrs", + "in": "In", + "instanceconflictext": "Your {{app}} account can only be used on one device at any given time. Refresh your session to take control.", + "instanceconflictitle": "Your account is being used elsewhere.", + "item": "Item", + "label": "Label", + "loading": "Loading...", + "loadingapp": "Loading {{app}}", + "loadingshop": "Loading shop data...", + "loggingin": "Authorizing...", + "markedexported": "Manually marked as exported.", + "media": "Media", + "message": "Message", + "monday": "Monday", + "na": "N/A", + "newpassword": "New Password", + "no": "No", + "nointernet": "It looks like you're not connected to the internet.", + "nointernet_sub": "Please check your connection and try again. ", + "none": "None", + "out": "Out", + "password": "Password", + "passwordresetsuccess": "A password reset link has been sent to you.", + "passwordresetsuccess_sub": "You should receive this email in the next few minutes. Please check your email including any junk or spam folders. ", + "passwordresetvalidatesuccess": "Password successfully reset. ", + "passwordresetvalidatesuccess_sub": "You may now sign in again using your new password. ", + "passwordsdonotmatch": "The passwords you have entered do not match.", + "print": "Print", + "refresh": "Refresh", + "reports": "Reports", + "required": "Required", + "saturday": "Saturday", + "search": "Search...", + "searchresults": "Results for {{search}}", + "selectdate": "Select date...", + "sendagain": "Send Again", + "sendby": "Send By", + "signin": "Sign In", + "sms": "SMS", + "status": "Status", + "sub_status": { + "expired": "The subscription for this shop has expired. Please contact technical support to reactivate the subscription. " + }, + "successful": "Successful", + "sunday": "Sunday", + "text": "Text", + "thursday": "Thursday", + "total": "Total", + "totals": "Totals", + "tuesday": "Tuesday", + "tvmode": "TV Mode", + "unknown": "Unknown", + "unsavedchanges": "Unsaved changes.", + "username": "Username", + "view": "View", + "wednesday": "Wednesday", + "yes": "Yes" + }, + "languages": { + "english": "English", + "french": "French", + "spanish": "Spanish" + }, + "messages": { + "exception": "{{app}} has encountered an error. Please try again. If the problem persists, please submit a support ticket or contact us.", + "newversionmessage": "Click refresh below to update to the latest available version of {{app}}. Please make sure all other tabs and windows are closed.", + "newversiontitle": "New version of {{app}} Available", + "noacctfilepath": "There is no accounting file path set. You will not be able to export any items.", + "nofeatureaccess": "You do not have access to this feature of {{app}}. Please contact support to request a license for this feature.", + "noshop": "You do not have access to any shops. Please reach out to your shop manager or technical support. ", + "notfoundsub": "Please make sure that you have access to the data or that the link is correct.", + "notfoundtitle": "We couldn't find what you're looking for...", + "partnernotrunning": "{{app}} has detected that the partner is not running. Please ensure it is running to enable full functionality.", + "rbacunauth": "You are not authorized to view this content. Please reach out to your shop manager to change your access level.", + "unsavedchanges": "You have unsaved changes.", + "unsavedchangespopup": "You have unsaved changes. Are you sure you want to leave?" + }, + "validation": { + "invalidemail": "Please enter a valid email.", + "invalidphone": "Please enter a valid phone number.", + "required": "{{label}} is required." + } + }, + "help": { + "actions": { + "connect": "Connect" + }, + "labels": { + "codeplacholder": "6 digit PIN code", + "rescuedesc": "Enter the 6 digit code provided by {{app}} Support below and click connect.", + "rescuetitle": "Rescue Me!" + } + }, + "intake": { + "labels": { + "printpack": "Intake Print Pack" + } + }, + "inventory": { + "actions": { + "addtoinventory": "Add to Inventory", + "addtoro": "Add to RO", + "consumefrominventory": "Consume from Inventory?", + "edit": "Edit Inventory LIne", + "new": "New Inventory Line" + }, + "errors": { + "inserting": "Error inserting inventory item. {{error}}" + }, + "fields": { + "comment": "Comment", + "manualinvoicenumber": "Invoice Number", + "manualvendor": "Vendor" + }, + "labels": { + "consumedbyjob": "Consumed by Job", + "deleteconfirm": "Are you sure you want to delete this from inventory? The associated bill will not be modified or deleted. ", + "frombillinvoicenumber": "Original Bill Invoice Number", + "fromvendor": "Original Bill Vendor", + "inventory": "Inventory", + "showall": "Show All Inventory", + "showavailable": "Show Only Available Inventory" + }, + "successes": { + "deleted": "Inventory lined deleted.", + "inserted": "Added line to inventory.", + "updated": "Inventory line updated." + } + }, + "job_lifecycle": { + "columns": { + "duration": "Duration", + "end": "End", + "human_readable": "Human Readable", + "percentage": "Percentage", + "relative_end": "Relative End", + "relative_start": "Relative Start", + "start": "Start", + "status": "Status", + "status_count": "In Status", + "value": "Value" + }, + "content": { + "calculated_based_on": "Calculated based on", + "current_status_accumulated_time": "Current Status Accumulated Time", + "data_unavailable": " There is currently no Lifecycle data for this Job.", + "jobs_in_since": "Jobs in since", + "legend_title": "Legend", + "loading": "Loading Job Timelines....", + "not_available": "N/A", + "previous_status_accumulated_time": "Previous Status Accumulated Time", + "title": "Job Lifecycle Component", + "title_durations": "Historical Status Durations", + "title_loading": "Loading", + "title_transitions": "Transitions" + }, + "errors": { + "fetch": "Error getting Job Lifecycle Data" + }, + "titles": { + "dashboard": "Job Lifecycle", + "top_durations": "Top Durations" + } + }, + "job_payments": { + "buttons": { + "create_short_link": "Generate Short Link", + "goback": "Go Back", + "proceedtopayment": "Proceed to Payment", + "refundpayment": "Refund Payment" + }, + "notifications": { + "error": { + "description": "Please try again. Make sure the refund amount does not exceeds the payment amount.", + "openingip": "Error connecting to IntelliPay service.", + "title": "Error placing refund" + } + }, + "titles": { + "amount": "Amount", + "dateOfPayment": "Date of Payment", + "descriptions": "Payment Details", + "hint": "Hint", + "payer": "Payer", + "payername": "Payer Name", + "paymentid": "Payment Reference ID", + "paymentnum": "Payment Number", + "paymenttype": "Payment Type", + "refundamount": "Refund Amount", + "transactionid": "Transaction ID" + } + }, + "joblines": { + "actions": { + "assign_team": "Assign Team", + "converttolabor": "Convert amount to Labor.", + "dispatchparts": "Dispatch Parts ({{count}})", + "new": "New Line" + }, + "errors": { + "creating": "Error encountered while creating job line. {{message}}", + "updating": "Error encountered updating job line. {{message}}" + }, + "fields": { + "act_price": "Retail Price", + "act_price_before_ppc": "Original Part Price", + "adjustment": "Adjustment", + "ah_detail_line": "Mark as Detail Labor Line (Autohouse Only)", + "amount": "Amount", + "assigned_team": "Team", + "assigned_team_name": "Team {{name}}", + "create_ppc": "Create PPC?", + "db_price": "List Price", + "lbr_types": { + "LA1": "LA1", + "LA2": "LA2", + "LA3": "LA3", + "LA4": "LA4", + "LAA": "Aluminum", + "LAB": "Body", + "LAD": "Diagnostic", + "LAE": "Electrical", + "LAF": "Frame", + "LAG": "Glass", + "LAM": "Mechanical", + "LAR": "Refinish", + "LAS": "Structural", + "LAU": "User Defined" + }, + "line_desc": "Line Desc.", + "line_ind": "S#", + "line_no": "Line #", + "location": "Location", + "mod_lb_hrs": "Hrs", + "mod_lbr_ty": "Labor Type", + "notes": "Notes", + "oem_partno": "OEM Part #", + "op_code_desc": "Op Code Description", + "part_qty": "Qty.", + "part_type": "Part Type", + "part_types": { + "CCC": "CC Cleaning", + "CCD": "CC Damage Waiver", + "CCDR": "CC Daily Rate", + "CCF": "CC Refuel", + "CCM": "CC Mileage", + "PAA": "Aftermarket", + "PAC": "Rechromed", + "PAE": "Existing", + "PAG": "Glass", + "PAL": "LKQ", + "PAM": "Remanufactured", + "PAN": "New/OEM", + "PAO": "Other", + "PAP": "OEM Partial", + "PAR": "Recored", + "PAS": "Sublet", + "PASL": "Sublet (L)" + }, + "profitcenter_labor": "Profit Center: Labor", + "profitcenter_part": "Profit Center: Part", + "prt_dsmk_m": "Line Discount/Markup $", + "prt_dsmk_p": "Line Discount/Markup %", + "status": "Status", + "tax_part": "Tax Part", + "total": "Total", + "unq_seq": "Seq #" + }, + "labels": { + "adjustmenttobeadded": "Adjustment to be added: {{adjustment}}", + "billref": "Latest Bill", + "convertedtolabor": "This line has been converted to labor. Ensure you adjust the profit center for the amount accordingly.", + "edit": "Edit Line", + "ioucreated": "IOU", + "new": "New Line", + "nostatus": "No Status", + "presets": "Jobline Presets" + }, + "successes": { + "created": "Job line created successfully.", + "saved": "Job line saved.", + "updated": "Job line updated successfully." + }, + "validations": { + "ahdetailonlyonuserdefinedtypes": "Detail line indicator can only be set for LA1, LA2, LA3, LA4, and LAU labor types.", + "hrsrequirediflbrtyp": "Labor hours are required if a labor type is selected. Clear the labor type if there are no labor hours.", + "requiredifparttype": "Required if a part type has been specified.", + "zeropriceexistingpart": "This line cannot have any price since it uses an existing part." + } + }, + "jobs": { + "actions": { + "addDocuments": "Add Job Documents", + "addNote": "Add Note", + "addtopartsqueue": "Add to Parts Queue", + "addtoproduction": "Add to Production", + "addtoscoreboard": "Add to Scoreboard", + "allocate": "Allocate", + "autoallocate": "Auto Allocate", + "changefilehandler": "Change Adjuster", + "changelaborrate": "Change Labor Rate", + "changestatus": "Change Status", + "changestimator": "Change Estimator", + "convert": "Convert", + "createiou": "Create IOU", + "deliver": "Deliver", + "dms": { + "addpayer": "Add Payer", + "createnewcustomer": "Create New Customer", + "findmakemodelcode": "Find Make/Model Code", + "getmakes": "Get Makes", + "labels": { + "refreshallocations": "Refresh this component to see the DMS allocations." + }, + "post": "Post", + "refetchmakesmodels": "Refetch Make and Model Codes", + "usegeneric": "Use Generic Customer", + "useselected": "Use Selected Customer" + }, + "dmsautoallocate": "DMS Auto Allocate", + "export": "Export", + "exportcustdata": "Export Customer Data", + "exportselected": "Export Selected", + "filterpartsonly": "Filter Parts Only", + "generatecsi": "Generate CSI & Copy Link", + "gotojob": "Go to Job", + "intake": "Intake", + "manualnew": "Create New Job Manually", + "mark": "Mark", + "markasexported": "Mark as Exported", + "markpstexempt": "Mark Job PST Exempt", + "markpstexemptconfirm": "Are you sure you want to do this? To undo this, you must manually update all PST rates.", + "postbills": "Post Bills", + "printCenter": "Print Center", + "recalculate": "Recalculate", + "reconcile": "Reconcile", + "removefromproduction": "Remove from Production", + "schedule": "Schedule", + "sendcsi": "Send CSI", + "sendpartspricechange": "Send Parts Price Change", + "sendtodms": "Send to DMS", + "sync": "Sync", + "taxprofileoverride": "Override Tax Profile with Shop Configuration", + "taxprofileoverride_confirm": "Are you sure you want to override the tax profile information? This cannot be undone without re-importing the job. ", + "uninvoice": "Uninvoice", + "unvoid": "Unvoid Job", + "viewchecklist": "View Checklists", + "viewdetail": "View Details" + }, + "errors": { + "addingtoproduction": "Error adding to production. {{error}}", + "cannotintake": "Intake cannot be completed for this Job. It has either already been completed or the job is already here.", + "closing": "Error closing Job. {{error}}", + "creating": "Error encountered while creating job. {{error}}", + "deleted": "Error deleting Job. {{error}}", + "exporting": "Error exporting Job. {{error}}", + "exporting-partner": "Unable to connect to partner application. Please ensure it is running and logged in.", + "invoicing": "Error invoicing Job. {{error}}", + "noaccess": "This Job does not exist or you do not have access to it.", + "nodamage": "No damage points on estimate.", + "nodates": "No dates specified for this Job.", + "nofinancial": "No financial data has been calculated yet for this job. Please save it again.", + "nojobselected": "No Job is selected.", + "noowner": "No owner associated.", + "novehicle": "No vehicle associated.", + "partspricechange": "Error sending parts price change. {{error}}.", + "saving": "Error encountered while saving record.", + "scanimport": "Error importing Job. {{message}}", + "totalscalc": "Error while calculating new Job totals.", + "updating": "Error while updating Job(s). {{error}}", + "validation": "Please ensure all fields are entered correctly.", + "validationtitle": "Validation Error", + "voiding": "Error voiding Job. {{error}}" + }, + "fields": { + "active_tasks": "Active Tasks", + "actual_completion": "Actual Completion", + "actual_delivery": "Actual Delivery", + "actual_in": "Actual In", + "adjustment_bottom_line": "Adjustments", + "adjustmenthours": "Adjustment Hours", + "alt_transport": "Alt. Trans.", + "area_of_damage_impact": { + "10": "Left Front Side", + "11": "Left Front Corner", + "12": "Front", + "13": "Rollover", + "14": "Unknown", + "15": "Total Loss", + "16": "Non-collision", + "25": "Hood", + "26": "Deck-lid", + "27": "Roof", + "28": "Undercarriage", + "34": "All Over", + "01": "Right Front Corner", + "02": "Right Front Side", + "03": "Right Side", + "04": "Right Rear Side", + "05": "Right Rear Corner", + "06": "Rear", + "07": "Left Rear Corner", + "08": "Left Rear Side", + "09": "Left Side" + }, + "auto_add_ats": "Automatically Add/Update ATS", + "ca_bc_pvrt": "PVRT", + "ca_customer_gst": "Customer Portion of GST", + "ca_gst_registrant": "GST Registrant", + "category": "Category", + "ccc": "CC Cleaning", + "ccd": "CC Damage Waiver", + "ccdr": "CC Daily Rate", + "ccf": "CC Refuel", + "ccm": "CC Mileage", + "cieca_id": "CIECA ID", + "cieca_pfl": { + "lbr_adjp": "Labor Adjustment", + "lbr_tax_in": "Tax Labor Indicator", + "lbr_taxp": "Labor Tax Rate", + "lbr_tx_in1": "Tax 1 Indicator", + "lbr_tx_in2": "Tax 2 Indicator", + "lbr_tx_in3": "Tax 3 Indicator", + "lbr_tx_in4": "Tax 4 Indicator", + "lbr_tx_in5": "Tax 5 Indicator" + }, + "cieca_pfo": { + "stor_t_in1": "Storage Tax 1 Indicator", + "stor_t_in2": "Storage Tax 2 Indicator", + "stor_t_in3": "Storage Tax 3 Indicator", + "stor_t_in4": "Storage Tax 4 Indicator", + "stor_t_in5": "Storage Tax 5 Indicator", + "tow_t_in1": "Tow Tax 1 Indicator", + "tow_t_in2": "Tow Tax 2 Indicator", + "tow_t_in3": "Tow Tax 3 Indicator", + "tow_t_in4": "Tow Tax 4 Indicator", + "tow_t_in5": "Tow Tax 5 Indicator" + }, + "claim_total": "Claim Total", + "class": "Class", + "clm_no": "Claim #", + "clm_total": "Claim Total", + "comment": "Comment", + "customerowing": "Customer Owing", + "date_estimated": "Date Estimated", + "date_exported": "Exported", + "date_invoiced": "Invoiced", + "date_last_contacted": "Last Contacted Date", + "date_lost_sale": "Lost Sale", + "date_next_contact": "Next Contact Date", + "date_open": "Open", + "date_rentalresp": "Shop Rental Responsibility Start", + "date_repairstarted": "Repairs Started", + "date_scheduled": "Scheduled", + "date_towin": "Towed In", + "date_void": "Void", + "ded_amt": "Deductible", + "ded_note": "Deductible Note", + "ded_status": "Deductible Status", + "depreciation_taxes": "Betterment/Depreciation/Taxes", + "dms": { + "address": "Customer Address", + "amount": "Amount", + "center": "Center", + "control_type": { + "account_number": "Account Number" + }, + "cost": "Cost", + "cost_dms_acctnumber": "Cost DMS Acct #", + "dms_make": "DMS Make", + "dms_model": "DMS Model", + "dms_model_override": "Override DMS Make/Model", + "dms_unsold": "New, Unsold Vehicle", + "dms_wip_acctnumber": "Cost WIP DMS Acct #", + "id": "DMS ID", + "inservicedate": "In Service Date", + "journal": "Journal #", + "lines": "Posting Lines", + "name1": "Customer Name", + "payer": { + "amount": "Amount", + "control_type": "Control Type", + "controlnumber": "Control Number", + "dms_acctnumber": "DMS Account #", + "name": "Payer Name" + }, + "sale": "Sale", + "sale_dms_acctnumber": "Sale DMS Acct #", + "story": "Story", + "vinowner": "VIN Owner" + }, + "dms_allocation": "DMS Allocation", + "driveable": "Driveable", + "employee_body": "Body", + "employee_csr": "Customer Service Rep.", + "employee_csr_writer": "Writer", + "employee_prep": "Prep", + "employee_refinish": "Refinish", + "est_addr1": "Estimator Address", + "est_co_nm": "Estimator Company", + "est_ct_fn": "Estimator First Name", + "est_ct_ln": "Estimator Last Name", + "est_ea": "Estimator Email", + "est_ph1": "Estimator Phone #", + "federal_tax_payable": "Federal Tax Payable", + "federal_tax_rate": "Federal Tax Rate", + "ins_addr1": "Insurance Co. Address", + "ins_city": "Insurance Co. City", + "ins_co_id": "Insurance Co. ID", + "ins_co_nm": "Insurance Company Name", + "ins_co_nm_short": "Ins. Co.", + "ins_ct_fn": "Adjuster First Name", + "ins_ct_ln": "Adjuster Last Name", + "ins_ea": "Adjuster Email", + "ins_ph1": "Adjuster Phone #", + "intake": { + "label": "Label", + "max": "Maximum", + "min": "Minimum", + "name": "Name", + "required": "Required?", + "type": "Type" + }, + "invoice_final_note": "Note to Display on Final Invoice", + "kmin": "Mileage In", + "kmout": "Mileage Out", + "la1": "LA1", + "la2": "LA2", + "la3": "LA3", + "la4": "LA4", + "laa": "Aluminum ", + "lab": "Body", + "labor_rate_desc": "Labor Rate Name", + "lad": "Diagnostic", + "lae": "Electrical", + "laf": "Frame", + "lag": "Glass", + "lam": "Mechanical", + "lar": "Refinish", + "las": "Structural", + "lau": "User Defined", + "local_tax_rate": "Local Tax Rate", + "loss_date": "Loss Date", + "loss_desc": "Loss Description", + "loss_of_use": "Loss of Use", + "lost_sale_reason": "Lost Sale Reason", + "ma2s": "2 Stage Paint", + "ma3s": "3 Stage Pain", + "mabl": "MABL?", + "macs": "MACS?", + "mahw": "Hazardous Waste", + "mapa": "Paint Materials", + "mash": "Shop Materials", + "matd": "Tire Disposal", + "materials": { + "MAPA": "Paint Materials", + "MASH": "Shop Materials", + "cal_maxdlr": "Threshhold", + "cal_opcode": "OP Codes", + "mat_adjp": "Material Adjustment", + "mat_taxp": "Material Tax Rate", + "mat_tx_in1": "Tax 1 Indicator", + "mat_tx_in2": "Tax 2 Indicator", + "mat_tx_in3": "Tax 3 Indicator", + "mat_tx_in4": "Tax 4 Indicator", + "mat_tx_in5": "Tax 5 Indicator", + "materials": "Profile - Materials", + "tax_ind": "Tax Indicator" + }, + "other_amount_payable": "Other Amount Payable", + "owner": "Owner", + "owner_owing": "Cust. Owes", + "ownr_ea": "Email", + "ownr_ph1": "Phone 1", + "ownr_ph2": "Phone 2", + "paa": "Aftermarket", + "pac": "Rechromed", + "pae": "Existing", + "pag": "Glass", + "pal": "LKQ", + "pam": "Remanufactured", + "pan": "OEM/New", + "pao": "Other", + "pap": "OEM Partial", + "par": "Re-cored", + "parts_tax_rates": { + "prt_discp": "Discount %", + "prt_mktyp": "Markup Type", + "prt_mkupp": "Markup %", + "prt_tax_in": "Tax Indicator", + "prt_tax_rt": "Part Tax Rate", + "prt_tx_in1": "Tax 1 Indicator", + "prt_tx_in2": "Tax 2 Indicator", + "prt_tx_in3": "Tax 3 Indicator", + "prt_tx_in4": "Tax 4 Indicator", + "prt_tx_in5": "Tax 5 Indicator", + "prt_tx_ty1": "Parts Tax Type 1", + "prt_type": "Part Type" + }, + "partsstatus": "Parts Status", + "pas": "Sublet", + "pay_date": "Pay Date", + "phoneshort": "PH", + "po_number": "PO Number", + "policy_no": "Policy #", + "ponumber": "PO Number", + "production_vars": { + "note": "Production Note" + }, + "qb_multiple_payers": { + "amount": "Amount", + "name": "Name" + }, + "queued_for_parts": "Queued for Parts", + "rate_ats": "ATS Rate", + "rate_la1": "LA1", + "rate_la2": "LA2", + "rate_la3": "LA3", + "rate_la4": "LA4", + "rate_laa": "Aluminum", + "rate_lab": "Body", + "rate_lad": "Diagnostic", + "rate_lae": "Electrical", + "rate_laf": "Frame", + "rate_lag": "Glass", + "rate_lam": "Mechanical", + "rate_lar": "Refinish", + "rate_las": "Structural", + "rate_lau": "User Defined", + "rate_ma2s": "2 Stage Paint", + "rate_ma3s": "3 Stage Paint", + "rate_mabl": "MABL??", + "rate_macs": "MACS??", + "rate_mahw": "Hazardous Waste", + "rate_mapa": "Paint Materials", + "rate_mash": "Shop Material", + "rate_matd": "Tire Disposal", + "referral_source_extra": "Other Referral Source", + "referral_source_other": "", + "referralsource": "Referral Source", + "regie_number": "Registration #", + "repairtotal": "Repair Total", + "ro_number": "RO #", + "scheduled_completion": "Scheduled Completion", + "scheduled_delivery": "Scheduled Delivery", + "scheduled_in": "Scheduled In", + "selling_dealer": "Selling Dealer", + "selling_dealer_contact": "Selling Dealer Contact", + "servicecar": "Service Car", + "servicing_dealer": "Servicing Dealer", + "servicing_dealer_contact": "Servicing Dealer Contact", + "special_coverage_policy": "Special Coverage Policy", + "specialcoveragepolicy": "Special Coverage Policy", + "state_tax_rate": "State Tax Rate", + "status": "Job Status", + "storage_payable": "Storage", + "tax_lbr_rt": "Labor Tax Rate", + "tax_levies_rt": "Levies Tax Rate", + "tax_paint_mat_rt": "Paint Material Tax Rate", + "tax_registration_number": "Tax Registration Number", + "tax_shop_mat_rt": "Shop Material Tax Rate", + "tax_str_rt": "Storage Tax Rate", + "tax_sub_rt": "Sublet Tax Rate", + "tax_tow_rt": "Towing Tax Rate", + "towin": "Tow In", + "towing_payable": "Towing Payable", + "unitnumber": "Unit #", + "updated_at": "Updated At", + "uploaded_by": "Uploaded By", + "vehicle": "Vehicle" + }, + "forms": { + "admindates": "Administrative Dates", + "appraiserinfo": "Estimator Info", + "claiminfo": "Claim Information", + "estdates": "Estimate Dates", + "laborrates": "Labor Rates", + "lossinfo": "Loss Information", + "other": "Other", + "repairdates": "Repair Dates", + "scheddates": "Schedule Dates" + }, + "labels": { + "accountsreceivable": "Accounts Receivable", + "act_price_ppc": "New Part Price", + "actual_completion_inferred": "$t(jobs.fields.actual_completion) inferred using $t(jobs.fields.scheduled_completion).", + "actual_delivery_inferred": "$t(jobs.fields.actual_delivery) inferred using $t(jobs.fields.scheduled_delivery).", + "actual_in_inferred": "$t(jobs.fields.actual_in) inferred using $t(jobs.fields.scheduled_in).", + "additionalpayeroverallocation": "You have allocated more than the sale of the Job to additional payers.", + "additionaltotal": "Additional Total", + "adjustmentrate": "Adjustment Rate", + "adjustments": "Adjustments", + "adminwarning": "Use the functionality on this page at your own risk. You are responsible for any and all changes to your data.", + "allocations": "Allocations", + "alreadyaddedtoscoreboard": "Job has already been added to scoreboard. Saving will update the previous entry.", + "alreadyclosed": "This Job has already been closed.", + "appointmentconfirmation": "Send confirmation to customer?", + "associationwarning": "Any changes to associations will require updating the data from the new parent record to the Job.", + "audit": "Audit Trail", + "available": "Available", + "availablejobs": "Available Jobs", + "ca_bc_pvrt": { + "days": "Days", + "rate": "PVRT Rate" + }, + "ca_gst_all_if_null": "If the Job is marked as a \"GST Registrant\" and this value is set to $0, the customer will be responsible for paying all of the GST by default. ", + "calc_repair_days": "Calculated Repair Days", + "calc_repair_days_tt": "This is the approximate number of days required to complete the repair according to the target touch time in your shop configuration (current set to {{target_touchtime}}).", + "calc_scheuled_completion": "Calculate Scheduled Completion", + "cards": { + "customer": "Customer Information", + "damage": "Area of Damage", + "dates": "Dates", + "documents": "Recent Documents", + "estimator": "Estimator", + "filehandler": "Adjuster", + "insurance": "Insurance Details", + "more": "More", + "notes": "Notes", + "parts": "Parts", + "totals": "Totals", + "vehicle": "Vehicle" + }, + "changeclass": "Changing the Job's class can have fundamental impacts to already exported accounting items. Are you sure you want to do this?", + "checklistcompletedby": "Checklist completed by {{by}} at {{at}}", + "checklistdocuments": "Checklist Documents", + "checklists": "Checklists", + "cieca_pfl": "Profile - Labor", + "cieca_pfo": "Profile - Other", + "cieca_pft": "Profile - Taxes", + "closeconfirm": "Are you sure you want to close this job? This cannot be easily undone.", + "closejob": "Close Job {{ro_number}}", + "closingperiod": "This Invoice Date is outside of the Closing Period.", + "contracts": "CC Contracts", + "convertedtolabor": "Labor Line Adjustments", + "cost": "Cost", + "cost_Additional": "Cost - Additional", + "cost_labor": "Cost - Labor", + "cost_parts": "Cost - Parts", + "cost_sublet": "Cost - Sublet", + "costs": "Costs", + "create": { + "jobinfo": "Job Info", + "newowner": "Create a new Owner instead. ", + "newvehicle": "Create a new Vehicle Instead", + "novehicle": "No vehicle (only for ROs to track parts/labor only work).", + "ownerinfo": "Owner Info", + "vehicleinfo": "Vehicle Info" + }, + "createiouwarning": "Are you sure you want to create an IOU for these lines? A new RO will be created based on those lines for this customer.", + "creating_new_job": "Creating new Job...", + "deductible": { + "stands": "Stands", + "waived": "Waived" + }, + "deleteconfirm": "Are you sure you want to delete this Job? This cannot be undone. ", + "deletedelivery": "Delete Delivery Checklist", + "deleteintake": "Delete Intake Checklist", + "deliverchecklist": "Deliver Checklist", + "difference": "Difference", + "diskscan": "Scan Disk for Estimates", + "dms": { + "apexported": "AP export completed. See logs for details.", + "damageto": "Damage to $t(jobs.fields.area_of_damage_impact.{{area_of_damage}}).", + "defaultstory": "B/S RO: {{ro_number}}. Owner: {{ownr_nm}}. Insurance Co: {{ins_co_nm}}. Claim/PO #: {{clm_po}}", + "disablebillwip": "Cost and WIP for bills has been ignored per shop configuration.", + "invoicedatefuture": "Invoice date must be today or in the future for CDK posting.", + "kmoutnotgreaterthankmin": "Mileage out must be greater than mileage in.", + "logs": "Logs", + "notallocated": "Not Allocated", + "postingform": "Posting Form", + "totalallocated": "Total Amount Allocated" + }, + "documents": "Documents", + "documents-images": "Images", + "documents-other": "Other Documents", + "duplicateconfirm": "Are you sure you want to duplicate this Job? Some elements of this Job will not be duplicated.", + "emailaudit": "Email Audit Trail", + "employeeassignments": "Employee Assignments", + "estimatelines": "Estimate Lines", + "estimator": "Estimator", + "existing_jobs": "Existing Jobs", + "federal_tax_amt": "Federal Taxes", + "gpdollars": "$ G.P.", + "gppercent": "% G.P.", + "hrs_claimed": "Hours Flagged", + "hrs_total": "Hours Total", + "importnote": "The Job was initially imported.", + "inproduction": "In Production", + "intakechecklist": "Intake Checklist", + "iou": "IOU", + "job": "Job Details", + "jobcosting": "Job Costing", + "jobtotals": "Job Totals", + "labor_hrs": "B/P/T Hrs", + "labor_rates_subtotal": "Labor Rates Subtotal", + "laborallocations": "Labor Allocations", + "labortotals": "Labor Totals", + "lines": "Estimate Lines", + "local_tax_amt": "Local Taxes", + "mapa": "Paint Materials", + "markforreexport": "Mark for Re-export", + "mash": "Shop Materials", + "masterbypass": "Master Bypass Password", + "materials": { + "mapa": "" + }, + "missingprofileinfo": "This job has missing tax profile info. To ensure correct totals calculations, re-import the job.", + "multipayers": "Additional Payers", + "net_repairs": "Net Repairs", + "notes": "Notes", + "othertotal": "Other Totals", + "outstanding_ar": "A balance is outstanding on this RO. Payments can still be entered when the job is closed. ", + "outstanding_credit_memos": "Outstanding credit memos have not been entered against this job. Credit Memos may still be posted once the job is closed.", + "outstanding_ppd": "There are outstanding PPDs that may not have been synced back to the estimate.", + "outstanding_reconciliation_discrep": "At least one discrepancy is not $0. This may indicate that this job is not properly reconciled and should not be closed.", + "outstanding_sublets": "There are sublet lines on the job which have not been marked as completed. ", + "outstandinghours": "There are outstanding hours on the job that have not been paid or have been overpaid.", + "override_header": "Override estimate header on import?", + "ownerassociation": "Owner Association", + "parts": "Parts", + "parts_lines": "Parts Lines", + "parts_received": "Parts Rec.", + "parts_tax_rates": "Parts Tax rates", + "partsfilter": "Parts Only", + "partssubletstotal": "Parts & Sublets Total", + "partstotal": "Parts Total (ex. Taxes)", + "performance": "Performance", + "pimraryamountpayable": "Total Primary Payable", + "plitooltips": { + "billtotal": "The total amount of all bill lines that have been posted against this RO (not including credits, taxes, or labor adjustments).", + "calculatedcreditsnotreceived": "The calculated credits not received is derived by subtracting the amount of credit memos entered from the retail total of returns created. This does not take into account whether the credit was marked as received. You can find more information here.", + "creditmemos": "The total retail amount of all returns created. This amount does not reflect credit memos that have been posted.", + "creditsnotreceived": "This total reflects the total retail of parts returns lines that have not been explicitly marked as returned when posting a credit memo. You can learn more about this here here. ", + "discrep1": "If the discrepancy is not $0, you may have one of the following:

\n\n
    \n
  • Too many bills/bill lines that have been posted against this RO. Check to make sure every bill posted on this RO is correctly posted and assigned.
  • \n
  • You do not have the latest supplement imported, or, a supplement must be submitted and then imported.
  • \n
  • You have posted a bill line to labor.
  • \n
\n
\nThere may be additional issues not listed above that prevent this Job from reconciling.", + "discrep2": "If the discrepancy is not $0, you may have one of the following:

\n\n
    \n
  • Used an incorrect rate when deducting from labor.
  • \n
  • An outstanding imbalance higher in the reconciliation process.
  • \n
\n
\nThere may be additional issues not listed above that prevent this Job from reconciling.", + "discrep3": "If the discrepancy is not $0, you may have one of the following:

\n\n
    \n
  • A parts order return has not been created.
  • \n
  • An outstanding imbalance higher in the reconciliation process.
  • \n
\n
\nThere may be additional issues not listed above that prevent this Job from reconciling.", + "laboradj": "The sum of all bill lines that deducted from labor hours, rather than part prices.", + "partstotal": "This is the total of all parts and sublet amounts on the vehicle (some of these may require an in-house invoice).
\nItems such as shop and paint materials, labor online lines, etc. are not included in this total.", + "totalreturns": "The total retail amount of returns created for this job." + }, + "ppc": "This line contains a part price change.", + "ppdnotexported": "PPDs not Exported", + "profileadjustments": "Profile Disc./Mkup", + "profitbypassrequired": "Minimum gross profit requirements have not been met.", + "profits": "Job Profits", + "prt_dsmk_total": "Line Item Adjustment", + "rates": "Rates", + "rates_subtotal": "All Rates Subtotal", + "reconciliation": { + "billlinestotal": "Bill Lines Total", + "byassoc": "By Line Association", + "byprice": "By Price", + "clear": "Clear All", + "discrepancy": "Discrepancy", + "joblinestotal": "Job Lines Total", + "multipleactprices": "${{act_price}} is the price for multiple job lines.", + "multiplebilllines": "{{line_desc}} has 2 or more bill lines associated to it.", + "multiplebillsforactprice": "Found more than 1 bill matching ${{act_price}} retail price.", + "removedpartsstrikethrough": "Strike through lines represent parts that have been removed from the estimate. They are included for completeness of reconciliation." + }, + "reconciliationheader": "Parts & Sublet Reconciliation", + "relatedros": "Related ROs", + "remove_from_ar": "Remove from AR", + "returntotals": "Return Totals", + "ro_guard": { + "enforce_ar": "AR collection enforced.", + "enforce_bills": "Bill discrepancy enforced.", + "enforce_cm": "Credit memo entry enforced.", + "enforce_labor": "Labor allocations enforced.", + "enforce_ppd": "PPD sync enforced.", + "enforce_profit": "Profit marginsenforced.", + "enforce_sublet": "Sublet completion enforced.", + "enforce_validation": "Master Bypass Required: {{message}}", + "enforced": "This check has been enforced by your shop manager. Enter the master bypass password to close the Job." + }, + "roguard": "RO Guard", + "roguardwarnings": "RO Guard Warnings", + "rosaletotal": "RO Parts Total", + "sale_additional": "Sales - Additional", + "sale_labor": "Sales - Labor", + "sale_parts": "Sales - Parts", + "sale_sublet": "Sales - Sublet", + "sales": "Sales", + "savebeforeconversion": "You have unsaved changes on the Job. Please save them before converting it. ", + "scheduledinchange": "The scheduled in is based off the latest appointment. To change this date, please schedule or reschedule the Job. ", + "specialcoveragepolicy": "Special Coverage Policy Applies", + "state_tax_amt": "Provincial/State Taxes", + "subletsnotcompleted": "Outstanding Sublets", + "subletstotal": "Sublets Total", + "subtotal": "Subtotal", + "supplementnote": "The Job had a supplement imported.", + "suspended": "SUSPENDED", + "suspense": "Suspense", + "tasks": "Tasks", + "threshhold": "Max Threshold: ${{amount}}", + "total_cost": "Total Cost", + "total_cust_payable": "Total Customer Amount Payable", + "total_repairs": "Total Repairs", + "total_sales": "Total Sales", + "total_sales_tax": "Total Sales Tax", + "totals": "Totals", + "unvoidnote": "This Job was unvoided.", + "update_scheduled_completion": "Update Scheduled Completion?", + "vehicle_info": "Vehicle", + "vehicleassociation": "Vehicle Association", + "viewallocations": "View Allocations", + "voidjob": "Are you sure you want to void this Job? This cannot be easily undone. ", + "voidnote": "This Job was voided." + }, + "successes": { + "addedtoproduction": "Job added to production board.", + "all_deleted": "{{count}} Jobs deleted successfully.", + "closed": "Job closed successfully.", + "converted": "Job converted successfully.", + "created": "Job created successfully. Click to view.", + "creatednoclick": "Job created successfully. ", + "delete": "Job deleted successfully.", + "deleted": "Job deleted successfully.", + "duplicated": "Job duplicated successfully. ", + "exported": "Job(s) exported successfully. ", + "invoiced": "Job closed and invoiced successfully.", + "ioucreated": "IOU created successfully. Click to see.", + "partsqueue": "Job added to parts queue.", + "save": "Job saved successfully.", + "savetitle": "Record saved successfully.", + "supplemented": "Job supplemented successfully. ", + "updated": "Job(s) updated successfully.", + "voided": "Job voided successfully." + } + }, + "landing": { + "bigfeature": { + "subtitle": "Rome Online is built using world class technology by experts in the collision repair industry. This translates to software that is tailor made for the unique challenges faced by repair facilities with no compromises. ", + "title": "Bringing the latest technology to the automotive repair industry. " + }, + "footer": { + "company": { + "about": "About Us", + "contact": "Contact", + "disclaimers": "Disclaimers", + "name": "Company", + "privacypolicy": "Privacy Policy" + }, + "io": { + "help": "Help", + "name": "Rome Online", + "status": "System Status" + }, + "slogan": "Rome Technologies. is a technology leader in the collision repair industry. We specialize in creating collision repair management systems and bodyshop management systems that lower cycle times and promote efficiency." + }, + "hero": { + "button": "Learn More", + "title": "Shop management reimagined." + }, + "labels": { + "features": "Features", + "managemyshop": "Sign In", + "pricing": "Pricing" + }, + "pricing": { + "basic": { + "name": "Basic", + "sub": "Best suited for shops looking to increase their volume." + }, + "essentials": { + "name": "Essentials", + "sub": "Best suited for small and low volume shops." + }, + "pricingtitle": "Features", + "pro": { + "name": "Pro", + "sub": "Empower your shop with the tools to operate at peak capacity." + }, + "title": "Features", + "unlimited": { + "name": "Unlimited", + "sub": "Everything you need and more for the high volume shop." + } + } + }, + "menus": { + "currentuser": { + "languageselector": "Language", + "profile": "Profile" + }, + "header": { + "accounting": "Accounting", + "accounting-payables": "Payables", + "accounting-payments": "Payments", + "accounting-receivables": "Receivables", + "activejobs": "Active Jobs", + "all_tasks": "All Tasks", + "alljobs": "All Jobs", + "allpayments": "All Payments", + "availablejobs": "Available Jobs", + "bills": "Bills", + "courtesycars": "Courtesy Cars", + "courtesycars-all": "All Courtesy Cars", + "courtesycars-contracts": "Contracts", + "courtesycars-newcontract": "New Contract", + "create_task": "Create Task", + "customers": "Customers", + "dashboard": "Dashboard", + "enterbills": "Enter Bills", + "entercardpayment": "New Card Charge", + "enterpayment": "Enter Payments", + "entertimeticket": "Enter Time Tickets", + "export": "Export", + "export-logs": "Export Logs", + "help": "Help", + "home": "Home", + "inventory": "Inventory", + "jobs": "Jobs", + "my_tasks": "My Tasks", + "newjob": "Create New Job", + "owners": "Owners", + "parts-queue": "Parts Queue", + "phonebook": "Phonebook", + "productionboard": "Production Board - Visual", + "productionlist": "Production Board - List", + "readyjobs": "Ready Jobs", + "recent": "Recent Items", + "reportcenter": "Report Center", + "rescueme": "Rescue me!", + "schedule": "Schedule", + "scoreboard": "Scoreboard", + "search": { + "bills": "Bills", + "jobs": "Jobs", + "owners": "Owners", + "payments": "Payments", + "phonebook": "Phonebook", + "vehicles": "Vehicles" + }, + "shiftclock": "Shift Clock", + "shop": "My Shop", + "shop_config": "Configuration", + "shop_csi": "CSI", + "shop_templates": "Templates", + "shop_vendors": "Vendors", + "tasks": "Tasks", + "temporarydocs": "Temporary Documents", + "timetickets": "Time Tickets", + "ttapprovals": "Time Ticket Approvals", + "vehicles": "Vehicles" + }, + "jobsactions": { + "admin": "Admin", + "cancelallappointments": "Cancel all appointments", + "closejob": "Close Job", + "deletejob": "Delete Job", + "duplicate": "Duplicate this Job", + "duplicatenolines": "Duplicate this Job without Repair Data", + "newcccontract": "Create Courtesy Car Contract", + "void": "Void Job" + }, + "jobsdetail": { + "claimdetail": "Claim Details", + "dates": "Dates", + "financials": "Financial Information", + "general": "General", + "insurance": "Insurance Information", + "labor": "Labor", + "lifecycle": "Lifecycle", + "parts": "Parts", + "partssublet": "Parts & Bills", + "rates": "Rates", + "repairdata": "Repair Data", + "totals": "Totals" + }, + "profilesidebar": { + "profile": "My Profile", + "shops": "My Shops" + }, + "tech": { + "assignedjobs": "Assigned Jobs", + "claimtask": "Flag Hours", + "dispatchedparts": "Dispatched Parts", + "home": "Home", + "jobclockin": "Job Clock In", + "jobclockout": "Job Clock Out", + "joblookup": "Job Lookup", + "login": "Login", + "logout": "Logout", + "productionboard": "Production Visual", + "productionlist": "Production List", + "shiftclockin": "Shift Clock" + } + }, + "messaging": { + "actions": { + "link": "Link to Job", + "new": "New Conversation" + }, + "errors": { + "invalidphone": "The phone number is invalid. Unable to open conversation. ", + "noattachedjobs": "No Jobs have been associated to this conversation. ", + "updatinglabel": "Error updating label. {{error}}" + }, + "labels": { + "addlabel": "Add a label to this conversation.", + "archive": "Archive", + "maxtenimages": "You can only select up to a maximum of 10 images at a time.", + "messaging": "Messaging", + "noallowtxt": "This customer has not indicated their permission to be messaged.", + "nojobs": "Not associated to any Job.", + "nopush": "Polling Mode Enabled", + "phonenumber": "Phone #", + "presets": "Presets", + "recentonly": "Only your most recent 50 conversations will be shown here. If you are looking for an older conversation, find the related contact and click their phone number to view the conversation.", + "selectmedia": "Select Media", + "sentby": "Sent by {{by}} at {{time}}", + "typeamessage": "Send a message...", + "unarchive": "Unarchive" + }, + "render": { + "conversation_list": "Conversation List" + } + }, + "notes": { + "actions": { + "actions": "Actions", + "deletenote": "Delete Note", + "edit": "Edit Note", + "new": "New Note", + "savetojobnotes": "Save to Job Notes" + }, + "errors": { + "inserting": "Error inserting note. {{error}}" + }, + "fields": { + "createdby": "Created By", + "critical": "Critical", + "private": "Private", + "text": "Contents", + "type": "Type", + "types": { + "customer": "Customer", + "general": "General", + "office": "Office", + "paint": "Paint", + "parts": "Parts", + "shop": "Shop", + "supplement": "Supplement" + }, + "updatedat": "Updated At" + }, + "labels": { + "addtorelatedro": "Add to Related ROs", + "newnoteplaceholder": "Add a note...", + "notetoadd": "Note to Add", + "systemnotes": "System Notes", + "usernotes": "User Notes" + }, + "successes": { + "create": "Note created successfully.", + "deleted": "Note deleted successfully.", + "updated": "Note updated successfully." + } + }, + "owner": { + "labels": { + "noownerinfo": "No owner information." + } + }, + "owners": { + "actions": { + "update": "Update Selected Records" + }, + "errors": { + "deleting": "Error deleting owner. {{error}}.", + "noaccess": "The record does not exist or you do not have access to it. ", + "saving": "Error saving owner. {{error}}.", + "selectexistingornew": "Select an existing owner record or create a new one. " + }, + "fields": { + "address": "Address", + "allow_text_message": "Permission to Text?", + "name": "Name", + "note": "Owner Note", + "ownr_addr1": "Address", + "ownr_addr2": "Address 2", + "ownr_city": "City", + "ownr_co_nm": "Owner Co. Name", + "ownr_ctry": "Country", + "ownr_ea": "Email", + "ownr_fn": "First Name", + "ownr_ln": "Last Name", + "ownr_ph1": "Phone 1", + "ownr_ph2": "Phone 2", + "ownr_st": "Province/State", + "ownr_title": "Title", + "ownr_zip": "Zip/Postal Code", + "preferred_contact": "Preferred Contact Method", + "tax_number": "Tax Number" + }, + "forms": { + "address": "Address", + "contact": "Contact Information", + "name": "Owner Details" + }, + "labels": { + "create_new": "Create a new owner record.", + "deleteconfirm": "Are you sure you want to delete this owner? This cannot be undone.", + "existing_owners": "Existing Owners", + "fromclaim": "Current Claim", + "fromowner": "Historical Owner Record", + "relatedjobs": "Related Jobs", + "updateowner": "Update Owner" + }, + "successes": { + "delete": "Owner deleted successfully.", + "save": "Owner saved successfully." + } + }, + "parts": { + "actions": { + "order": "Order Parts", + "orderinhouse": "Order as In House" + } + }, + "parts_dispatch": { + "actions": { + "accept": "Accept" + }, + "errors": { + "accepting": "Error accepting parts dispatch. {{error}}", + "creating": "Error dispatching parts. {{error}}" + }, + "fields": { + "number": "Number", + "percent_accepted": "% Accepted" + }, + "labels": { + "notyetdispatched": "This part has not been dispatched.", + "parts_dispatch": "Parts Dispatch" + } + }, + "parts_dispatch_lines": { + "fields": { + "accepted_at": "Accepted At" + } + }, + "parts_orders": { + "actions": { + "backordered": "Mark Backordered", + "receive": "Receive", + "receivebill": "Receive Bill" + }, + "errors": { + "associatedbills": "This parts order cannot", + "backordering": "Error backordering part {{message}}.", + "creating": "Error encountered when creating parts order. ", + "oec": "Error creating EMS files for parts order. {{error}}", + "saving": "Error saving parts order. {{error}}.", + "updating": "Error updating parts order/parts order line. {{error}}." + }, + "fields": { + "act_price": "Price", + "backordered_eta": "B.O. ETA", + "backordered_on": "B.O. On", + "cm_received": "CM Received?", + "comments": "Comments", + "cost": "Cost", + "db_price": "List Price", + "deliver_by": "Deliver By", + "job_line_id": "Job Line Id", + "line_desc": "Line Description", + "line_remarks": "Remarks", + "lineremarks": "Line Remarks", + "oem_partno": "Part #", + "order_date": "Order Date", + "order_number": "Order Number", + "orderedby": "Ordered By", + "part_type": "Type", + "quantity": "Qty.", + "return": "Return", + "status": "Status" + }, + "labels": { + "allpartsto": "All Parts Location", + "confirmdelete": "Are you sure you want to delete this item? It cannot be recovered. Job line statuses will not be updated and may require manual review. ", + "custompercent": "Custom %", + "discount": "Discount {{percent}}", + "email": "Send by Email", + "inthisorder": "Parts in this Order", + "is_quote": "Parts Quote?", + "mark_as_received": "Mark as Received?", + "newpartsorder": "New Parts Order", + "notyetordered": "This part has not yet been ordered.", + "oec": "Order via EMS", + "order_type": "Order Type", + "orderhistory": "Order History", + "parts_order": "Parts Order", + "parts_orders": "Parts Orders", + "print": "Show Printed Form", + "receive": "Receive Parts Order", + "removefrompartsqueue": "Unqueue from Parts Queue?", + "returnpartsorder": "Return Parts Order", + "sublet_order": "Sublet Order" + }, + "successes": { + "created": "Parts order created successfully. ", + "line_updated": "Parts return line updated.", + "received": "Parts order received.", + "return_created": "Parts return created successfully." + } + }, + "payments": { + "actions": { + "generatepaymentlink": "Generate Payment Link" + }, + "errors": { + "exporting": "Error exporting payment(s). {{error}}", + "exporting-partner": "Error exporting to partner. Please check the partner interaction log for more errors.", + "inserting": "Error inserting payment. {{error}}" + }, + "fields": { + "amount": "Amount", + "created_at": "Created At", + "date": "Payment Date", + "exportedat": "Exported At", + "memo": "Memo", + "payer": "Payer", + "paymentnum": "Payment Number", + "stripeid": "Stripe ID", + "transactionid": "Transaction ID", + "type": "Type" + }, + "labels": { + "balance": "Balance", + "ca_bc_etf_table": "ICBC EFT Table Converter", + "customer": "Customer", + "edit": "Edit Payment", + "electronicpayment": "Use Electronic Payment Processing?", + "external": "External", + "findermodal": "ICBC Payment Finder", + "insurance": "Insurance", + "markexported": "Mark Exported", + "markforreexport": "Mark for Re-export", + "new": "New Payment", + "signup": "Please contact support to sign up for electronic payments.", + "smspaymentreminder": "This is {{shopname}} reminding you about your balance of {{amount}}. To pay, click the following link {{payment_link}}.", + "title": "Payments", + "totalpayments": "Total Payments" + }, + "successes": { + "exported": "Payment(s) exported successfully.", + "markexported": "Payment(s) marked exported.", + "markreexported": "Payment marked for re-export successfully", + "payment": "Payment created successfully. ", + "paymentupdate": "Payment updated successfully. ", + "stripe": "Credit card transaction charged successfully." + } + }, + "phonebook": { + "actions": { + "new": "New Phonebook Entry" + }, + "errors": { + "adding": "Error adding phonebook entry. {{error}}", + "saving": "Error saving phonebook entry. {{error}}" + }, + "fields": { + "address1": "Street 1", + "address2": "Street 2", + "category": "Category", + "city": "City", + "company": "Company", + "country": "Country", + "email": "Email", + "fax": "Fax", + "firstname": "First Name", + "lastname": "Last Name", + "phone1": "Phone 1", + "phone2": "Phone 2", + "state": "Province/State" + }, + "labels": { + "noneselected": "No phone book entry selected. ", + "onenamerequired": "At least one name related field is required.", + "vendorcategory": "Vendor" + }, + "successes": { + "added": "Phonebook entry added successfully. ", + "deleted": "Phonebook entry deleted successfully. ", + "saved": "Phonebook entry saved successfully. " + } + }, + "printcenter": { + "appointments": { + "appointment_confirmation": "Appointment Confirmation" + }, + "bills": { + "inhouse_invoice": "In House Invoice" + }, + "courtesycarcontract": { + "courtesy_car_contract": "Courtesy Car Contract", + "courtesy_car_impound": "Impound Charges", + "courtesy_car_inventory": "Courtesy Car Inventory", + "courtesy_car_terms": "Courtesy Car Terms" + }, + "errors": { + "nocontexttype": "No context type set." + }, + "jobs": { + "3rdpartyfields": { + "addr1": "Address 1", + "addr2": "Address 2", + "addr3": "Address 3", + "attn": "Attention", + "city": "City", + "custgst": "Customer Portion of GST", + "ded_amt": "Deductible", + "depreciation": "Depreciation", + "other": "Other", + "ponumber": "PO Number", + "refnumber": "Reference Number", + "sendtype": "Send by", + "state": "Province/State", + "zip": "Postal Code/Zip" + }, + "3rdpartypayer": "Invoice to Third Party Payer", + "ab_proof_of_loss": "AB - Proof of Loss", + "appointment_confirmation": "Appointment Confirmation", + "appointment_reminder": "Appointment Reminder", + "casl_authorization": "CASL Authorization", + "committed_timetickets_ro": "Committed Time Tickets", + "coversheet_landscape": "Coversheet (Landscape)", + "coversheet_portrait": "Coversheet Portrait", + "csi_invitation": "CSI Invitation", + "csi_invitation_action": "CSI Invite", + "diagnostic_authorization": "Diagnostic Authorization", + "dms_posting_sheet": "DMS Posting Sheet", + "envelope_return_address": "#10 Envelope Return Address Label", + "estimate": "Estimate Only", + "estimate_detail": "Estimate Details", + "estimate_followup": "Estimate Followup", + "express_repair_checklist": "Express Repair Checklist", + "filing_coversheet_landscape": "Filing Coversheet (Landscape)", + "filing_coversheet_portrait": "Filing Coversheet (Portrait)", + "final_invoice": "Final Invoice", + "fippa_authorization": "FIPPA Authorization", + "folder_label_multiple": "Folder Label - Multi", + "glass_express_checklist": "Glass Express Checklist", + "guarantee": "Repair Guarantee", + "individual_job_note": "RO Job Note", + "invoice_customer_payable": "Invoice (Customer Payable)", + "invoice_total_payable": "Invoice (Total Payable)", + "iou_form": "IOU Form", + "job_costing_ro": "Job Costing", + "job_lifecycle_ro": "Job Lifecycle", + "job_notes": "Job Notes", + "job_tasks": "Job Tasks", + "key_tag": "Key Tag", + "labels": { + "count": "Count", + "labels": "Labels", + "position": "Starting Position" + }, + "lag_time_ro": "Lag Time", + "mechanical_authorization": "Mechanical Authorization", + "mpi_animal_checklist": "MPI - Animal Checklist", + "mpi_eglass_auth": "MPI - eGlass Auth", + "mpi_final_acct_sheet": "MPI - Final Accounting Sheet (Direct Repair)", + "mpi_final_repair_acct_sheet": "MPI - Final Accounting Sheet", + "paint_grid": "Paint Grid", + "parts_dispatch": "Parts Dispatch", + "parts_invoice_label_single": "Parts Label Single", + "parts_label_multiple": "Parts Label - Multi", + "parts_label_single": "Parts Label - Single", + "parts_list": "Parts List", + "parts_order": "Parts Order Confirmation", + "parts_order_confirmation": "", + "parts_order_history": "Parts Order History", + "parts_return_slip": "Parts Return Slip", + "payment_receipt": "Payment Receipt", + "payment_request": "Payment Request", + "payments_by_job": "Job Payments", + "purchases_by_ro_detail": "Purchases - Detail", + "purchases_by_ro_summary": "Purchases - Summary", + "qc_sheet": "Quality Control Sheet", + "rental_reservation": "Rental Reservation", + "ro_totals": "RO Totals", + "ro_with_description": "RO Summary with Descriptions", + "sgi_certificate_of_repairs": "SGI - Certificate of Repairs", + "sgi_windshield_auth": "SGI - Windshield Authorization", + "stolen_recovery_checklist": "Stolen Recovery Checklist", + "sublet_order": "Sublet Order", + "supplement_request": "Supplement Request", + "thank_you_ro": "Thank You Letter", + "thirdpartypayer": "Third Party Payer", + "timetickets_ro": "Time Tickets", + "vehicle_check_in": "Vehicle Intake", + "vehicle_delivery_check": "Vehicle Delivery Checklist", + "window_tag": "Window Tag", + "window_tag_sublet": "Window Tag - Sublet", + "work_authorization": "Work Authorization", + "worksheet_by_line_number": "Worksheet by Line Number", + "worksheet_sorted_by_operation": "Worksheet by Operation", + "worksheet_sorted_by_operation_no_hours": "Worksheet by Operation (No Hours)", + "worksheet_sorted_by_operation_part_type": "Worksheet by Operation & Part Type", + "worksheet_sorted_by_operation_type": "Worksheet by Operation Type", + "worksheet_sorted_by_team": "Worksheet by Team" + }, + "labels": { + "groups": { + "authorization": "Authorization", + "financial": "Financial", + "post": "Post-Production", + "pre": "Pre-Production", + "ro": "Repair Order", + "worksheet": "Worksheets" + }, + "misc": "Miscellaneous Documents", + "repairorder": "Repair Order Related", + "reportcentermodal": "Report Center", + "speedprint": "Speed Print", + "title": "Print Center" + }, + "payments": { + "ca_bc_etf_table": "ICBC EFT Table", + "exported_payroll": "Payroll Table" + }, + "special": { + "attendance_detail_csv": "Attendance Table" + }, + "subjects": { + "jobs": { + "individual_job_note": "Job Note RO: {{ro_number}}", + "parts_dispatch": "Parts Dispatch RO: {{ro_number}}", + "parts_order": "Parts Order PO: {{ro_number}} - {{name}}", + "parts_return_slip": "Parts Return PO: {{ro_number}} - {{name}}", + "sublet_order": "Sublet Order PO: {{ro_number}} - {{name}}" + } + }, + "vendors": { + "purchases_by_vendor_detailed": "Purchases by Vendor - Detailed", + "purchases_by_vendor_summary": "Purchases by Vendor - Summary" + } + }, + "production": { + "actions": { + "addcolumns": "Add Columns", + "bodypriority-clear": "Clear Body Priority", + "bodypriority-set": "Set Body Priority", + "detailpriority-clear": "Clear Detail Priority", + "detailpriority-set": "Set Detail Priority", + "paintpriority-clear": "Clear Paint Priority", + "paintpriority-set": "Set Paint Priority", + "remove": "Remove from Production", + "removecolumn": "Remove Column", + "saveconfig": "Save Configuration", + "suspend": "Suspend", + "unsuspend": "Unsuspend" + }, + "constants": { + "main_profile": "Default" + }, + "errors": { + "boardupdate": "Error encountered updating Job. {{message}}", + "name_exists": "A Profile with this name already exists. Please choose a different name.", + "name_required": "Profile name is required.", + "removing": "Error removing from production board. {{error}}", + "settings": "Error saving board settings: {{error}}" + }, + "labels": { + "actual_in": "Actual In", + "addnewprofile": "Add New Profile", + "alert": "Alert", + "alertoff": "Remove alert from Job", + "alerton": "Add alert to Job", + "alerts": "Alerts", + "ats": "Alternative Transportation", + "bodyhours": "B", + "bodypriority": "B/P", + "bodyshop": { + "labels": { + "qbo_departmentid": "QBO Department ID", + "qbo_usa": "QBO USA" + } + }, + "card_size": "Card Size", + "cardcolor": "Colored Cards", + "cardsettings": "Card Settings", + "clm_no": "Claim Number", + "comment": "Comment", + "compact": "Compact Cards", + "detailpriority": "D/P", + "employeeassignments": "Employee Assignments", + "employeesearch": "Employee Search", + "estimator": "Estimator", + "horizontal": "Horizontal", + "ins_co_nm": "Insurance Company Name", + "jobdetail": "Job Details", + "kiosk_mode": "Kiosk Mode", + "laborhrs": "Labor Hours", + "legend": "Legend:", + "model_info": "Vehicle Info", + "note": "Production Note", + "off": "Off", + "on": "On", + "orientation": "Board Orientation", + "ownr_nm": "Customer Name", + "paintpriority": "P/P", + "partsstatus": "Parts Status", + "production_note": "Production Note", + "refinishhours": "R", + "scheduled_completion": "Scheduled Completion", + "selectview": "Select a View", + "stickyheader": "Sticky Header (BETA)", + "sublets": "Sublets", + "subtotal": "Subtotal", + "tall": "Tall", + "tasks": "Tasks", + "totalhours": "Total Hrs ", + "touchtime": "T/T", + "vertical": "Vertical", + "viewname": "View Name", + "wide": "Wide" + }, + "options": { + "horizontal": "Horizontal", + "large": "Large", + "medium": "Medium", + "small": "Small", + "vertical": "Vertical" + }, + "settings": { + "board_settings": "Board Settings", + "filters": { + "md_estimators": "Estimators", + "md_ins_cos": "Insurance Companies" + }, + "filters_title": "Filters", + "information": "Information", + "layout": "Layout", + "statistics": { + "jobs_in_production": "Jobs in Production", + "tasks_in_production": "Tasks in Production", + "tasks_in_view": "Tasks in View", + "tasks_on_board": "Tasks on Board", + "total_amount_in_production": "Dollars in Production", + "total_amount_in_view": "Dollars in View", + "total_amount_on_board": "Dollars on Board", + "total_hours_in_production": "Hours in Production", + "total_hours_in_view": "Hours in View", + "total_hours_on_board": "Hours on Board", + "total_jobs_in_view": "Jobs in View", + "total_jobs_on_board": "Jobs on Board", + "total_lab_in_production": "Body Hours in Production", + "total_lab_in_view": "Body Hours in View", + "total_lab_on_board": "Body Hours on Board", + "total_lar_in_production": "Refinish Hours in Production", + "total_lar_in_view": "Refinish Hours in View", + "total_lar_on_board": "Refinish Hours on Board" + }, + "statistics_title": "Statistics" + }, + "statistics": { + "currency_symbol": "$", + "hours": "Hours", + "jobs": "Jobs", + "jobs_in_production": "Jobs in Production", + "tasks": "Tasks", + "tasks_in_production": "Tasks in Production", + "tasks_in_view": "Tasks in View", + "tasks_on_board": "Tasks on Board", + "total_amount_in_production": "Dollars in Production", + "total_amount_in_view": "Dollars in View", + "total_amount_on_board": "Dollars on Board", + "total_hours_in_production": "Hours in Production", + "total_hours_in_view": "Hours in View", + "total_hours_on_board": "Hours on Board", + "total_jobs_in_view": "Jobs in View", + "total_jobs_on_board": "Jobs on Board", + "total_lab_in_production": "Body Hours in Production", + "total_lab_in_view": "Body Hours in View", + "total_lab_on_board": "Body Hours on Board", + "total_lar_in_production": "Refinish Hours in Production", + "total_lar_in_view": "Refinish Hours in View", + "total_lar_on_board": "Refinish Hours on Board" + }, + "successes": { + "removed": "Job removed from production." + } + }, + "profile": { + "errors": { + "state": "Error reading page state. Please refresh." + }, + "labels": { + "activeshop": "Active Shop" + }, + "successes": { + "updated": "Profile updated successfully." + } + }, + "reportcenter": { + "actions": { + "generate": "Generate" + }, + "labels": { + "advanced_filters": "Advanced Filters and Sorters", + "advanced_filters_false": "False", + "advanced_filters_filter_field": "Field", + "advanced_filters_filter_operator": "Operator", + "advanced_filters_filter_value": "Value", + "advanced_filters_filters": "Filters", + "advanced_filters_hide": "Hide", + "advanced_filters_show": "Show", + "advanced_filters_sorter_direction": "Direction", + "advanced_filters_sorter_field": "Field", + "advanced_filters_sorters": "Sorters", + "advanced_filters_true": "True", + "dates": "Dates", + "employee": "Employee", + "filterson": "Filters on {{object}}: {{field}}", + "generateasemail": "Generate as Email?", + "groups": { + "customers": "Customers", + "jobs": "Jobs & Costing", + "payroll": "Payroll", + "purchases": "Purchases", + "sales": "Sales" + }, + "key": "Report", + "objects": { + "appointments": "Appointments", + "bills": "Bills", + "csi": "CSI", + "exportlogs": "Export Logs", + "jobs": "Jobs", + "parts_orders": "Parts Orders", + "payments": "Payments", + "scoreboard": "Scoreboard", + "tasks": "Tasks", + "timetickets": "Timetickets" + }, + "vendor": "Vendor" + }, + "templates": { + "adp_payroll_flat": "ADP Payroll - Flat Rate", + "adp_payroll_straight": "ADP Payroll - Straight Time", + "anticipated_revenue": "Anticipated Revenue", + "ar_aging": "AR Aging", + "attendance_detail": "Attendance (All Employees)", + "attendance_employee": "Employee Attendance", + "attendance_summary": "Attendance Summary (All Employees)", + "committed_timetickets": "Committed Time Tickets", + "committed_timetickets_employee": "Committed Employee Time Tickets", + "committed_timetickets_summary": "Committed Time Tickets Summary", + "credits_not_received_date": "Credits not Received by Date", + "credits_not_received_date_vendorid": "Credits not Received by Vendor", + "csi": "CSI Responses", + "customer_list": "Customer List", + "cycle_time_analysis": "Cycle Time Analysis", + "estimates_written_converted": "Estimates Written/Converted", + "estimator_detail": "Jobs by Estimator (Detail)", + "estimator_summary": "Jobs by Estimator (Summary)", + "export_payables": "Export Log - Payables", + "export_payments": "Export Log - Payments", + "export_receivables": "Export Log - Receivables", + "exported_gsr_by_ro": "Exported Gross Sales - Excel", + "exported_gsr_by_ro_labor": "Exported Gross Sales (Labor) - Excel", + "gsr_by_atp": "", + "gsr_by_ats": "Gross Sales by ATS", + "gsr_by_category": "Gross Sales by Category", + "gsr_by_csr": "Gross Sales by CSR", + "gsr_by_delivery_date": "Gross Sales by Delivery Date", + "gsr_by_estimator": "Gross Sales by Estimator", + "gsr_by_exported_date": "Exported Gross Sales", + "gsr_by_ins_co": "Gross Sales by Insurance Company", + "gsr_by_make": "Gross Sales by Vehicle Make", + "gsr_by_referral": "Gross Sales by Referral Source", + "gsr_by_ro": "Gross Sales by RO", + "gsr_labor_only": "Gross Sales - Labor Only", + "hours_sold_detail_closed": "Hours Sold Detail - Closed", + "hours_sold_detail_closed_csr": "Hours Sold Detail - Closed by CSR", + "hours_sold_detail_closed_estimator": "Hours Sold Detail - Closed by Estimator", + "hours_sold_detail_closed_ins_co": "Hours Sold Detail - Closed by Source", + "hours_sold_detail_closed_status": "Hours Sold Detail - Closed by Status", + "hours_sold_detail_open": "Hours Sold Detail - Open", + "hours_sold_detail_open_csr": "Hours Sold Detail - Open by CSR", + "hours_sold_detail_open_estimator": "Hours Sold Detail - Open by Estimator", + "hours_sold_detail_open_ins_co": "Hours Sold Detail - Open by Source", + "hours_sold_detail_open_status": "Hours Sold Detail - Open by Status", + "hours_sold_summary_closed": "Hours Sold Summary - Closed", + "hours_sold_summary_closed_csr": "Hours Sold Summary - Closed by CSR", + "hours_sold_summary_closed_estimator": "Hours Sold Summary - Closed by Estimator", + "hours_sold_summary_closed_ins_co": "Hours Sold Summary - Closed by Source", + "hours_sold_summary_closed_status": "Hours Sold Summary - Closed by Status", + "hours_sold_summary_open": "Hours Sold Summary - Open", + "hours_sold_summary_open_csr": "Hours Sold Summary - Open CSR", + "hours_sold_summary_open_estimator": "Hours Sold Summary - Open Estimator", + "hours_sold_summary_open_ins_co": "Hours Sold Summary - Open by Source", + "hours_sold_summary_open_status": "Hours Sold Summary - Open by Status", + "job_costing_ro_csr": "Job Costing by CSR", + "job_costing_ro_date_detail": "Job Costing by RO - Detail", + "job_costing_ro_date_summary": "Job Costing by RO - Summary", + "job_costing_ro_estimator": "Job Costing by Estimator", + "job_costing_ro_ins_co": "Job Costing by RO Source", + "job_lifecycle_date_detail": "Job Lifecycle by Date - Detail", + "job_lifecycle_date_summary": "Job Lifecycle by Date - Summary", + "jobs_completed_not_invoiced": "Jobs Completed not Invoiced", + "jobs_invoiced_not_exported": "Jobs Invoiced not Exported", + "jobs_reconcile": "Parts/Sublet/Labor Reconciliation", + "jobs_scheduled_completion": "Jobs Scheduled Completion", + "lag_time": "Lag Time", + "load_level": "Load Level", + "lost_sales": "Lost Sales", + "open_orders": "Open Orders by Date", + "open_orders_csr": "Open Orders by CSR", + "open_orders_estimator": "Open Orders by Estimator", + "open_orders_excel": "Open Orders - Excel", + "open_orders_ins_co": "Open Orders by Insurance Company", + "open_orders_referral": "Open Orders by Referral Source", + "open_orders_specific_csr": "Open Orders filtered by CSR", + "open_orders_status": "Open Orders by Status", + "parts_backorder": "IOU Parts List", + "parts_not_recieved": "Parts Not Received", + "parts_not_recieved_vendor": "Parts Not Received by Vendor", + "parts_received_not_scheduled": "Parts Received for Jobs Not Scheduled", + "payments_by_date": "Payments by Date", + "payments_by_date_payment": "Payments by Date and Payment Type", + "payments_by_date_type": "Payments by Date and Customer Type", + "production_by_category": "Production by Category", + "production_by_category_one": "Production filtered by Category", + "production_by_csr": "Production by CSR", + "production_by_last_name": "Production by Last Name", + "production_by_repair_status": "Production by Status", + "production_by_repair_status_one": "Production filtered by Status", + "production_by_ro": "Production by RO", + "production_by_target_date": "Production by Target Date", + "production_by_technician": "Production by Technician", + "production_by_technician_one": "Production filtered by Technician", + "production_not_production_status": "Production not in Production Status", + "production_over_time": "Production Level over Time", + "psr_by_make": "Percent of Sales by Vehicle Make", + "purchase_return_ratio_grouped_by_vendor_detail": "Purchase & Return Ratio by Vendor (Detail)", + "purchase_return_ratio_grouped_by_vendor_summary": "Purchase & Return Ratio by Vendor (Summary)", + "purchases_by_cost_center_detail": "Purchases by Cost Center (Detail)", + "purchases_by_cost_center_summary": "Purchases by Cost Center (Summary)", + "purchases_by_date_range_detail": "Purchases by Date - Detail", + "purchases_by_date_range_summary": "Purchases by Date - Summary", + "purchases_by_ro_detail_date": "Purchases by RO - Detail", + "purchases_by_ro_summary_date": "Purchases by RO - Summary", + "purchases_by_vendor_detailed_date_range": "Purchases By Vendor - Detailed", + "purchases_by_vendor_summary_date_range": "Purchases by Vendor - Summary", + "purchases_grouped_by_vendor_detailed": "Purchases Grouped by Vendor - Detailed", + "purchases_grouped_by_vendor_summary": "Purchases Grouped by Vendor - Summary", + "returns_grouped_by_vendor_detailed": "Returns Grouped by Vendor - Detailed", + "returns_grouped_by_vendor_summary": "Returns Grouped by Vendor - Summary", + "schedule": "Appointment Schedule", + "scheduled_parts_list": "Parts for Jobs Scheduled In", + "scoreboard_detail": "Scoreboard Detail", + "scoreboard_summary": "Scoreboard Summary", + "supplement_ratio_ins_co": "Supplement Ratio by Source", + "tasks_date": "Tasks by Date", + "tasks_date_employee": "Employee Tasks by Date", + "thank_you_date": "Thank You Letters", + "timetickets": "Time Tickets", + "timetickets_employee": "Employee Time Tickets", + "timetickets_summary": "Time Tickets Summary", + "unclaimed_hrs": "Unflagged Hours", + "void_ros": "Void ROs", + "work_in_progress_committed_labour": "Work in Progress - Committed Labor", + "work_in_progress_jobs": "Work in Progress - Jobs", + "work_in_progress_labour": "Work in Progress - Labor", + "work_in_progress_payables": "Work in Progress - Payables" + } + }, + "schedule": { + "labels": { + "atssummary": "ATS Summary", + "employeevacation": "Employee Vacations", + "estimators": "Filter by Writer/Customer Rep.", + "ins_co_nm_filter": "Filter by Insurance Company", + "intake": "Intake Events", + "manual": "Manual Events", + "manualevent": "Add Manual Event" + } + }, + "scoreboard": { + "actions": { + "edit": "Edit" + }, + "errors": { + "adding": "Error adding Job to Scoreboard. {{message}}", + "removing": "Error removing Job from Scoreboard. {{message}}", + "updating": "Error updating Scoreboard. {{message}}" + }, + "fields": { + "bodyhrs": "Body Hours", + "date": "Date", + "painthrs": "Paint Hours" + }, + "labels": { + "allemployeetimetickets": "All Employee Time Tickets", + "asoftodaytarget": "As of Today", + "body": "Body", + "bodyabbrev": "B", + "bodycharttitle": "Body Targets vs Actual", + "calendarperiod": "Periods based on calendar weeks/months.", + "combinedcharttitle": "Combined Targets vs Actual", + "dailyactual": "Actual (D)", + "dailytarget": "Daily", + "efficiencyoverperiod": "Efficiency over Selected Dates", + "entries": "Scoreboard Entries", + "jobs": "Jobs", + "jobscompletednotinvoiced": "Completed Not Invoiced", + "lastmonth": "Last Month", + "lastweek": "Last Week", + "monthlytarget": "Monthly", + "priorweek": "Prior Week", + "productivestatistics": "Productive Hours Statistics", + "productivetimeticketsoverdate": "Productive Hours over Selected Dates", + "refinish": "Refinish", + "refinishabbrev": "R", + "refinishcharttitle": "Refinish Targets vs Actual", + "targets": "Targets", + "thismonth": "This Month", + "thisweek": "This Week", + "timetickets": "Time Tickets", + "timeticketsemployee": "Time Tickets by Employee", + "todateactual": "Actual (MTD)", + "total": "Total", + "totalhrs": "Total Hours", + "totaloverperiod": "Total over Selected Dates", + "weeklyactual": "Actual (W)", + "weeklytarget": "Weekly", + "workingdays": "Working Days / Month" + }, + "successes": { + "added": "Job added to scoreboard.", + "removed": "Job removed from scoreboard.", + "updated": "Scoreboard updated." + } + }, + "tasks": { + "actions": { + "edit": "Edit Task", + "new": "New Task" + }, + "buttons": { + "allTasks": "All", + "complete": "Toggle Complete", + "create": "Create Task", + "delete": "Toggle Delete", + "edit": "Edit", + "myTasks": "Mine", + "refresh": "Refresh" + }, + "date_presets": { + "completion": "Completion", + "day": "Day", + "days": "Days", + "delivery": "Delivery", + "next_week": "Next Week", + "one_month": "One Month", + "three_months": "Three Months", + "three_weeks": "Three Weeks", + "today": "Today", + "tomorrow": "Tomorrow", + "two_weeks": "Two Weeks" + }, + "failures": { + "completed": "Failed to toggle Task completion.", + "created": "Failed to create Task.", + "deleted": "Failed to toggle Task deletion.", + "updated": "Failed to update Task." + }, + "fields": { + "actions": "Actions", + "assigned_to": "Assigned To", + "bill": "Bill", + "billid": "Bill", + "completed": "Completed", + "created_at": "Created At", + "created_by": "Created By", + "description": "Description", + "due_date": "Due Date", + "job": { + "ro_number": "RO #" + }, + "jobid": "Job", + "jobline": "Job Line", + "joblineid": "Job Line", + "parts_order": "Parts Order", + "partsorderid": "Parts Order", + "priorities": { + "high": "High", + "low": "Low", + "medium": "Medium" + }, + "priority": "Priority", + "related_items": "Related Items", + "remind_at": "Remind At", + "title": "Title" + }, + "placeholders": { + "assigned_to": "Select an Employee", + "billid": "Select a Bill", + "description": "Enter a description", + "jobid": "Select a Job", + "joblineid": "Select a Job Line", + "partsorderid": "Select a Parts Order" + }, + "successes": { + "completed": "Toggled Task completion successfully.", + "created": "Task created successfully.", + "deleted": "Toggled Task deletion successfully.", + "updated": "Task updated successfully." + }, + "titles": { + "all_tasks": "All Tasks", + "completed": "Completed Tasks", + "deleted": "Deleted Tasks", + "job_tasks": "Job Tasks", + "mine": "My Tasks", + "my_tasks": "My Tasks" + }, + "validation": { + "due_at_error_message": "The due date must be in the future!", + "remind_at_error_message": "The reminder date and time must be at least 30 minutes in the future!" + } + }, + "tech": { + "fields": { + "employeeid": "Employee ID", + "pin": "PIN" + }, + "labels": { + "loggedin": "Logged in as {{name}}", + "notloggedin": "Not logged in." + } + }, + "templates": { + "errors": { + "updating": "Error updating template {{error}}." + }, + "successes": { + "updated": "Template updated successfully." + } + }, + "timetickets": { + "actions": { + "claimtasks": "Flag Hours", + "clockin": "Clock In", + "clockout": "Clock Out", + "commit": "Commit Tickets ({{count}})", + "commitone": "Commit", + "enter": "Enter New Time Ticket", + "payall": "Pay All", + "printemployee": "Print Time Tickets", + "uncommit": "Uncommit" + }, + "errors": { + "clockingin": "Error while clocking in. {{message}}", + "clockingout": "Error while clocking out. {{message}}", + "creating": "Error creating time ticket. {{message}}", + "deleting": "Error deleting time ticket. {{message}}", + "noemployeeforuser": "Unable to use Shift Clock", + "noemployeeforuser_sub": "An employee record has not been created for this user. Please create one before using the shift clock. ", + "payall": "Error flagging hours. {{error}}", + "shiftalreadyclockedon": "You are already clocked onto a shift. Unable to create shift entry." + }, + "fields": { + "actualhrs": "Actual Hours", + "ciecacode": "CIECA Code", + "clockhours": "Clock Hours", + "clockoff": "Clock Off", + "clockon": "Clocked In", + "committed": "Committed", + "committed_at": "Committed At", + "cost_center": "Cost Center", + "created_by": "Created By", + "date": "Ticket Date", + "efficiency": "Efficiency", + "employee": "Employee", + "employee_team": "Employee Team", + "flat_rate": "Flat Rate?", + "memo": "Memo", + "productivehrs": "Productive Hours", + "ro_number": "Job to Post Against", + "task_name": "Task" + }, + "labels": { + "alreadyclockedon": "You are already clocked in to the following Job(s):", + "ambreak": "AM Break", + "amshift": "AM Shift", + "claimtaskpreview": "Flagged Hours Preview", + "clockhours": "Shift Clock Hours Summary", + "clockintojob": "Clock In to Job", + "deleteconfirm": "Are you sure you want to delete this time ticket? This cannot be undone.", + "edit": "Edit Time Ticket", + "efficiency": "Efficiency", + "flat_rate": "Flat Rate", + "jobhours": "Job Related Time Tickets Summary", + "lunch": "Lunch", + "new": "New Time Ticket", + "payrollclaimedtasks": "These time tickets will be automatically entered to the system as a part of claiming this task. These numbers are calculated using the jobs assigned lines. If lines are unassigned, they will be excluded from created tickets.", + "pmbreak": "PM Break", + "pmshift": "PM Shift", + "shift": "Shift", + "shiftalreadyclockedon": "Active Shift Time Tickets", + "straight_time": "Straight Time", + "task": "Task", + "timetickets": "Time Tickets", + "unassigned": "Unassigned", + "zeroactualnegativeprod": "Actual hours must be 0 if entering negative productive hours." + }, + "successes": { + "clockedin": "Clocked in successfully.", + "clockedout": "Clocked out successfully.", + "committed": "Time Tickets Committed Successfully", + "created": "Time ticket entered successfully.", + "deleted": "Time ticket deleted successfully.", + "payall": "All hours paid out successfully." + }, + "validation": { + "clockoffmustbeafterclockon": "Clock off time must be the same or after clock in time.", + "clockoffwithoutclockon": "Clock off time cannot be set without a clock in time.", + "hoursenteredmorethanavailable": "The number of hours entered is more than what is available for this cost center.", + "unassignedlines": "There are currently {{unassignedHours}} hours of repair lines that are unassigned. These hours are not including in the above calculations and must be paid manually." + } + }, + "titles": { + "accounting-payables": "Payables | {{app}}", + "accounting-payments": "Payments | {{app}}", + "accounting-receivables": "Receivables | {{app}}", + "all_tasks": "All Tasks", + "app": "", + "bc": { + "accounting-payables": "Payables", + "accounting-payments": "Payments", + "accounting-receivables": "Receivables", + "all_tasks": "All Tasks", + "availablejobs": "Available Jobs", + "bills-list": "Bills", + "contracts": "Contracts", + "contracts-create": "New Contract", + "contracts-detail": "Contract #{{number}}", + "courtesycars": "Courtesy Cars", + "courtesycars-detail": "Courtesy Car {{number}}", + "courtesycars-new": "New Courtesy Car", + "dashboard": "Dashboard", + "dms": "DMS Export", + "export-logs": "Export Logs", + "inventory": "Inventory", + "jobs": "Jobs", + "jobs-active": "Active Jobs", + "jobs-admin": "Admin", + "jobs-all": "All Jobs", + "jobs-checklist": "Checklist", + "jobs-close": "Close Job", + "jobs-deliver": "Deliver Job", + "jobs-detail": "Job {{number}}", + "jobs-intake": "Intake", + "jobs-new": "Create a New Job", + "jobs-ready": "Ready Jobs", + "my_tasks": "My Tasks", + "owner-detail": "{{name}}", + "owners": "Owners", + "parts-queue": "Parts Queue", + "payments-all": "All Payments", + "phonebook": "Phonebook", + "productionboard": "Production Board - Visual", + "productionlist": "Production Board - List", + "profile": "My Profile", + "schedule": "Schedule", + "scoreboard": "Scoreboard", + "shop": "Manage my Shop ({{shopname}})", + "shop-csi": "CSI Responses", + "shop-templates": "Shop Templates", + "shop-vendors": "Vendors", + "tasks": "Tasks", + "temporarydocs": "Temporary Documents", + "timetickets": "Time Tickets", + "ttapprovals": "Time Ticket Approvals", + "vehicle-details": "Vehicle: {{vehicle}}", + "vehicles": "Vehicles" + }, + "bills-list": "Bills | {{app}}", + "contracts": "Courtesy Car Contracts | {{app}}", + "contracts-create": "New Contract | {{app}}", + "contracts-detail": "Contract {{id}} | {{app}}", + "courtesycars": "Courtesy Cars | {{app}}", + "courtesycars-create": "New Courtesy Car | {{app}}", + "courtesycars-detail": "Courtesy Car {{id}} | {{app}}", + "dashboard": "Dashboard | {{app}}", + "dms": "DMS Export | {{app}}", + "export-logs": "Export Logs | {{app}}", + "imexonline": "ImEX Online", + "inventory": "Inventory | {{app}}", + "jobs": "Active Jobs | {{app}}", + "jobs-admin": "Job {{ro_number}} - Admin | {{app}}", + "jobs-all": "All Jobs | {{app}}", + "jobs-checklist": "Job Checklist | {{app}}", + "jobs-close": "Close Job {{number}} | {{app}}", + "jobs-create": "Create a New Job | {{app}}", + "jobs-deliver": "Deliver Job | {{app}}", + "jobs-intake": "Intake | {{app}}", + "jobsavailable": "Available Jobs | {{app}}", + "jobsdetail": "Job {{ro_number}} | {{app}}", + "jobsdocuments": "Job Documents {{ro_number}} | {{app}}", + "manageroot": "Home | {{app}}", + "my_tasks": "My Tasks", + "owners": "All Owners | {{app}}", + "owners-detail": "{{name}} | {{app}}", + "parts-queue": "Parts Queue | {{app}}", + "payments-all": "Payments | {{app}}", + "phonebook": "Phonebook | {{app}}", + "productionboard": "Production Board - Visual | {{app}}", + "productionlist": "Production Board - List | {{app}}", + "profile": "My Profile | {{app}}", + "promanager": "ProManager", + "readyjobs": "Ready Jobs | {{app}}", + "resetpassword": "Reset Password", + "resetpasswordvalidate": "Enter New Password", + "romeonline": "Rome Online", + "schedule": "Schedule | {{app}}", + "scoreboard": "Scoreboard | {{app}}", + "shop": "My Shop | {{app}}", + "shop-csi": "CSI Responses | {{app}}", + "shop-templates": "Shop Templates | {{app}}", + "shop_vendors": "Vendors | {{app}}", + "tasks": "Tasks", + "techconsole": "Technician Console | {{app}}", + "techjobclock": "Technician Job Clock | {{app}}", + "techjoblookup": "Technician Job Lookup | {{app}}", + "techshiftclock": "Technician Shift Clock | {{app}}", + "temporarydocs": "Temporary Documents | {{app}}", + "timetickets": "Time Tickets | {{app}}", + "ttapprovals": "Time Ticket Approvals | {{app}}", + "vehicledetail": "Vehicle Details {{vehicle}} | {{app}}", + "vehicles": "All Vehicles | {{app}}" + }, + "trello": { + "labels": { + "add_card": "Add Card", + "add_lane": "Add Lane", + "cancel": "Cancel", + "delete_lane": "Delete Lane", + "description": "Description", + "label": "Label", + "lane_actions": "Lane Actions", + "title": "Title" + } + }, + "tt_approvals": { + "actions": { + "approveselected": "Approve Selected" + }, + "labels": { + "approval_queue_in_use": "Time tickets will be added to the approval queue.", + "calculate": "Calculate" + } + }, + "user": { + "actions": { + "changepassword": "Change Password", + "signout": "Sign Out", + "updateprofile": "Update Profile" + }, + "errors": { + "updating": "Error updating user or association {{message}}" + }, + "fields": { + "authlevel": "Authorization Level", + "displayname": "Display Name", + "email": "Email", + "photourl": "Avatar URL" + }, + "labels": { + "actions": "Actions", + "changepassword": "Change Password", + "profileinfo": "Profile Info" + }, + "successess": { + "passwordchanged": "Password changed successfully. " + } + }, + "users": { + "errors": { + "signinerror": { + "auth/user-disabled": "User account disabled. ", + "auth/user-not-found": "A user with this email does not exist.", + "auth/wrong-password": "The email and password combination you provided is incorrect." + } + } + }, + "vehicles": { + "errors": { + "deleting": "Error deleting vehicle. {{error}}.", + "noaccess": "The vehicle does not exist or you do not have access to it.", + "selectexistingornew": "Select an existing vehicle record or create a new one. ", + "validation": "Please ensure all fields are entered correctly.", + "validationtitle": "Validation Error" + }, + "fields": { + "description": "Vehicle Description", + "notes": "Vehicle Notes", + "plate_no": "License Plate", + "plate_st": "Plate Jurisdiction", + "trim_color": "Trim Color", + "v_bstyle": "Body Style", + "v_color": "Color", + "v_cond": "Condition", + "v_engine": "Engine", + "v_make_desc": "Make", + "v_makecode": "Make Code", + "v_mldgcode": "Molding Code", + "v_model_desc": "Model", + "v_model_yr": "Year", + "v_options": "Options", + "v_paint_codes": "Paint Codes {{number}}", + "v_prod_dt": "Production Date", + "v_stage": "Stage", + "v_tone": "Tone", + "v_trimcode": "Trim Code", + "v_type": "Type", + "v_vin": "V.I.N." + }, + "forms": { + "detail": "Vehicle Details", + "misc": "Miscellaneous", + "registration": "Registration" + }, + "labels": { + "deleteconfirm": "Are you sure you want to delete this vehicle? This cannot be undone.", + "fromvehicle": "Historical Vehicle Record", + "novehinfo": "No Vehicle Information", + "relatedjobs": "Related Jobs", + "updatevehicle": "Update Vehicle Information" + }, + "successes": { + "delete": "Vehicle deleted successfully.", + "save": "Vehicle saved successfully." + } + }, + "vendors": { + "actions": { + "addtophonebook": "Add to Phonebook", + "new": "New Vendor", + "newpreferredmake": "New Preferred Make" + }, + "errors": { + "deleting": "Error encountered while deleting vendor. ", + "saving": "Error encountered while saving vendor. " + }, + "fields": { + "active": "Active", + "am": "Aftermarket", + "city": "City", + "cost_center": "Cost Center", + "country": "Country", + "discount": "Discount % (as decimal)", + "display_name": "Display Name", + "dmsid": "DMS ID", + "due_date": "Payment Due Date (# of days)", + "email": "Contact Email", + "favorite": "Favorite?", + "lkq": "LKQ", + "make": "Make", + "name": "Vendor Name", + "oem": "OEM", + "phone": "Phone", + "prompt_discount": "Prompt Discount %", + "state": "Province/State", + "street1": "Street", + "street2": "Address 2", + "taxid": "Tax ID", + "terms": "Payment Terms", + "zip": "Zip/Postal Code" + }, + "labels": { + "noneselected": "No vendor is selected.", + "preferredmakes": "Preferred Makes for Vendor", + "search": "Type a Vendor's Name" + }, + "successes": { + "deleted": "Vendor deleted successfully. ", + "saved": "Vendor saved successfully." + }, + "validation": { + "unique_vendor_name": "You must enter a unique vendor name." + } + } + } } diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index ed7760e9d..6693e7e45 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -1,3601 +1,3603 @@ { - "translation": { - "allocations": { - "actions": { - "assign": "Asignar" - }, - "errors": { - "deleting": "", - "saving": "", - "validation": "" - }, - "fields": { - "employee": "Asignado a" - }, - "successes": { - "deleted": "", - "save": "" - } - }, - "appointments": { - "actions": { - "block": "", - "calculate": "", - "cancel": "Cancelar", - "intake": "Consumo", - "new": "Nueva cita", - "preview": "", - "reschedule": "Reprogramar", - "sendreminder": "", - "unblock": "", - "viewjob": "Ver trabajo" - }, - "errors": { - "blocking": "", - "canceling": "Error al cancelar la cita. {{message}}", - "saving": "Error al programar la cita. {{message}}" - }, - "fields": { - "alt_transport": "", - "color": "", - "end": "", - "note": "", - "start": "", - "time": "", - "title": "Título" - }, - "labels": { - "arrivedon": "Llegado el:", - "arrivingjobs": "", - "blocked": "", - "cancelledappointment": "Cita cancelada para:", - "completingjobs": "", - "dataconsistency": "", - "expectedjobs": "", - "expectedprodhrs": "", - "history": "", - "inproduction": "", - "manualevent": "", - "noarrivingjobs": "", - "nocompletingjobs": "", - "nodateselected": "No se ha seleccionado ninguna fecha.", - "priorappointments": "Nombramientos previos", - "reminder": "", - "scheduledfor": "Cita programada para:", - "severalerrorsfound": "", - "smartscheduling": "", - "smspaymentreminder": "", - "suggesteddates": "" - }, - "successes": { - "canceled": "Cita cancelada con éxito.", - "created": "Cita programada con éxito.", - "saved": "" - } - }, - "associations": { - "actions": { - "activate": "Activar" - }, - "fields": { - "active": "¿Activo?", - "shopname": "Nombre de tienda" - }, - "labels": { - "actions": "Comportamiento" - } - }, - "audit": { - "fields": { - "cc": "", - "contents": "", - "created": "", - "operation": "", - "status": "", - "subject": "", - "to": "", - "useremail": "", - "values": "" - } - }, - "audit_trail": { - "messages": { - "admin_job_remove_from_ar": "", - "admin_jobmarkexported": "", - "admin_jobmarkforreexport": "", - "admin_jobuninvoice": "", - "admin_jobunvoid": "", - "alerttoggle": "", - "appointmentcancel": "", - "appointmentinsert": "", - "assignedlinehours": "", - "billdeleted": "", - "billposted": "", - "billupdated": "", - "failedpayment": "", - "jobassignmentchange": "", - "jobassignmentremoved": "", - "jobchecklist": "", - "jobclosedwithbypass": "", - "jobconverted": "", - "jobdelivery": "", - "jobexported": "", - "jobfieldchanged": "", - "jobimported": "", - "jobinproductionchange": "", - "jobintake": "", - "jobinvoiced": "", - "jobioucreated": "", - "jobmodifylbradj": "", - "jobnoteadded": "", - "jobnotedeleted": "", - "jobnoteupdated": "", - "jobspartsorder": "", - "jobspartsreturn": "", - "jobstatuschange": "", - "jobsupplement": "", - "jobsuspend": "", - "jobvoid": "", - "tasks_completed": "", - "tasks_created": "", - "tasks_deleted": "", - "tasks_uncompleted": "", - "tasks_undeleted": "", - "tasks_updated": "" - } - }, - "billlines": { - "actions": { - "newline": "" - }, - "fields": { - "actual_cost": "", - "actual_price": "", - "cost_center": "", - "federal_tax_applicable": "", - "jobline": "", - "line_desc": "", - "local_tax_applicable": "", - "location": "", - "quantity": "", - "state_tax_applicable": "" - }, - "labels": { - "deductedfromlbr": "", - "entered": "", - "from": "", - "mod_lbr_adjustment": "", - "other": "", - "reconciled": "", - "unreconciled": "" - }, - "validation": { - "atleastone": "" - } - }, - "bills": { - "actions": { - "deductallhours": "", - "edit": "", - "receive": "", - "return": "" - }, - "errors": { - "creating": "", - "deleting": "", - "existinginventoryline": "", - "exporting": "", - "exporting-partner": "", - "invalidro": "", - "invalidvendor": "", - "validation": "" - }, - "fields": { - "allpartslocation": "", - "date": "", - "exported": "", - "federal_tax_rate": "", - "invoice_number": "", - "is_credit_memo": "", - "is_credit_memo_short": "", - "local_tax_rate": "", - "ro_number": "", - "state_tax_rate": "", - "total": "", - "vendor": "", - "vendorname": "" - }, - "labels": { - "actions": "", - "bill_lines": "", - "bill_total": "", - "billcmtotal": "", - "bills": "", - "calculatedcreditsnotreceived": "", - "creditsnotreceived": "", - "creditsreceived": "", - "dedfromlbr": "", - "deleteconfirm": "", - "discrepancy": "", - "discrepwithcms": "", - "discrepwithlbradj": "", - "editadjwarning": "", - "entered_total": "", - "enteringcreditmemo": "", - "federal_tax": "", - "federal_tax_exempt": "", - "generatepartslabel": "", - "iouexists": "", - "local_tax": "", - "markexported": "", - "markforreexport": "", - "new": "", - "nobilllines": "", - "noneselected": "", - "onlycmforinvoiced": "", - "printlabels": "", - "retailtotal": "", - "returnfrombill": "", - "savewithdiscrepancy": "", - "state_tax": "", - "subtotal": "", - "totalreturns": "" - }, - "successes": { - "created": "", - "deleted": "", - "exported": "", - "markexported": "", - "reexport": "" - }, - "validation": { - "closingperiod": "", - "inventoryquantity": "", - "manualinhouse": "", - "unique_invoice_number": "" - } - }, - "bodyshop": { - "actions": { - "add_task_preset": "", - "addapptcolor": "", - "addbucket": "", - "addpartslocation": "", - "addpartsrule": "", - "addspeedprint": "", - "addtemplate": "", - "newlaborrate": "", - "newsalestaxcode": "", - "newstatus": "", - "testrender": "" - }, - "errors": { - "creatingdefaultview": "", - "loading": "No se pueden cargar los detalles de la tienda. Por favor llame al soporte técnico.", - "saving": "" - }, - "fields": { - "ReceivableCustomField": "", - "address1": "", - "address2": "", - "appt_alt_transport": "", - "appt_colors": { - "color": "", - "label": "" - }, - "appt_length": "", - "attach_pdf_to_email": "", - "batchid": "", - "bill_allow_post_to_closed": "", - "bill_federal_tax_rate": "", - "bill_local_tax_rate": "", - "bill_state_tax_rate": "", - "city": "", - "closingperiod": "", - "companycode": "", - "country": "", - "dailybodytarget": "", - "dailypainttarget": "", - "default_adjustment_rate": "", - "deliver": { - "require_actual_delivery_date": "", - "templates": "" - }, - "dms": { - "apcontrol": "", - "appostingaccount": "", - "cashierid": "", - "default_journal": "", - "disablebillwip": "", - "disablecontactvehiclecreation": "", - "dms_acctnumber": "", - "dms_control_override": "", - "dms_wip_acctnumber": "", - "generic_customer_number": "", - "itc_federal": "", - "itc_local": "", - "itc_state": "", - "mappingname": "", - "sendmaterialscosting": "", - "srcco": "" - }, - "email": "", - "enforce_class": "", - "enforce_conversion_category": "", - "enforce_conversion_csr": "", - "enforce_referral": "", - "federal_tax_id": "", - "ignoreblockeddays": "", - "inhousevendorid": "", - "insurance_vendor_id": "", - "intake": { - "next_contact_hours": "", - "templates": "" - }, - "intellipay_config": { - "cash_discount_percentage": "", - "enable_cash_discount": "" - }, - "invoice_federal_tax_rate": "", - "invoice_local_tax_rate": "", - "invoice_state_tax_rate": "", - "jc_hourly_rates": { - "mapa": "", - "mash": "" - }, - "last_name_first": "", - "lastnumberworkingdays": "", - "localmediaserverhttp": "", - "localmediaservernetwork": "", - "localmediatoken": "", - "logo_img_footer_margin": "", - "logo_img_header_margin": "", - "logo_img_path": "", - "logo_img_path_height": "", - "logo_img_path_width": "", - "md_categories": "", - "md_ccc_rates": "", - "md_classes": "", - "md_ded_notes": "", - "md_email_cc": "", - "md_from_emails": "", - "md_functionality_toggles": { - "parts_queue_toggle": "" - }, - "md_hour_split": { - "paint": "", - "prep": "" - }, - "md_ins_co": { - "city": "", - "name": "", - "private": "", - "state": "", - "street1": "", - "street2": "", - "zip": "" - }, - "md_jobline_presets": "", - "md_lost_sale_reasons": "", - "md_parts_order_comment": "", - "md_parts_scan": { - "expression": "", - "flags": "" - }, - "md_payment_types": "", - "md_referral_sources": "", - "md_ro_guard": { - "enabled": "", - "enforce_ar": "", - "enforce_bills": "", - "enforce_cm": "", - "enforce_labor": "", - "enforce_ppd": "", - "enforce_profit": "", - "enforce_sublet": "", - "masterbypass": "", - "totalgppercent_minimum": "" - }, - "md_tasks_presets": { - "enable_tasks": "", - "hourstype": "", - "memo": "", - "name": "", - "nextstatus": "", - "percent": "", - "use_approvals": "" - }, - "messaginglabel": "", - "messagingtext": "", - "noteslabel": "", - "notestext": "", - "partslocation": "", - "phone": "", - "prodtargethrs": "", - "rbac": { - "accounting": { - "exportlog": "", - "payables": "", - "payments": "", - "receivables": "" - }, - "bills": { - "delete": "", - "enter": "", - "list": "", - "reexport": "", - "view": "" - }, - "contracts": { - "create": "", - "detail": "", - "list": "" - }, - "courtesycar": { - "create": "", - "detail": "", - "list": "" - }, - "csi": { - "export": "", - "page": "" - }, - "employee_teams": { - "page": "" - }, - "employees": { - "page": "" - }, - "inventory": { - "delete": "", - "list": "" - }, - "jobs": { - "admin": "", - "available-list": "", - "checklist-view": "", - "close": "", - "create": "", - "deliver": "", - "detail": "", - "intake": "", - "list-active": "", - "list-all": "", - "list-ready": "", - "partsqueue": "", - "void": "" - }, - "owners": { - "detail": "", - "list": "" - }, - "payments": { - "enter": "", - "list": "" - }, - "phonebook": { - "edit": "", - "view": "" - }, - "production": { - "board": "", - "list": "" - }, - "schedule": { - "view": "" - }, - "scoreboard": { - "view": "" - }, - "shiftclock": { - "view": "" - }, - "shop": { - "config": "", - "dashboard": "", - "rbac": "", - "reportcenter": "", - "templates": "", - "vendors": "" - }, - "temporarydocs": { - "view": "" - }, - "timetickets": { - "edit": "", - "editcommitted": "", - "enter": "", - "list": "", - "shiftedit": "" - }, - "ttapprovals": { - "approve": "", - "view": "" - }, - "users": { - "editaccess": "" - } - }, - "responsibilitycenter": "", - "responsibilitycenter_accountdesc": "", - "responsibilitycenter_accountitem": "", - "responsibilitycenter_accountname": "", - "responsibilitycenter_accountnumber": "", - "responsibilitycenter_rate": "", - "responsibilitycenter_tax_rate": "", - "responsibilitycenter_tax_sur": "", - "responsibilitycenter_tax_thres": "", - "responsibilitycenter_tax_tier": "", - "responsibilitycenter_tax_type": "", - "responsibilitycenters": { - "ap": "", - "ar": "", - "ats": "", - "federal_tax": "", - "federal_tax_itc": "", - "gst_override": "", - "invoiceexemptcode": "", - "itemexemptcode": "", - "la1": "", - "la2": "", - "la3": "", - "la4": "", - "laa": "", - "lab": "", - "lad": "", - "lae": "", - "laf": "", - "lag": "", - "lam": "", - "lar": "", - "las": "", - "lau": "", - "local_tax": "", - "mapa": "", - "mash": "", - "paa": "", - "pac": "", - "pag": "", - "pal": "", - "pam": "", - "pan": "", - "pao": "", - "pap": "", - "par": "", - "pas": "", - "pasl": "", - "refund": "", - "sales_tax_codes": { - "code": "", - "description": "", - "federal": "", - "local": "", - "state": "" - }, - "state_tax": "", - "tow": "" - }, - "schedule_end_time": "", - "schedule_start_time": "", - "shopname": "", - "speedprint": { - "id": "", - "label": "", - "templates": "" - }, - "ss_configuration": { - "dailyhrslimit": "" - }, - "ssbuckets": { - "color": "", - "gte": "", - "id": "", - "label": "", - "lt": "", - "target": "" - }, - "state": "", - "state_tax_id": "", - "status": "", - "statuses": { - "active_statuses": "", - "additional_board_statuses": "", - "color": "", - "default_arrived": "", - "default_bo": "", - "default_canceled": "", - "default_completed": "", - "default_delivered": "", - "default_exported": "", - "default_imported": "", - "default_invoiced": "", - "default_ordered": "", - "default_quote": "", - "default_received": "", - "default_returned": "", - "default_scheduled": "", - "default_void": "", - "open_statuses": "", - "post_production_statuses": "", - "pre_production_statuses": "", - "production_colors": "", - "production_statuses": "", - "ready_statuses": "" - }, - "target_touchtime": "", - "timezone": "", - "tt_allow_post_to_invoiced": "", - "tt_enforce_hours_for_tech_console": "", - "use_fippa": "", - "use_paint_scale_data": "", - "uselocalmediaserver": "", - "website": "", - "zip_post": "" - }, - "labels": { - "2tiername": "", - "2tiersetup": "", - "2tiersource": "", - "accountingsetup": "", - "accountingtiers": "", - "alljobstatuses": "", - "allopenjobstatuses": "", - "apptcolors": "", - "businessinformation": "", - "checklists": "", - "csiq": "", - "customtemplates": "", - "defaultcostsmapping": "", - "defaultprofitsmapping": "", - "deliverchecklist": "", - "dms": { - "cdk": { - "controllist": "", - "payers": "" - }, - "cdk_dealerid": "", - "costsmapping": "", - "dms_allocations": "", - "pbs_serialnumber": "", - "profitsmapping": "", - "title": "" - }, - "emaillater": "", - "employee_teams": "", - "employees": "", - "estimators": "", - "filehandlers": "", - "insurancecos": "", - "intakechecklist": "", - "intellipay": "", - "intellipay_cash_discount": "", - "jobstatuses": "", - "laborrates": "", - "licensing": "", - "md_parts_scan": "", - "md_ro_guard": "", - "md_tasks_presets": "", - "md_to_emails": "", - "md_to_emails_emails": "", - "messagingpresets": "", - "notemplatesavailable": "", - "notespresets": "", - "orderstatuses": "", - "partslocations": "", - "partsscan": "", - "printlater": "", - "qbo": "", - "qbo_departmentid": "", - "qbo_usa": "", - "rbac": "", - "responsibilitycenters": { - "costs": "", - "profits": "", - "sales_tax_codes": "", - "tax_accounts": "", - "title": "" - }, - "roguard": { - "title": "" - }, - "scheduling": "", - "scoreboardsetup": "", - "shopinfo": "", - "speedprint": "", - "ssbuckets": "", - "systemsettings": "", - "task-presets": "", - "workingdays": "" - }, - "successes": { - "areyousure": "", - "defaultviewcreated": "", - "save": "", - "unsavedchanges": "" - }, - "validation": { - "centermustexist": "", - "larsplit": "", - "useremailmustexist": "" - } - }, - "checklist": { - "actions": { - "printall": "" - }, - "errors": { - "complete": "", - "nochecklist": "" - }, - "labels": { - "addtoproduction": "", - "allow_text_message": "", - "checklist": "", - "printpack": "", - "removefromproduction": "" - }, - "successes": { - "completed": "" - } - }, - "contracts": { - "actions": { - "changerate": "", - "convertoro": "", - "decodelicense": "", - "find": "", - "printcontract": "", - "senddltoform": "" - }, - "errors": { - "fetchingjobinfo": "", - "returning": "", - "saving": "", - "selectjobandcar": "" - }, - "fields": { - "actax": "", - "actualreturn": "", - "agreementnumber": "", - "cc_cardholder": "", - "cc_expiry": "", - "cc_num": "", - "cleanupcharge": "", - "coverage": "", - "dailyfreekm": "", - "dailyrate": "", - "damage": "", - "damagewaiver": "", - "driver": "", - "driver_addr1": "", - "driver_addr2": "", - "driver_city": "", - "driver_dlexpiry": "", - "driver_dlnumber": "", - "driver_dlst": "", - "driver_dob": "", - "driver_fn": "", - "driver_ln": "", - "driver_ph1": "", - "driver_state": "", - "driver_zip": "", - "excesskmrate": "", - "federaltax": "", - "fuelin": "", - "fuelout": "", - "kmend": "", - "kmstart": "", - "length": "", - "localtax": "", - "refuelcharge": "", - "scheduledreturn": "", - "start": " ", - "statetax": "", - "status": "" - }, - "labels": { - "agreement": "", - "availablecars": "", - "cardueforservice": "", - "convertform": { - "applycleanupcharge": "", - "refuelqty": "" - }, - "correctdataonform": "", - "dateinpast": "", - "dlexpirebeforereturn": "", - "driverinformation": "", - "findcontract": "", - "findermodal": "", - "insuranceexpired": "", - "noteconvertedfrom": "", - "populatefromjob": "", - "rates": "", - "time": "", - "vehicle": "", - "waitingforscan": "" - }, - "status": { - "new": "", - "out": "", - "returned": "" - }, - "successes": { - "saved": "" - } - }, - "courtesycars": { - "actions": { - "new": "", - "return": "" - }, - "errors": { - "saving": "" - }, - "fields": { - "color": "", - "dailycost": "", - "damage": "", - "fleetnumber": "", - "fuel": "", - "insuranceexpires": "", - "leaseenddate": "", - "make": "", - "mileage": "", - "model": "", - "nextservicedate": "", - "nextservicekm": "", - "notes": "", - "plate": "", - "purchasedate": "", - "readiness": "", - "registrationexpires": "", - "serviceenddate": "", - "servicestartdate": "", - "status": "", - "vin": "", - "year": "" - }, - "labels": { - "courtesycar": "", - "fuel": { - "12": "", - "14": "", - "18": "", - "34": "", - "38": "", - "58": "", - "78": "", - "empty": "", - "full": "" - }, - "outwith": "", - "return": "", - "status": "", - "uniquefleet": "", - "usage": "", - "vehicle": "" - }, - "readiness": { - "notready": "", - "ready": "" - }, - "status": { - "in": "", - "inservice": "", - "leasereturn": "", - "out": "", - "sold": "", - "unavailable": "" - }, - "successes": { - "saved": "" - } - }, - "csi": { - "actions": { - "activate": "" - }, - "errors": { - "creating": "", - "notconfigured": "", - "notfoundsubtitle": "", - "notfoundtitle": "", - "surveycompletesubtitle": "", - "surveycompletetitle": "" - }, - "fields": { - "completedon": "", - "created_at": "", - "surveyid": "", - "validuntil": "" - }, - "labels": { - "copyright": "", - "greeting": "", - "intro": "", - "nologgedinuser": "", - "nologgedinuser_sub": "", - "noneselected": "", - "title": "" - }, - "successes": { - "created": "", - "submitted": "", - "submittedsub": "" - } - }, - "dashboard": { - "actions": { - "addcomponent": "" - }, - "errors": { - "refreshrequired": "", - "updatinglayout": "" - }, - "labels": { - "bodyhrs": "", - "dollarsinproduction": "", - "phone": "", - "prodhrs": "", - "refhrs": "" - }, - "titles": { - "joblifecycle": "", - "labhours": "", - "larhours": "", - "monthlyemployeeefficiency": "", - "monthlyjobcosting": "", - "monthlylaborsales": "", - "monthlypartssales": "", - "monthlyrevenuegraph": "", - "prodhrssummary": "", - "productiondollars": "", - "productionhours": "", - "projectedmonthlysales": "", - "scheduledindate": "", - "scheduledintoday": "", - "scheduledoutdate": "", - "scheduledouttoday": "", - "tasks": "" - } - }, - "dms": { - "errors": { - "alreadyexported": "" - }, - "labels": { - "refreshallocations": "" - } - }, - "documents": { - "actions": { - "delete": "", - "download": "", - "reassign": "", - "selectallimages": "", - "selectallotherdocuments": "" - }, - "errors": { - "deletes3": "Error al eliminar el documento del almacenamiento.", - "deleting": "", - "deleting_cloudinary": "", - "getpresignurl": "Error al obtener la URL prescrita para el documento. {{message}}", - "insert": "Incapaz de cargar el archivo. {{message}}", - "nodocuments": "No hay documentos", - "updating": "" - }, - "labels": { - "confirmdelete": "", - "doctype": "", - "newjobid": "", - "openinexplorer": "", - "optimizedimage": "", - "reassign_limitexceeded": "", - "reassign_limitexceeded_title": "", - "storageexceeded": "", - "storageexceeded_title": "", - "upload": "Subir", - "upload_limitexceeded": "", - "upload_limitexceeded_title": "", - "uploading": "", - "usage": "" - }, - "successes": { - "delete": "Documento eliminado con éxito.", - "edituploaded": "", - "insert": "Documento cargado con éxito.", - "updated": "" - } - }, - "emails": { - "errors": { - "notsent": "Correo electrónico no enviado Se encontró un error al enviar {{message}}" - }, - "fields": { - "cc": "", - "from": "", - "subject": "", - "to": "" - }, - "labels": { - "attachments": "", - "documents": "", - "emailpreview": "", - "generatingemail": "", - "pdfcopywillbeattached": "", - "preview": "" - }, - "successes": { - "sent": "Correo electrónico enviado con éxito." - } - }, - "employee_teams": { - "actions": { - "new": "", - "newmember": "" - }, - "fields": { - "active": "", - "employeeid": "", - "max_load": "", - "name": "", - "percentage": "" - } - }, - "employees": { - "actions": { - "addvacation": "", - "new": "Nuevo empleado", - "newrate": "" - }, - "errors": { - "delete": "Se encontró un error al eliminar al empleado. {{message}}", - "save": "Se encontró un error al salvar al empleado. {{message}}", - "validation": "Por favor verifique todos los campos.", - "validationtitle": "No se puede salvar al empleado." - }, - "fields": { - "active": "¿Activo?", - "base_rate": "Tasa básica", - "cost_center": "Centro de costos", - "employee_number": "Numero de empleado", - "external_id": "", - "first_name": "Nombre de pila", - "flat_rate": "Tarifa plana (deshabilitado es tiempo recto)", - "hire_date": "Fecha de contratación", - "last_name": "Apellido", - "pin": "", - "rate": "", - "termination_date": "Fecha de conclusión", - "user_email": "", - "vacation": { - "end": "", - "length": "", - "start": "" - } - }, - "labels": { - "actions": "", - "active": "", - "endmustbeafterstart": "", - "flat_rate": "", - "inactive": "", - "name": "", - "rate_type": "", - "status": "", - "straight_time": "" - }, - "successes": { - "delete": "Empleado eliminado con éxito.", - "save": "Empleado guardado con éxito.", - "vacationadded": "" - }, - "validation": { - "unique_employee_number": "" - } - }, - "eula": { - "buttons": { - "accept": "Accept EULA" - }, - "content": { - "never_scrolled": "You must scroll to the bottom of the Terms and Conditions before accepting." - }, - "errors": { - "acceptance": { - "description": "Something went wrong while accepting the EULA. Please try again.", - "message": "Eula Acceptance Error" - } - }, - "labels": { - "accepted_terms": "I accept the terms and conditions of this agreement.", - "address": "Address", - "business_name": "Legal Business Name", - "date_accepted": "Date Accepted", - "first_name": "First Name", - "last_name": "Last Name", - "phone_number": "Phone Number" - }, - "messages": { - "accepted_terms": "Please accept the terms and conditions of this agreement.", - "business_name": "Please enter your legal business name.", - "date_accepted": "Please enter Today's Date.", - "first_name": "Please enter your first name.", - "last_name": "Please enter your last name.", - "phone_number": "Please enter your phone number." - }, - "titles": { - "modal": "Terms and Conditions", - "upper_card": "Acknowledgement" - } - }, - "exportlogs": { - "fields": { - "createdat": "" - }, - "labels": { - "attempts": "", - "priorsuccesfulexport": "" - } - }, - "general": { - "actions": { - "add": "", - "autoupdate": "", - "calculate": "", - "cancel": "", - "clear": "", - "close": "", - "copied": "", - "copylink": "", - "create": "", - "defaults": "defaults", - "delay": "", - "delete": "Borrar", - "deleteall": "", - "deselectall": "", - "download": "", - "edit": "Editar", - "login": "", - "next": "", - "previous": "", - "print": "", - "refresh": "", - "remove": "", - "remove_alert": "", - "reset": " Restablecer a original.", - "resetpassword": "", - "save": "Salvar", - "saveandnew": "", - "saveas": "", - "selectall": "", - "send": "", - "sendbysms": "", - "senderrortosupport": "", - "submit": "", - "tryagain": "", - "view": "", - "viewreleasenotes": "" - }, - "errors": { - "fcm": "", - "notfound": "", - "sizelimit": "" - }, - "itemtypes": { - "contract": "", - "courtesycar": "", - "job": "", - "owner": "", - "vehicle": "" - }, - "labels": { - "actions": "Comportamiento", - "areyousure": "", - "barcode": "código de barras", - "cancel": "", - "clear": "", - "confirmpassword": "", - "created_at": "", - "date": "", - "datetime": "", - "email": "", - "errors": "", - "excel": "", - "exceptiontitle": "", - "friday": "", - "globalsearch": "", - "help": "", - "hours": "", - "in": "en", - "instanceconflictext": "", - "instanceconflictitle": "", - "item": "", - "label": "", - "loading": "Cargando...", - "loadingapp": "Cargando {{app}}", - "loadingshop": "Cargando datos de la tienda ...", - "loggingin": "Iniciando sesión ...", - "markedexported": "", - "media": "", - "message": "", - "monday": "", - "na": "N / A", - "newpassword": "", - "no": "", - "nointernet": "", - "nointernet_sub": "", - "none": "", - "out": "Afuera", - "password": "", - "passwordresetsuccess": "", - "passwordresetsuccess_sub": "", - "passwordresetvalidatesuccess": "", - "passwordresetvalidatesuccess_sub": "", - "passwordsdonotmatch": "", - "print": "", - "refresh": "", - "reports": "", - "required": "", - "saturday": "", - "search": "Buscar...", - "searchresults": "", - "selectdate": "", - "sendagain": "", - "sendby": "", - "signin": "", - "sms": "", - "status": "", - "sub_status": { - "expired": "" - }, - "successful": "", - "sunday": "", - "text": "", - "thursday": "", - "total": "", - "totals": "", - "tuesday": "", - "tvmode": "", - "unknown": "Desconocido", - "unsavedchanges": "", - "username": "", - "view": "", - "wednesday": "", - "yes": "" - }, - "languages": { - "english": "Inglés", - "french": "francés", - "spanish": "español" - }, - "messages": { - "exception": "", - "newversionmessage": "", - "newversiontitle": "", - "noacctfilepath": "", - "nofeatureaccess": "", - "noshop": "", - "notfoundsub": "", - "notfoundtitle": "", - "partnernotrunning": "", - "rbacunauth": "", - "unsavedchanges": "Usted tiene cambios no guardados.", - "unsavedchangespopup": "" - }, - "validation": { - "invalidemail": "Por favor introduzca una dirección de correo electrónico válida.", - "invalidphone": "", - "required": "Este campo es requerido." - } - }, - "help": { - "actions": { - "connect": "" - }, - "labels": { - "codeplacholder": "", - "rescuedesc": "", - "rescuetitle": "" - } - }, - "intake": { - "labels": { - "printpack": "" - } - }, - "inventory": { - "actions": { - "addtoinventory": "", - "addtoro": "", - "consumefrominventory": "", - "edit": "", - "new": "" - }, - "errors": { - "inserting": "" - }, - "fields": { - "comment": "", - "manualinvoicenumber": "", - "manualvendor": "" - }, - "labels": { - "consumedbyjob": "", - "deleteconfirm": "", - "frombillinvoicenumber": "", - "fromvendor": "", - "inventory": "", - "showall": "", - "showavailable": "" - }, - "successes": { - "deleted": "", - "inserted": "", - "updated": "" - } - }, - "job_lifecycle": { - "columns": { - "duration": "", - "end": "", - "human_readable": "", - "percentage": "", - "relative_end": "", - "relative_start": "", - "start": "", - "status": "", - "status_count": "", - "value": "" - }, - "content": { - "calculated_based_on": "", - "current_status_accumulated_time": "", - "data_unavailable": "", - "jobs_in_since": "", - "legend_title": "", - "loading": "", - "not_available": "", - "previous_status_accumulated_time": "", - "title": "", - "title_durations": "", - "title_loading": "", - "title_transitions": "" - }, - "errors": { - "fetch": "Error al obtener los datos del ciclo de vida del trabajo" - }, - "titles": { - "dashboard": "", - "top_durations": "" - } - }, - "job_payments": { - "buttons": { - "create_short_link": "", - "goback": "", - "proceedtopayment": "", - "refundpayment": "" - }, - "notifications": { - "error": { - "description": "", - "openingip": "", - "title": "" - } - }, - "titles": { - "amount": "", - "dateOfPayment": "", - "descriptions": "", - "hint": "", - "payer": "", - "payername": "", - "paymentid": "", - "paymentnum": "", - "paymenttype": "", - "refundamount": "", - "transactionid": "" - } - }, - "joblines": { - "actions": { - "assign_team": "", - "converttolabor": "", - "dispatchparts": "", - "new": "" - }, - "errors": { - "creating": "", - "updating": "" - }, - "fields": { - "act_price": "Precio actual", - "act_price_before_ppc": "", - "adjustment": "", - "ah_detail_line": "", - "amount": "", - "assigned_team": "", - "assigned_team_name": "", - "create_ppc": "", - "db_price": "Precio de base de datos", - "lbr_types": { - "LA1": "", - "LA2": "", - "LA3": "", - "LA4": "", - "LAA": "", - "LAB": "", - "LAD": "", - "LAE": "", - "LAF": "", - "LAG": "", - "LAM": "", - "LAR": "", - "LAS": "", - "LAU": "" - }, - "line_desc": "Descripción de línea", - "line_ind": "S#", - "line_no": "", - "location": "", - "mod_lb_hrs": "Horas laborales", - "mod_lbr_ty": "Tipo de trabajo", - "notes": "", - "oem_partno": "OEM parte #", - "op_code_desc": "", - "part_qty": "", - "part_type": "Tipo de parte", - "part_types": { - "CCC": "", - "CCD": "", - "CCDR": "", - "CCF": "", - "CCM": "", - "PAA": "", - "PAC": "", - "PAE": "", - "PAG": "", - "PAL": "", - "PAM": "", - "PAN": "", - "PAO": "", - "PAP": "", - "PAR": "", - "PAS": "", - "PASL": "" - }, - "profitcenter_labor": "", - "profitcenter_part": "", - "prt_dsmk_m": "", - "prt_dsmk_p": "", - "status": "Estado", - "tax_part": "", - "total": "", - "unq_seq": "Seq #" - }, - "labels": { - "adjustmenttobeadded": "", - "billref": "", - "convertedtolabor": "", - "edit": "Línea de edición", - "ioucreated": "", - "new": "Nueva línea", - "nostatus": "", - "presets": "" - }, - "successes": { - "created": "", - "saved": "", - "updated": "" - }, - "validations": { - "ahdetailonlyonuserdefinedtypes": "", - "hrsrequirediflbrtyp": "", - "requiredifparttype": "", - "zeropriceexistingpart": "" - } - }, - "jobs": { - "actions": { - "addDocuments": "Agregar documentos de trabajo", - "addNote": "Añadir la nota", - "addtopartsqueue": "", - "addtoproduction": "", - "addtoscoreboard": "", - "allocate": "", - "autoallocate": "", - "changefilehandler": "", - "changelaborrate": "", - "changestatus": "Cambiar Estado", - "changestimator": "", - "convert": "Convertir", - "createiou": "", - "deliver": "", - "dms": { - "addpayer": "", - "createnewcustomer": "", - "findmakemodelcode": "", - "getmakes": "", - "labels": { - "refreshallocations": "" - }, - "post": "", - "refetchmakesmodels": "", - "usegeneric": "", - "useselected": "" - }, - "dmsautoallocate": "", - "export": "", - "exportcustdata": "", - "exportselected": "", - "filterpartsonly": "", - "generatecsi": "", - "gotojob": "", - "intake": "", - "manualnew": "", - "mark": "", - "markasexported": "", - "markpstexempt": "", - "markpstexemptconfirm": "", - "postbills": "Contabilizar facturas", - "printCenter": "Centro de impresión", - "recalculate": "", - "reconcile": "", - "removefromproduction": "", - "schedule": "Programar", - "sendcsi": "", - "sendpartspricechange": "", - "sendtodms": "", - "sync": "", - "taxprofileoverride": "", - "taxprofileoverride_confirm": "", - "uninvoice": "", - "unvoid": "", - "viewchecklist": "", - "viewdetail": "" - }, - "errors": { - "addingtoproduction": "", - "cannotintake": "", - "closing": "", - "creating": "", - "deleted": "Error al eliminar el trabajo.", - "exporting": "", - "exporting-partner": "", - "invoicing": "", - "noaccess": "Este trabajo no existe o no tiene acceso a él.", - "nodamage": "", - "nodates": "No hay fechas especificadas para este trabajo.", - "nofinancial": "", - "nojobselected": "No hay trabajo seleccionado.", - "noowner": "Ningún propietario asociado.", - "novehicle": "No hay vehículo asociado.", - "partspricechange": "", - "saving": "Se encontró un error al guardar el registro.", - "scanimport": "", - "totalscalc": "", - "updating": "", - "validation": "Asegúrese de que todos los campos se ingresen correctamente.", - "validationtitle": "Error de validacion", - "voiding": "" - }, - "fields": { - "active_tasks": "", - "actual_completion": "Realización real", - "actual_delivery": "Entrega real", - "actual_in": "Real en", - "adjustment_bottom_line": "Ajustes", - "adjustmenthours": "", - "alt_transport": "", - "area_of_damage_impact": { - "10": "", - "11": "", - "12": "", - "13": "", - "14": "", - "15": "", - "16": "", - "25": "", - "26": "", - "27": "", - "28": "", - "34": "", - "01": "", - "02": "", - "03": "", - "04": "", - "05": "", - "06": "", - "07": "", - "08": "", - "09": "" - }, - "auto_add_ats": "", - "ca_bc_pvrt": "", - "ca_customer_gst": "", - "ca_gst_registrant": "", - "category": "", - "ccc": "", - "ccd": "", - "ccdr": "", - "ccf": "", - "ccm": "", - "cieca_id": "CIECA ID", - "cieca_pfl": { - "lbr_adjp": "", - "lbr_tax_in": "", - "lbr_taxp": "", - "lbr_tx_in1": "", - "lbr_tx_in2": "", - "lbr_tx_in3": "", - "lbr_tx_in4": "", - "lbr_tx_in5": "" - }, - "cieca_pfo": { - "stor_t_in1": "", - "stor_t_in2": "", - "stor_t_in3": "", - "stor_t_in4": "", - "stor_t_in5": "", - "tow_t_in1": "", - "tow_t_in2": "", - "tow_t_in3": "", - "tow_t_in4": "", - "tow_t_in5": "" - }, - "claim_total": "Reclamar total", - "class": "", - "clm_no": "Reclamación #", - "clm_total": "Reclamar total", - "comment": "", - "customerowing": "Cliente debido", - "date_estimated": "Fecha estimada", - "date_exported": "Exportado", - "date_invoiced": "Facturado", - "date_last_contacted": "", - "date_lost_sale": "", - "date_next_contact": "", - "date_open": "Abierto", - "date_rentalresp": "", - "date_repairstarted": "", - "date_scheduled": "Programado", - "date_towin": "", - "date_void": "", - "ded_amt": "Deducible", - "ded_note": "", - "ded_status": "Estado deducible", - "depreciation_taxes": "Depreciación / Impuestos", - "dms": { - "address": "", - "amount": "", - "center": "", - "control_type": { - "account_number": "" - }, - "cost": "", - "cost_dms_acctnumber": "", - "dms_make": "", - "dms_model": "", - "dms_model_override": "", - "dms_unsold": "", - "dms_wip_acctnumber": "", - "id": "", - "inservicedate": "", - "journal": "", - "lines": "", - "name1": "", - "payer": { - "amount": "", - "control_type": "", - "controlnumber": "", - "dms_acctnumber": "", - "name": "" - }, - "sale": "", - "sale_dms_acctnumber": "", - "story": "", - "vinowner": "" - }, - "dms_allocation": "", - "driveable": "", - "employee_body": "", - "employee_csr": "Representante de servicio al cliente.", - "employee_csr_writer": "", - "employee_prep": "", - "employee_refinish": "", - "est_addr1": "Dirección del tasador", - "est_co_nm": "Tasador", - "est_ct_fn": "Nombre del tasador", - "est_ct_ln": "Apellido del tasador", - "est_ea": "Correo electrónico del tasador", - "est_ph1": "Número de teléfono del tasador", - "federal_tax_payable": "Impuesto federal por pagar", - "federal_tax_rate": "", - "ins_addr1": "Dirección de Insurance Co.", - "ins_city": "Ciudad de seguros", - "ins_co_id": "ID de la compañía de seguros", - "ins_co_nm": "Nombre de la compañía de seguros", - "ins_co_nm_short": "", - "ins_ct_fn": "Nombre del controlador de archivos", - "ins_ct_ln": "Apellido del manejador de archivos", - "ins_ea": "Correo electrónico del controlador de archivos", - "ins_ph1": "File Handler Phone #", - "intake": { - "label": "", - "max": "", - "min": "", - "name": "", - "required": "", - "type": "" - }, - "invoice_final_note": "", - "kmin": "Kilometraje en", - "kmout": "Kilometraje", - "la1": "", - "la2": "", - "la3": "", - "la4": "", - "laa": "", - "lab": "", - "labor_rate_desc": "Nombre de la tasa laboral", - "lad": "", - "lae": "", - "laf": "", - "lag": "", - "lam": "", - "lar": "", - "las": "", - "lau": "", - "local_tax_rate": "", - "loss_date": "Fecha de pérdida", - "loss_desc": "", - "loss_of_use": "", - "lost_sale_reason": "", - "ma2s": "", - "ma3s": "", - "mabl": "", - "macs": "", - "mahw": "", - "mapa": "", - "mash": "", - "matd": "", - "materials": { - "MAPA": "", - "MASH": "", - "cal_maxdlr": "", - "cal_opcode": "", - "mat_adjp": "", - "mat_taxp": "", - "mat_tx_in1": "", - "mat_tx_in2": "", - "mat_tx_in3": "", - "mat_tx_in4": "", - "mat_tx_in5": "", - "materials": "", - "tax_ind": "" - }, - "other_amount_payable": "Otra cantidad a pagar", - "owner": "Propietario", - "owner_owing": "Cust. Debe", - "ownr_ea": "Email", - "ownr_ph1": "Teléfono 1", - "ownr_ph2": "", - "paa": "", - "pac": "", - "pae": "", - "pag": "", - "pal": "", - "pam": "", - "pan": "", - "pao": "", - "pap": "", - "par": "", - "parts_tax_rates": { - "prt_discp": "", - "prt_mktyp": "", - "prt_mkupp": "", - "prt_tax_in": "", - "prt_tax_rt": "", - "prt_tx_in1": "", - "prt_tx_in2": "", - "prt_tx_in3": "", - "prt_tx_in4": "", - "prt_tx_in5": "", - "prt_tx_ty1": "", - "prt_type": "" - }, - "partsstatus": "", - "pas": "", - "pay_date": "Fecha de Pay", - "phoneshort": "PH", - "po_number": "", - "policy_no": "Política #", - "ponumber": "numero postal", - "production_vars": { - "note": "" - }, - "qb_multiple_payers": { - "amount": "", - "name": "" - }, - "queued_for_parts": "", - "rate_ats": "", - "rate_la1": "Tarifa LA1", - "rate_la2": "Tarifa LA2", - "rate_la3": "Tarifa LA3", - "rate_la4": "Tarifa LA4", - "rate_laa": "Tasa de aluminio", - "rate_lab": "Tasa de trabajo", - "rate_lad": "Tasa de diagnóstico", - "rate_lae": "tarifa eléctrica", - "rate_laf": "Cuadros por segundo", - "rate_lag": "Tasa de vidrio", - "rate_lam": "Tasa mecánica", - "rate_lar": "Tasa de acabado", - "rate_las": "", - "rate_lau": "", - "rate_ma2s": "Velocidad de pintura de 2 etapas", - "rate_ma3s": "Tasa de pintura de 3 etapas", - "rate_mabl": "MABL ??", - "rate_macs": "MACS ??", - "rate_mahw": "Tasa de residuos peligrosos", - "rate_mapa": "Tasa de materiales de pintura", - "rate_mash": "Comprar material de tarifa", - "rate_matd": "Tasa de eliminación de neumáticos", - "referral_source_extra": "", - "referral_source_other": "", - "referralsource": "Fuente de referencia", - "regie_number": "N. ° de registro", - "repairtotal": "Reparación total", - "ro_number": "RO #", - "scheduled_completion": "Finalización programada", - "scheduled_delivery": "Entrega programada", - "scheduled_in": "Programado en", - "selling_dealer": "Distribuidor vendedor", - "selling_dealer_contact": "Contacto con el vendedor", - "servicecar": "Auto de servicio", - "servicing_dealer": "Distribuidor de servicio", - "servicing_dealer_contact": "Servicio Contacto con el concesionario", - "special_coverage_policy": "Política de cobertura especial", - "specialcoveragepolicy": "Política de cobertura especial", - "state_tax_rate": "", - "status": "Estado del trabajo", - "storage_payable": "Almacenamiento ", - "tax_lbr_rt": "", - "tax_levies_rt": "", - "tax_paint_mat_rt": "", - "tax_registration_number": "", - "tax_shop_mat_rt": "", - "tax_str_rt": "", - "tax_sub_rt": "", - "tax_tow_rt": "", - "towin": "", - "towing_payable": "Remolque a pagar", - "unitnumber": "Unidad #", - "updated_at": "Actualizado en", - "uploaded_by": "Subido por", - "vehicle": "Vehículo" - }, - "forms": { - "admindates": "", - "appraiserinfo": "", - "claiminfo": "", - "estdates": "", - "laborrates": "", - "lossinfo": "", - "other": "", - "repairdates": "", - "scheddates": "" - }, - "labels": { - "accountsreceivable": "", - "act_price_ppc": "", - "actual_completion_inferred": "", - "actual_delivery_inferred": "", - "actual_in_inferred": "", - "additionalpayeroverallocation": "", - "additionaltotal": "", - "adjustmentrate": "", - "adjustments": "", - "adminwarning": "", - "allocations": "", - "alreadyaddedtoscoreboard": "", - "alreadyclosed": "", - "appointmentconfirmation": "¿Enviar confirmación al cliente?", - "associationwarning": "", - "audit": "", - "available": "", - "availablejobs": "", - "ca_bc_pvrt": { - "days": "", - "rate": "" - }, - "ca_gst_all_if_null": "", - "calc_repair_days": "", - "calc_repair_days_tt": "", - "calc_scheuled_completion": "", - "cards": { - "customer": "Información al cliente", - "damage": "Área de Daño", - "dates": "fechas", - "documents": "Documentos recientes", - "estimator": "Estimador", - "filehandler": "File Handler", - "insurance": "detalles del seguro", - "more": "Más", - "notes": "Notas", - "parts": "Partes", - "totals": "Totales", - "vehicle": "Vehículo" - }, - "changeclass": "", - "checklistcompletedby": "", - "checklistdocuments": "", - "checklists": "", - "cieca_pfl": "", - "cieca_pfo": "", - "cieca_pft": "", - "closeconfirm": "", - "closejob": "", - "closingperiod": "", - "contracts": "", - "convertedtolabor": "", - "cost": "", - "cost_Additional": "", - "cost_labor": "", - "cost_parts": "", - "cost_sublet": "", - "costs": "", - "create": { - "jobinfo": "", - "newowner": "", - "newvehicle": "", - "novehicle": "", - "ownerinfo": "", - "vehicleinfo": "" - }, - "createiouwarning": "", - "creating_new_job": "Creando nuevo trabajo ...", - "deductible": { - "stands": "", - "waived": "" - }, - "deleteconfirm": "", - "deletedelivery": "", - "deleteintake": "", - "deliverchecklist": "", - "difference": "", - "diskscan": "", - "dms": { - "apexported": "", - "damageto": "", - "defaultstory": "", - "disablebillwip": "", - "invoicedatefuture": "", - "kmoutnotgreaterthankmin": "", - "logs": "", - "notallocated": "", - "postingform": "", - "totalallocated": "" - }, - "documents": "documentos", - "documents-images": "", - "documents-other": "", - "duplicateconfirm": "", - "emailaudit": "", - "employeeassignments": "", - "estimatelines": "", - "estimator": "", - "existing_jobs": "Empleos existentes", - "federal_tax_amt": "", - "gpdollars": "", - "gppercent": "", - "hrs_claimed": "", - "hrs_total": "", - "importnote": "", - "inproduction": "", - "intakechecklist": "", - "iou": "", - "job": "", - "jobcosting": "", - "jobtotals": "", - "labor_hrs": "", - "labor_rates_subtotal": "", - "laborallocations": "", - "labortotals": "", - "lines": "Líneas estimadas", - "local_tax_amt": "", - "mapa": "", - "markforreexport": "", - "mash": "", - "masterbypass": "", - "materials": { - "mapa": "" - }, - "missingprofileinfo": "", - "multipayers": "", - "net_repairs": "", - "notes": "Notas", - "othertotal": "", - "outstanding_ar": "", - "outstanding_credit_memos": "", - "outstanding_ppd": "", - "outstanding_reconciliation_discrep": "", - "outstanding_sublets": "", - "outstandinghours": "", - "override_header": "¿Anular encabezado estimado al importar?", - "ownerassociation": "", - "parts": "Partes", - "parts_lines": "", - "parts_received": "", - "parts_tax_rates": "", - "partsfilter": "", - "partssubletstotal": "", - "partstotal": "", - "performance": "", - "pimraryamountpayable": "", - "plitooltips": { - "billtotal": "", - "calculatedcreditsnotreceived": "", - "creditmemos": "", - "creditsnotreceived": "", - "discrep1": "", - "discrep2": "", - "discrep3": "", - "laboradj": "", - "partstotal": "", - "totalreturns": "" - }, - "ppc": "", - "ppdnotexported": "", - "profileadjustments": "", - "profitbypassrequired": "", - "profits": "", - "prt_dsmk_total": "", - "rates": "Tarifas", - "rates_subtotal": "", - "reconciliation": { - "billlinestotal": "", - "byassoc": "", - "byprice": "", - "clear": "", - "discrepancy": "", - "joblinestotal": "", - "multipleactprices": "", - "multiplebilllines": "", - "multiplebillsforactprice": "", - "removedpartsstrikethrough": "" - }, - "reconciliationheader": "", - "relatedros": "", - "remove_from_ar": "", - "returntotals": "", - "ro_guard": { - "enforce_ar": "", - "enforce_bills": "", - "enforce_cm": "", - "enforce_labor": "", - "enforce_ppd": "", - "enforce_profit": "", - "enforce_sublet": "", - "enforce_validation": "", - "enforced": "" - }, - "roguard": "", - "roguardwarnings": "", - "rosaletotal": "", - "sale_additional": "", - "sale_labor": "", - "sale_parts": "", - "sale_sublet": "", - "sales": "", - "savebeforeconversion": "", - "scheduledinchange": "", - "specialcoveragepolicy": "", - "state_tax_amt": "", - "subletsnotcompleted": "", - "subletstotal": "", - "subtotal": "", - "supplementnote": "", - "suspended": "", - "suspense": "", - "tasks": "", - "threshhold": "", - "total_cost": "", - "total_cust_payable": "", - "total_repairs": "", - "total_sales": "", - "total_sales_tax": "", - "totals": "", - "unvoidnote": "", - "update_scheduled_completion": "", - "vehicle_info": "Vehículo", - "vehicleassociation": "", - "viewallocations": "", - "voidjob": "", - "voidnote": "" - }, - "successes": { - "addedtoproduction": "", - "all_deleted": "{{count}} trabajos eliminados con éxito.", - "closed": "", - "converted": "Trabajo convertido con éxito.", - "created": "Trabajo creado con éxito. Click para ver.", - "creatednoclick": "", - "delete": "", - "deleted": "Trabajo eliminado con éxito.", - "duplicated": "", - "exported": "", - "invoiced": "", - "ioucreated": "", - "partsqueue": "", - "save": "Trabajo guardado con éxito.", - "savetitle": "Registro guardado con éxito.", - "supplemented": "Trabajo complementado con éxito.", - "updated": "", - "voided": "" - } - }, - "landing": { - "bigfeature": { - "subtitle": "", - "title": "" - }, - "footer": { - "company": { - "about": "", - "contact": "", - "disclaimers": "", - "name": "", - "privacypolicy": "" - }, - "io": { - "help": "", - "name": "", - "status": "" - }, - "slogan": "" - }, - "hero": { - "button": "", - "title": "" - }, - "labels": { - "features": "", - "managemyshop": "", - "pricing": "" - }, - "pricing": { - "basic": { - "name": "", - "sub": "" - }, - "essentials": { - "name": "", - "sub": "" - }, - "pricingtitle": "", - "pro": { - "name": "", - "sub": "" - }, - "title": "", - "unlimited": { - "name": "", - "sub": "" - } - } - }, - "menus": { - "currentuser": { - "languageselector": "idioma", - "profile": "Perfil" - }, - "header": { - "accounting": "", - "accounting-payables": "", - "accounting-payments": "", - "accounting-receivables": "", - "activejobs": "Empleos activos", - "all_tasks": "", - "alljobs": "", - "allpayments": "", - "availablejobs": "Trabajos disponibles", - "bills": "", - "courtesycars": "", - "courtesycars-all": "", - "courtesycars-contracts": "", - "courtesycars-newcontract": "", - "create_task": "", - "customers": "Clientes", - "dashboard": "", - "enterbills": "", - "entercardpayment": "", - "enterpayment": "", - "entertimeticket": "", - "export": "", - "export-logs": "", - "help": "", - "home": "Casa", - "inventory": "", - "jobs": "Trabajos", - "my_tasks": "", - "newjob": "", - "owners": "propietarios", - "parts-queue": "", - "phonebook": "", - "productionboard": "", - "productionlist": "", - "readyjobs": "", - "recent": "", - "reportcenter": "", - "rescueme": "", - "schedule": "Programar", - "scoreboard": "", - "search": { - "bills": "", - "jobs": "", - "owners": "", - "payments": "", - "phonebook": "", - "vehicles": "" - }, - "shiftclock": "", - "shop": "Mi tienda", - "shop_config": "Configuración", - "shop_csi": "", - "shop_templates": "", - "shop_vendors": "Vendedores", - "tasks": "", - "temporarydocs": "", - "timetickets": "", - "ttapprovals": "", - "vehicles": "Vehículos" - }, - "jobsactions": { - "admin": "", - "cancelallappointments": "", - "closejob": "", - "deletejob": "", - "duplicate": "", - "duplicatenolines": "", - "newcccontract": "", - "void": "" - }, - "jobsdetail": { - "claimdetail": "Detalles de la reclamación", - "dates": "fechas", - "financials": "", - "general": "", - "insurance": "", - "labor": "Labor", - "lifecycle": "", - "parts": "", - "partssublet": "Piezas / Subarrendamiento", - "rates": "", - "repairdata": "Datos de reparación", - "totals": "" - }, - "profilesidebar": { - "profile": "Mi perfil", - "shops": "Mis tiendas" - }, - "tech": { - "assignedjobs": "", - "claimtask": "", - "dispatchedparts": "", - "home": "", - "jobclockin": "", - "jobclockout": "", - "joblookup": "", - "login": "", - "logout": "", - "productionboard": "", - "productionlist": "", - "shiftclockin": "" - } - }, - "messaging": { - "actions": { - "link": "", - "new": "" - }, - "errors": { - "invalidphone": "", - "noattachedjobs": "", - "updatinglabel": "" - }, - "labels": { - "addlabel": "", - "archive": "", - "maxtenimages": "", - "messaging": "Mensajería", - "noallowtxt": "", - "nojobs": "", - "nopush": "", - "phonenumber": "", - "presets": "", - "recentonly": "", - "selectmedia": "", - "sentby": "", - "typeamessage": "Enviar un mensaje...", - "unarchive": "" - }, - "render": { - "conversation_list": "" - } - }, - "notes": { - "actions": { - "actions": "Comportamiento", - "deletenote": "Borrar nota", - "edit": "Editar nota", - "new": "Nueva nota", - "savetojobnotes": "" - }, - "errors": { - "inserting": "" - }, - "fields": { - "createdby": "Creado por", - "critical": "Crítico", - "private": "Privado", - "text": "Contenido", - "type": "", - "types": { - "customer": "", - "general": "", - "office": "", - "paint": "", - "parts": "", - "shop": "", - "supplement": "" - }, - "updatedat": "Actualizado en" - }, - "labels": { - "addtorelatedro": "", - "newnoteplaceholder": "Agrega una nota...", - "notetoadd": "", - "systemnotes": "", - "usernotes": "" - }, - "successes": { - "create": "Nota creada con éxito.", - "deleted": "Nota eliminada con éxito.", - "updated": "Nota actualizada con éxito." - } - }, - "owner": { - "labels": { - "noownerinfo": "" - } - }, - "owners": { - "actions": { - "update": "" - }, - "errors": { - "deleting": "", - "noaccess": "El registro no existe o no tiene acceso a él.", - "saving": "", - "selectexistingornew": "" - }, - "fields": { - "address": "Dirección", - "allow_text_message": "Permiso de texto?", - "name": "Nombre", - "note": "", - "ownr_addr1": "Dirección", - "ownr_addr2": "Dirección 2", - "ownr_city": "ciudad", - "ownr_co_nm": "", - "ownr_ctry": "País", - "ownr_ea": "Email", - "ownr_fn": "Nombre de pila", - "ownr_ln": "Apellido", - "ownr_ph1": "Teléfono 1", - "ownr_ph2": "", - "ownr_st": "Provincia del estado", - "ownr_title": "Título", - "ownr_zip": "código postal", - "preferred_contact": "Método de Contacto Preferido", - "tax_number": "" - }, - "forms": { - "address": "", - "contact": "", - "name": "" - }, - "labels": { - "create_new": "Crea un nuevo registro de propietario.", - "deleteconfirm": "", - "existing_owners": "Propietarios existentes", - "fromclaim": "", - "fromowner": "", - "relatedjobs": "", - "updateowner": "" - }, - "successes": { - "delete": "", - "save": "Propietario guardado con éxito." - } - }, - "parts": { - "actions": { - "order": "Pedido de piezas", - "orderinhouse": "" - } - }, - "parts_dispatch": { - "actions": { - "accept": "" - }, - "errors": { - "accepting": "", - "creating": "" - }, - "fields": { - "number": "", - "percent_accepted": "" - }, - "labels": { - "notyetdispatched": "", - "parts_dispatch": "" - } - }, - "parts_dispatch_lines": { - "fields": { - "accepted_at": "" - } - }, - "parts_orders": { - "actions": { - "backordered": "", - "receive": "", - "receivebill": "" - }, - "errors": { - "associatedbills": "", - "backordering": "", - "creating": "Se encontró un error al crear el pedido de piezas.", - "oec": "", - "saving": "", - "updating": "" - }, - "fields": { - "act_price": "", - "backordered_eta": "", - "backordered_on": "", - "cm_received": "", - "comments": "", - "cost": "", - "db_price": "", - "deliver_by": "", - "job_line_id": "", - "line_desc": "", - "line_remarks": "", - "lineremarks": "Comentarios de línea", - "oem_partno": "", - "order_date": "", - "order_number": "", - "orderedby": "", - "part_type": "", - "quantity": "", - "return": "", - "status": "" - }, - "labels": { - "allpartsto": "", - "confirmdelete": "", - "custompercent": "", - "discount": "", - "email": "Enviar por correo electrónico", - "inthisorder": "Partes en este pedido", - "is_quote": "", - "mark_as_received": "", - "newpartsorder": "", - "notyetordered": "", - "oec": "", - "order_type": "", - "orderhistory": "Historial de pedidos", - "parts_order": "", - "parts_orders": "", - "print": "Mostrar formulario impreso", - "receive": "", - "removefrompartsqueue": "", - "returnpartsorder": "", - "sublet_order": "" - }, - "successes": { - "created": "Pedido de piezas creado con éxito.", - "line_updated": "", - "received": "", - "return_created": "" - } - }, - "payments": { - "actions": { - "generatepaymentlink": "" - }, - "errors": { - "exporting": "", - "exporting-partner": "", - "inserting": "" - }, - "fields": { - "amount": "", - "created_at": "", - "date": "", - "exportedat": "", - "memo": "", - "payer": "", - "paymentnum": "", - "stripeid": "", - "transactionid": "", - "type": "" - }, - "labels": { - "balance": "", - "ca_bc_etf_table": "", - "customer": "", - "edit": "", - "electronicpayment": "", - "external": "", - "findermodal": "", - "insurance": "", - "markexported": "", - "markforreexport": "", - "new": "", - "signup": "", - "smspaymentreminder": "", - "title": "", - "totalpayments": "" - }, - "successes": { - "exported": "", - "markexported": "", - "markreexported": "", - "payment": "", - "paymentupdate": "", - "stripe": "" - } - }, - "phonebook": { - "actions": { - "new": "" - }, - "errors": { - "adding": "", - "saving": "" - }, - "fields": { - "address1": "", - "address2": "", - "category": "", - "city": "", - "company": "", - "country": "", - "email": "", - "fax": "", - "firstname": "", - "lastname": "", - "phone1": "", - "phone2": "", - "state": "" - }, - "labels": { - "noneselected": "", - "onenamerequired": "", - "vendorcategory": "" - }, - "successes": { - "added": "", - "deleted": "", - "saved": "" - } - }, - "printcenter": { - "appointments": { - "appointment_confirmation": "" - }, - "bills": { - "inhouse_invoice": "" - }, - "courtesycarcontract": { - "courtesy_car_contract": "", - "courtesy_car_impound": "", - "courtesy_car_inventory": "", - "courtesy_car_terms": "" - }, - "errors": { - "nocontexttype": "" - }, - "jobs": { - "3rdpartyfields": { - "addr1": "", - "addr2": "", - "addr3": "", - "attn": "", - "city": "", - "custgst": "", - "ded_amt": "", - "depreciation": "", - "other": "", - "ponumber": "", - "refnumber": "", - "sendtype": "", - "state": "", - "zip": "" - }, - "3rdpartypayer": "", - "ab_proof_of_loss": "", - "appointment_confirmation": "", - "appointment_reminder": "", - "casl_authorization": "", - "committed_timetickets_ro": "", - "coversheet_landscape": "", - "coversheet_portrait": "", - "csi_invitation": "", - "csi_invitation_action": "", - "diagnostic_authorization": "", - "dms_posting_sheet": "", - "envelope_return_address": "", - "estimate": "", - "estimate_detail": "", - "estimate_followup": "", - "express_repair_checklist": "", - "filing_coversheet_landscape": "", - "filing_coversheet_portrait": "", - "final_invoice": "", - "fippa_authorization": "", - "folder_label_multiple": "", - "glass_express_checklist": "", - "guarantee": "", - "individual_job_note": "", - "invoice_customer_payable": "", - "invoice_total_payable": "", - "iou_form": "", - "job_costing_ro": "", - "job_lifecycle_ro": "", - "job_notes": "", - "job_tasks": "", - "key_tag": "", - "labels": { - "count": "", - "labels": "", - "position": "" - }, - "lag_time_ro": "", - "mechanical_authorization": "", - "mpi_animal_checklist": "", - "mpi_eglass_auth": "", - "mpi_final_acct_sheet": "", - "mpi_final_repair_acct_sheet": "", - "paint_grid": "", - "parts_dispatch": "", - "parts_invoice_label_single": "", - "parts_label_multiple": "", - "parts_label_single": "", - "parts_list": "", - "parts_order": "", - "parts_order_confirmation": "", - "parts_order_history": "", - "parts_return_slip": "", - "payment_receipt": "", - "payment_request": "", - "payments_by_job": "", - "purchases_by_ro_detail": "", - "purchases_by_ro_summary": "", - "qc_sheet": "", - "rental_reservation": "", - "ro_totals": "", - "ro_with_description": "", - "sgi_certificate_of_repairs": "", - "sgi_windshield_auth": "", - "stolen_recovery_checklist": "", - "sublet_order": "", - "supplement_request": "", - "thank_you_ro": "", - "thirdpartypayer": "", - "timetickets_ro": "", - "vehicle_check_in": "", - "vehicle_delivery_check": "", - "window_tag": "", - "window_tag_sublet": "", - "work_authorization": "", - "worksheet_by_line_number": "", - "worksheet_sorted_by_operation": "", - "worksheet_sorted_by_operation_no_hours": "", - "worksheet_sorted_by_operation_part_type": "", - "worksheet_sorted_by_operation_type": "", - "worksheet_sorted_by_team": "" - }, - "labels": { - "groups": { - "authorization": "", - "financial": "", - "post": "", - "pre": "", - "ro": "", - "worksheet": "" - }, - "misc": "", - "repairorder": "", - "reportcentermodal": "", - "speedprint": "", - "title": "" - }, - "payments": { - "ca_bc_etf_table": "", - "exported_payroll": "" - }, - "special": { - "attendance_detail_csv": "" - }, - "subjects": { - "jobs": { - "individual_job_note": "", - "parts_dispatch": "", - "parts_order": "", - "parts_return_slip": "", - "sublet_order": "" - } - }, - "vendors": { - "purchases_by_vendor_detailed": "", - "purchases_by_vendor_summary": "" - } - }, - "production": { - "actions": { - "addcolumns": "", - "bodypriority-clear": "", - "bodypriority-set": "", - "detailpriority-clear": "", - "detailpriority-set": "", - "paintpriority-clear": "", - "paintpriority-set": "", - "remove": "", - "removecolumn": "", - "saveconfig": "", - "suspend": "", - "unsuspend": "" - }, - "constants": { - "main_profile": "" - }, - "errors": { - "boardupdate": "", - "name_exists": "", - "name_required": "", - "removing": "", - "settings": "" - }, - "labels": { - "actual_in": "", - "addnewprofile": "", - "alert": "", - "alertoff": "", - "alerton": "", - "alerts": "", - "ats": "", - "bodyhours": "", - "bodypriority": "", - "bodyshop": { - "labels": { - "qbo_departmentid": "", - "qbo_usa": "" - } - }, - "card_size": "", - "cardcolor": "", - "cardsettings": "", - "clm_no": "", - "comment": "", - "compact": "", - "detailpriority": "", - "employeeassignments": "", - "employeesearch": "", - "estimator": "", - "horizontal": "", - "ins_co_nm": "", - "jobdetail": "", - "kiosk_mode": "", - "laborhrs": "", - "legend": "", - "model_info": "", - "note": "", - "off": "", - "on": "", - "orientation": "", - "ownr_nm": "", - "paintpriority": "", - "partsstatus": "", - "production_note": "", - "refinishhours": "", - "scheduled_completion": "", - "selectview": "", - "stickyheader": "", - "sublets": "", - "subtotal": "", - "tall": "", - "tasks": "", - "totalhours": "", - "touchtime": "", - "vertical": "", - "viewname": "", - "wide": "" - }, - "options": { - "horizontal": "", - "large": "", - "medium": "", - "small": "", - "vertical": "" - }, - "settings": { - "board_settings": "", - "filters": { - "md_estimators": "", - "md_ins_cos": "" - }, - "filters_title": "", - "information": "", - "layout": "", - "statistics": { - "jobs_in_production": "", - "tasks_in_production": "", - "tasks_on_board": "", - "tasks_in_view": "", - "total_amount_in_production": "", - "total_amount_on_board": "", - "total_amount_in_view": "", - "total_hours_in_production": "", - "total_hours_on_board": "", - "total_hours_in_view": "", - "total_jobs_on_board": "", - "total_jobs_in_view": "", - "total_lab_in_production": "", - "total_lab_on_board": "", - "total_lab_in_view": "", - "total_lar_in_production": "", - "total_lar_on_board": "", - "total_lar_in_view": "" - }, - "statistics_title": "" - }, - "statistics": { - "currency_symbol": "", - "hours": "", - "jobs": "", - "jobs_in_production": "", - "tasks": "", - "tasks_in_production": "", - "tasks_on_board": "", - "tasks_in_view": "", - "total_amount_in_production": "", - "total_amount_on_board": "", - "total_amount_in_view": "", - "total_hours_in_production": "", - "total_hours_on_board": "", - "total_hours_in_view": "", - "total_jobs_on_board": "", - "total_jobs_in_view": "", - "total_lab_in_production": "", - "total_lab_on_board": "", - "total_lab_in_view": "", - "total_lar_in_production": "", - "total_lar_on_board": "", - "total_lar_in_view": "" - }, - "successes": { - "removed": "" - } - }, - "profile": { - "errors": { - "state": "Error al leer el estado de la página. Porfavor refresca." - }, - "labels": { - "activeshop": "" - }, - "successes": { - "updated": "" - } - }, - "reportcenter": { - "actions": { - "generate": "" - }, - "labels": { - "advanced_filters": "", - "advanced_filters_false": "", - "advanced_filters_filter_field": "", - "advanced_filters_filter_operator": "", - "advanced_filters_filter_value": "", - "advanced_filters_filters": "", - "advanced_filters_hide": "", - "advanced_filters_show": "", - "advanced_filters_sorter_direction": "", - "advanced_filters_sorter_field": "", - "advanced_filters_sorters": "", - "advanced_filters_true": "", - "dates": "", - "employee": "", - "filterson": "", - "generateasemail": "", - "groups": { - "customers": "", - "jobs": "", - "payroll": "", - "purchases": "", - "sales": "" - }, - "key": "", - "objects": { - "appointments": "", - "bills": "", - "csi": "", - "exportlogs": "", - "jobs": "", - "parts_orders": "", - "payments": "", - "scoreboard": "", - "tasks": "", - "timetickets": "" - }, - "vendor": "" - }, - "templates": { - "adp_payroll_flat": "", - "adp_payroll_straight": "", - "anticipated_revenue": "", - "ar_aging": "", - "attendance_detail": "", - "attendance_employee": "", - "attendance_summary": "", - "committed_timetickets": "", - "committed_timetickets_employee": "", - "committed_timetickets_summary": "", - "credits_not_received_date": "", - "credits_not_received_date_vendorid": "", - "csi": "", - "customer_list": "", - "cycle_time_analysis": "", - "estimates_written_converted": "", - "estimator_detail": "", - "estimator_summary": "", - "export_payables": "", - "export_payments": "", - "export_receivables": "", - "exported_gsr_by_ro": "", - "exported_gsr_by_ro_labor": "", - "gsr_by_atp": "", - "gsr_by_ats": "", - "gsr_by_category": "", - "gsr_by_csr": "", - "gsr_by_delivery_date": "", - "gsr_by_estimator": "", - "gsr_by_exported_date": "", - "gsr_by_ins_co": "", - "gsr_by_make": "", - "gsr_by_referral": "", - "gsr_by_ro": "", - "gsr_labor_only": "", - "hours_sold_detail_closed": "", - "hours_sold_detail_closed_csr": "", - "hours_sold_detail_closed_estimator": "", - "hours_sold_detail_closed_ins_co": "", - "hours_sold_detail_closed_status": "", - "hours_sold_detail_open": "", - "hours_sold_detail_open_csr": "", - "hours_sold_detail_open_estimator": "", - "hours_sold_detail_open_ins_co": "", - "hours_sold_detail_open_status": "", - "hours_sold_summary_closed": "", - "hours_sold_summary_closed_csr": "", - "hours_sold_summary_closed_estimator": "", - "hours_sold_summary_closed_ins_co": "", - "hours_sold_summary_closed_status": "", - "hours_sold_summary_open": "", - "hours_sold_summary_open_csr": "", - "hours_sold_summary_open_estimator": "", - "hours_sold_summary_open_ins_co": "", - "hours_sold_summary_open_status": "", - "job_costing_ro_csr": "", - "job_costing_ro_date_detail": "", - "job_costing_ro_date_summary": "", - "job_costing_ro_estimator": "", - "job_costing_ro_ins_co": "", - "job_lifecycle_date_detail": "", - "job_lifecycle_date_summary": "", - "jobs_completed_not_invoiced": "", - "jobs_invoiced_not_exported": "", - "jobs_reconcile": "", - "jobs_scheduled_completion": "", - "lag_time": "", - "load_level": "", - "lost_sales": "", - "open_orders": "", - "open_orders_csr": "", - "open_orders_estimator": "", - "open_orders_excel": "", - "open_orders_ins_co": "", - "open_orders_referral": "", - "open_orders_specific_csr": "", - "open_orders_status": "", - "parts_backorder": "", - "parts_not_recieved": "", - "parts_not_recieved_vendor": "", - "parts_received_not_scheduled": "", - "payments_by_date": "", - "payments_by_date_payment": "", - "payments_by_date_type": "", - "production_by_category": "", - "production_by_category_one": "", - "production_by_csr": "", - "production_by_last_name": "", - "production_by_repair_status": "", - "production_by_repair_status_one": "", - "production_by_ro": "", - "production_by_target_date": "", - "production_by_technician": "", - "production_by_technician_one": "", - "production_not_production_status": "", - "production_over_time": "", - "psr_by_make": "", - "purchase_return_ratio_grouped_by_vendor_detail": "", - "purchase_return_ratio_grouped_by_vendor_summary": "", - "purchases_by_cost_center_detail": "", - "purchases_by_cost_center_summary": "", - "purchases_by_date_range_detail": "", - "purchases_by_date_range_summary": "", - "purchases_by_ro_detail_date": "", - "purchases_by_ro_summary_date": "", - "purchases_by_vendor_detailed_date_range": "", - "purchases_by_vendor_summary_date_range": "", - "purchases_grouped_by_vendor_detailed": "", - "purchases_grouped_by_vendor_summary": "", - "returns_grouped_by_vendor_detailed": "", - "returns_grouped_by_vendor_summary": "", - "schedule": "", - "scheduled_parts_list": "", - "scoreboard_detail": "", - "scoreboard_summary": "", - "supplement_ratio_ins_co": "", - "tasks_date": "", - "tasks_date_employee": "", - "thank_you_date": "", - "timetickets": "", - "timetickets_employee": "", - "timetickets_summary": "", - "unclaimed_hrs": "", - "void_ros": "", - "work_in_progress_committed_labour": "", - "work_in_progress_jobs": "", - "work_in_progress_labour": "", - "work_in_progress_payables": "" - } - }, - "schedule": { - "labels": { - "atssummary": "", - "employeevacation": "", - "estimators": "", - "ins_co_nm_filter": "", - "intake": "", - "manual": "", - "manualevent": "" - } - }, - "scoreboard": { - "actions": { - "edit": "" - }, - "errors": { - "adding": "", - "removing": "", - "updating": "" - }, - "fields": { - "bodyhrs": "", - "date": "", - "painthrs": "" - }, - "labels": { - "allemployeetimetickets": "", - "asoftodaytarget": "", - "body": "", - "bodyabbrev": "", - "bodycharttitle": "", - "calendarperiod": "", - "combinedcharttitle": "", - "dailyactual": "", - "dailytarget": "", - "efficiencyoverperiod": "", - "entries": "", - "jobs": "", - "jobscompletednotinvoiced": "", - "lastmonth": "", - "lastweek": "", - "monthlytarget": "", - "priorweek": "", - "productivestatistics": "", - "productivetimeticketsoverdate": "", - "refinish": "", - "refinishabbrev": "", - "refinishcharttitle": "", - "targets": "", - "thismonth": "", - "thisweek": "", - "timetickets": "", - "timeticketsemployee": "", - "todateactual": "", - "total": "", - "totalhrs": "", - "totaloverperiod": "", - "weeklyactual": "", - "weeklytarget": "", - "workingdays": "" - }, - "successes": { - "added": "", - "removed": "", - "updated": "" - } - }, - "tasks": { - "actions": { - "edit": "", - "new": "" - }, - "buttons": { - "allTasks": "", - "complete": "", - "create": "", - "delete": "", - "edit": "", - "myTasks": "", - "refresh": "" - }, - "date_presets": { - "completion": "", - "day": "", - "days": "", - "delivery": "", - "next_week": "", - "one_month": "", - "three_months": "", - "three_weeks": "", - "today": "", - "tomorrow": "", - "two_weeks": "" - }, - "failures": { - "completed": "", - "created": "", - "deleted": "", - "updated": "" - }, - "fields": { - "actions": "", - "assigned_to": "", - "bill": "", - "billid": "", - "completed": "", - "created_at": "", - "created_by": "", - "description": "", - "due_date": "", - "job": { - "ro_number": "" - }, - "jobid": "", - "jobline": "", - "joblineid": "", - "parts_order": "", - "partsorderid": "", - "priorities": { - "high": "", - "low": "", - "medium": "" - }, - "priority": "", - "related_items": "", - "remind_at": "", - "title": "" - }, - "placeholders": { - "assigned_to": "", - "billid": "", - "description": "", - "jobid": "", - "joblineid": "", - "partsorderid": "" - }, - "successes": { - "completed": "", - "created": "", - "deleted": "", - "updated": "" - }, - "titles": { - "all_tasks": "", - "completed": "", - "deleted": "", - "job_tasks": "", - "mine": "", - "my_tasks": "" - }, - "validation": { - "due_at_error_message": "", - "remind_at_error_message": "" - } - }, - "tech": { - "fields": { - "employeeid": "", - "pin": "" - }, - "labels": { - "loggedin": "", - "notloggedin": "" - } - }, - "templates": { - "errors": { - "updating": "" - }, - "successes": { - "updated": "" - } - }, - "timetickets": { - "actions": { - "claimtasks": "", - "clockin": "", - "clockout": "", - "commit": "", - "commitone": "", - "enter": "", - "payall": "", - "printemployee": "", - "uncommit": "" - }, - "errors": { - "clockingin": "", - "clockingout": "", - "creating": "", - "deleting": "", - "noemployeeforuser": "", - "noemployeeforuser_sub": "", - "payall": "", - "shiftalreadyclockedon": "" - }, - "fields": { - "actualhrs": "", - "ciecacode": "", - "clockhours": "", - "clockoff": "", - "clockon": "", - "committed": "", - "committed_at": "", - "cost_center": "", - "created_by": "", - "date": "", - "efficiency": "", - "employee": "", - "employee_team": "", - "flat_rate": "", - "memo": "", - "productivehrs": "", - "ro_number": "", - "task_name": "" - }, - "labels": { - "alreadyclockedon": "", - "ambreak": "", - "amshift": "", - "claimtaskpreview": "", - "clockhours": "", - "clockintojob": "", - "deleteconfirm": "", - "edit": "", - "efficiency": "", - "flat_rate": "", - "jobhours": "", - "lunch": "", - "new": "", - "payrollclaimedtasks": "", - "pmbreak": "", - "pmshift": "", - "shift": "", - "shiftalreadyclockedon": "", - "straight_time": "", - "task": "", - "timetickets": "", - "unassigned": "", - "zeroactualnegativeprod": "" - }, - "successes": { - "clockedin": "", - "clockedout": "", - "committed": "", - "created": "", - "deleted": "", - "payall": "" - }, - "validation": { - "clockoffmustbeafterclockon": "", - "clockoffwithoutclockon": "", - "hoursenteredmorethanavailable": "", - "unassignedlines": "" - } - }, - "titles": { - "accounting-payables": "", - "accounting-payments": "", - "accounting-receivables": "", - "all_tasks": "", - "app": "", - "bc": { - "accounting-payables": "", - "accounting-payments": "", - "accounting-receivables": "", - "all_tasks": "", - "availablejobs": "", - "bills-list": "", - "contracts": "", - "contracts-create": "", - "contracts-detail": "", - "courtesycars": "", - "courtesycars-detail": "", - "courtesycars-new": "", - "dashboard": "", - "dms": "", - "export-logs": "", - "inventory": "", - "jobs": "", - "jobs-active": "", - "jobs-admin": "", - "jobs-all": "", - "jobs-checklist": "", - "jobs-close": "", - "jobs-deliver": "", - "jobs-detail": "", - "jobs-intake": "", - "jobs-new": "", - "jobs-ready": "", - "my_tasks": "", - "owner-detail": "", - "owners": "", - "parts-queue": "", - "payments-all": "", - "phonebook": "", - "productionboard": "", - "productionlist": "", - "profile": "", - "schedule": "", - "scoreboard": "", - "shop": "", - "shop-csi": "", - "shop-templates": "", - "shop-vendors": "", - "tasks": "", - "temporarydocs": "", - "timetickets": "", - "ttapprovals": "", - "vehicle-details": "", - "vehicles": "" - }, - "bills-list": "", - "contracts": "", - "contracts-create": "", - "contracts-detail": "", - "courtesycars": "", - "courtesycars-create": "", - "courtesycars-detail": "", - "dashboard": "", - "dms": "", - "export-logs": "", - "imexonline": "", - "inventory": "", - "jobs": "Todos los trabajos | {{app}}", - "jobs-admin": "", - "jobs-all": "", - "jobs-checklist": "", - "jobs-close": "", - "jobs-create": "", - "jobs-deliver": "", - "jobs-intake": "", - "jobsavailable": "Empleos disponibles | {{app}}", - "jobsdetail": "Trabajo {{ro_number}} | {{app}}", - "jobsdocuments": "Documentos de trabajo {{ro_number}} | {{app}}", - "manageroot": "Casa | {{app}}", - "my_tasks": "", - "owners": "Todos los propietarios | {{app}}", - "owners-detail": "", - "parts-queue": "", - "payments-all": "", - "phonebook": "", - "productionboard": "", - "productionlist": "", - "profile": "Mi perfil | {{app}}", - "promanager": "", - "readyjobs": "", - "resetpassword": "", - "resetpasswordvalidate": "", - "romeonline": "", - "schedule": "Horario | {{app}}", - "scoreboard": "", - "shop": "Mi tienda | {{app}}", - "shop-csi": "", - "shop-templates": "", - "shop_vendors": "Vendedores | {{app}}", - "tasks": "", - "techconsole": "{{app}}", - "techjobclock": "{{app}}", - "techjoblookup": "{{app}}", - "techshiftclock": "{{app}}", - "temporarydocs": "", - "timetickets": "", - "ttapprovals": "", - "vehicledetail": "Detalles del vehículo {{vehicle}} | {{app}}", - "vehicles": "Todos los vehiculos | {{app}}" - }, - "trello": { - "labels": { - "add_card": "", - "add_lane": "", - "cancel": "", - "delete_lane": "", - "description": "", - "label": "", - "lane_actions": "", - "title": "" - } - }, - "tt_approvals": { - "actions": { - "approveselected": "" - }, - "labels": { - "approval_queue_in_use": "", - "calculate": "" - } - }, - "user": { - "actions": { - "changepassword": "", - "signout": "desconectar", - "updateprofile": "Actualización del perfil" - }, - "errors": { - "updating": "" - }, - "fields": { - "authlevel": "", - "displayname": "Nombre para mostrar", - "email": "", - "photourl": "URL de avatar" - }, - "labels": { - "actions": "", - "changepassword": "", - "profileinfo": "" - }, - "successess": { - "passwordchanged": "" - } - }, - "users": { - "errors": { - "signinerror": { - "auth/user-disabled": "", - "auth/user-not-found": "", - "auth/wrong-password": "" - } - } - }, - "vehicles": { - "errors": { - "deleting": "", - "noaccess": "El vehículo no existe o usted no tiene acceso a él.", - "selectexistingornew": "", - "validation": "Asegúrese de que todos los campos se ingresen correctamente.", - "validationtitle": "Error de validacion" - }, - "fields": { - "description": "Descripcion del vehiculo", - "notes": "", - "plate_no": "Placa", - "plate_st": "Jurisdicción de placas", - "trim_color": "Recortar color", - "v_bstyle": "Tipo de cuerpo", - "v_color": "Color", - "v_cond": "condición", - "v_engine": "Motor", - "v_make_desc": "Hacer", - "v_makecode": "Hacer código", - "v_mldgcode": "Código de moldeo", - "v_model_desc": "Modelo", - "v_model_yr": "año", - "v_options": "Opciones", - "v_paint_codes": "Códigos de pintura", - "v_prod_dt": "Fecha de producción", - "v_stage": "Escenario", - "v_tone": "Tono", - "v_trimcode": "Código de recorte", - "v_type": "Tipo", - "v_vin": "V.I.N." - }, - "forms": { - "detail": "", - "misc": "", - "registration": "" - }, - "labels": { - "deleteconfirm": "", - "fromvehicle": "", - "novehinfo": "", - "relatedjobs": "", - "updatevehicle": "" - }, - "successes": { - "delete": "", - "save": "Vehículo guardado con éxito." - } - }, - "vendors": { - "actions": { - "addtophonebook": "", - "new": "Nuevo vendedor", - "newpreferredmake": "" - }, - "errors": { - "deleting": "Se encontró un error al eliminar el proveedor.", - "saving": "Se encontró un error al guardar el proveedor." - }, - "fields": { - "active": "", - "am": "", - "city": "ciudad", - "cost_center": "Centro de costos", - "country": "País", - "discount": "% De descuento", - "display_name": "Nombre para mostrar", - "dmsid": "", - "due_date": "Fecha de vencimiento del pago", - "email": "Email de contacto", - "favorite": "¿Favorito?", - "lkq": "", - "make": "", - "name": "Nombre del vendedor", - "oem": "", - "phone": "", - "prompt_discount": "Descuento pronto", - "state": "Provincia del estado", - "street1": "calle", - "street2": "Dirección 2", - "taxid": "Identificación del impuesto", - "terms": "Términos de pago", - "zip": "código postal" - }, - "labels": { - "noneselected": "Ningún vendedor está seleccionado.", - "preferredmakes": "", - "search": "Escriba el nombre de un proveedor" - }, - "successes": { - "deleted": "Proveedor eliminado correctamente.", - "saved": "Proveedor guardado con éxito." - }, - "validation": { - "unique_vendor_name": "" - } - } - } + "translation": { + "allocations": { + "actions": { + "assign": "Asignar" + }, + "errors": { + "deleting": "", + "saving": "", + "validation": "" + }, + "fields": { + "employee": "Asignado a" + }, + "successes": { + "deleted": "", + "save": "" + } + }, + "appointments": { + "actions": { + "block": "", + "calculate": "", + "cancel": "Cancelar", + "intake": "Consumo", + "new": "Nueva cita", + "preview": "", + "reschedule": "Reprogramar", + "sendreminder": "", + "unblock": "", + "viewjob": "Ver trabajo" + }, + "errors": { + "blocking": "", + "canceling": "Error al cancelar la cita. {{message}}", + "saving": "Error al programar la cita. {{message}}" + }, + "fields": { + "alt_transport": "", + "color": "", + "end": "", + "note": "", + "start": "", + "time": "", + "title": "Título" + }, + "labels": { + "arrivedon": "Llegado el:", + "arrivingjobs": "", + "blocked": "", + "cancelledappointment": "Cita cancelada para:", + "completingjobs": "", + "dataconsistency": "", + "expectedjobs": "", + "expectedprodhrs": "", + "history": "", + "inproduction": "", + "manualevent": "", + "noarrivingjobs": "", + "nocompletingjobs": "", + "nodateselected": "No se ha seleccionado ninguna fecha.", + "priorappointments": "Nombramientos previos", + "reminder": "", + "scheduledfor": "Cita programada para:", + "severalerrorsfound": "", + "smartscheduling": "", + "smspaymentreminder": "", + "suggesteddates": "" + }, + "successes": { + "canceled": "Cita cancelada con éxito.", + "created": "Cita programada con éxito.", + "saved": "" + } + }, + "associations": { + "actions": { + "activate": "Activar" + }, + "fields": { + "active": "¿Activo?", + "shopname": "Nombre de tienda" + }, + "labels": { + "actions": "Comportamiento" + } + }, + "audit": { + "fields": { + "cc": "", + "contents": "", + "created": "", + "operation": "", + "status": "", + "subject": "", + "to": "", + "useremail": "", + "values": "" + } + }, + "audit_trail": { + "messages": { + "admin_job_remove_from_ar": "", + "admin_jobmarkexported": "", + "admin_jobmarkforreexport": "", + "admin_jobuninvoice": "", + "admin_jobunvoid": "", + "alerttoggle": "", + "appointmentcancel": "", + "appointmentinsert": "", + "assignedlinehours": "", + "billdeleted": "", + "billposted": "", + "billupdated": "", + "failedpayment": "", + "jobassignmentchange": "", + "jobassignmentremoved": "", + "jobchecklist": "", + "jobclosedwithbypass": "", + "jobconverted": "", + "jobdelivery": "", + "jobexported": "", + "jobfieldchanged": "", + "jobimported": "", + "jobinproductionchange": "", + "jobintake": "", + "jobinvoiced": "", + "jobioucreated": "", + "jobmodifylbradj": "", + "jobnoteadded": "", + "jobnotedeleted": "", + "jobnoteupdated": "", + "jobspartsorder": "", + "jobspartsreturn": "", + "jobstatuschange": "", + "jobsupplement": "", + "jobsuspend": "", + "jobvoid": "", + "tasks_completed": "", + "tasks_created": "", + "tasks_deleted": "", + "tasks_uncompleted": "", + "tasks_undeleted": "", + "tasks_updated": "" + } + }, + "billlines": { + "actions": { + "newline": "" + }, + "fields": { + "actual_cost": "", + "actual_price": "", + "cost_center": "", + "federal_tax_applicable": "", + "jobline": "", + "line_desc": "", + "local_tax_applicable": "", + "location": "", + "quantity": "", + "state_tax_applicable": "" + }, + "labels": { + "deductedfromlbr": "", + "entered": "", + "from": "", + "mod_lbr_adjustment": "", + "other": "", + "reconciled": "", + "unreconciled": "" + }, + "validation": { + "atleastone": "" + } + }, + "bills": { + "actions": { + "deductallhours": "", + "edit": "", + "receive": "", + "return": "" + }, + "errors": { + "creating": "", + "deleting": "", + "existinginventoryline": "", + "exporting": "", + "exporting-partner": "", + "invalidro": "", + "invalidvendor": "", + "validation": "" + }, + "fields": { + "allpartslocation": "", + "date": "", + "exported": "", + "federal_tax_rate": "", + "invoice_number": "", + "is_credit_memo": "", + "is_credit_memo_short": "", + "local_tax_rate": "", + "ro_number": "", + "state_tax_rate": "", + "total": "", + "vendor": "", + "vendorname": "" + }, + "labels": { + "actions": "", + "bill_lines": "", + "bill_total": "", + "billcmtotal": "", + "bills": "", + "calculatedcreditsnotreceived": "", + "creditsnotreceived": "", + "creditsreceived": "", + "dedfromlbr": "", + "deleteconfirm": "", + "discrepancy": "", + "discrepwithcms": "", + "discrepwithlbradj": "", + "editadjwarning": "", + "entered_total": "", + "enteringcreditmemo": "", + "federal_tax": "", + "federal_tax_exempt": "", + "generatepartslabel": "", + "iouexists": "", + "local_tax": "", + "markexported": "", + "markforreexport": "", + "new": "", + "nobilllines": "", + "noneselected": "", + "onlycmforinvoiced": "", + "printlabels": "", + "retailtotal": "", + "returnfrombill": "", + "savewithdiscrepancy": "", + "state_tax": "", + "subtotal": "", + "totalreturns": "" + }, + "successes": { + "created": "", + "deleted": "", + "exported": "", + "markexported": "", + "reexport": "" + }, + "validation": { + "closingperiod": "", + "inventoryquantity": "", + "manualinhouse": "", + "unique_invoice_number": "" + } + }, + "bodyshop": { + "actions": { + "add_task_preset": "", + "addapptcolor": "", + "addbucket": "", + "addpartslocation": "", + "addpartsrule": "", + "addspeedprint": "", + "addtemplate": "", + "newlaborrate": "", + "newsalestaxcode": "", + "newstatus": "", + "testrender": "" + }, + "errors": { + "creatingdefaultview": "", + "loading": "No se pueden cargar los detalles de la tienda. Por favor llame al soporte técnico.", + "saving": "" + }, + "fields": { + "ReceivableCustomField": "", + "address1": "", + "address2": "", + "appt_alt_transport": "", + "appt_colors": { + "color": "", + "label": "" + }, + "appt_length": "", + "attach_pdf_to_email": "", + "batchid": "", + "bill_allow_post_to_closed": "", + "bill_federal_tax_rate": "", + "bill_local_tax_rate": "", + "bill_state_tax_rate": "", + "city": "", + "closingperiod": "", + "companycode": "", + "country": "", + "dailybodytarget": "", + "dailypainttarget": "", + "default_adjustment_rate": "", + "deliver": { + "require_actual_delivery_date": "", + "templates": "" + }, + "dms": { + "apcontrol": "", + "appostingaccount": "", + "cashierid": "", + "default_journal": "", + "disablebillwip": "", + "disablecontactvehiclecreation": "", + "dms_acctnumber": "", + "dms_control_override": "", + "dms_wip_acctnumber": "", + "generic_customer_number": "", + "itc_federal": "", + "itc_local": "", + "itc_state": "", + "mappingname": "", + "sendmaterialscosting": "", + "srcco": "" + }, + "email": "", + "enforce_class": "", + "enforce_conversion_category": "", + "enforce_conversion_csr": "", + "enforce_referral": "", + "federal_tax_id": "", + "ignoreblockeddays": "", + "inhousevendorid": "", + "insurance_vendor_id": "", + "intake": { + "next_contact_hours": "", + "templates": "" + }, + "intellipay_config": { + "cash_discount_percentage": "", + "enable_cash_discount": "" + }, + "invoice_federal_tax_rate": "", + "invoice_local_tax_rate": "", + "invoice_state_tax_rate": "", + "jc_hourly_rates": { + "mapa": "", + "mash": "" + }, + "last_name_first": "", + "lastnumberworkingdays": "", + "localmediaserverhttp": "", + "localmediaservernetwork": "", + "localmediatoken": "", + "logo_img_footer_margin": "", + "logo_img_header_margin": "", + "logo_img_path": "", + "logo_img_path_height": "", + "logo_img_path_width": "", + "md_categories": "", + "md_ccc_rates": "", + "md_classes": "", + "md_ded_notes": "", + "md_email_cc": "", + "md_from_emails": "", + "md_functionality_toggles": { + "parts_queue_toggle": "" + }, + "md_hour_split": { + "paint": "", + "prep": "" + }, + "md_ins_co": { + "city": "", + "name": "", + "private": "", + "state": "", + "street1": "", + "street2": "", + "zip": "" + }, + "md_jobline_presets": "", + "md_lost_sale_reasons": "", + "md_parts_order_comment": "", + "md_parts_scan": { + "expression": "", + "flags": "" + }, + "md_payment_types": "", + "md_referral_sources": "", + "md_ro_guard": { + "enabled": "", + "enforce_ar": "", + "enforce_bills": "", + "enforce_cm": "", + "enforce_labor": "", + "enforce_ppd": "", + "enforce_profit": "", + "enforce_sublet": "", + "masterbypass": "", + "totalgppercent_minimum": "" + }, + "md_tasks_presets": { + "enable_tasks": "", + "hourstype": "", + "memo": "", + "name": "", + "nextstatus": "", + "percent": "", + "use_approvals": "" + }, + "messaginglabel": "", + "messagingtext": "", + "noteslabel": "", + "notestext": "", + "partslocation": "", + "phone": "", + "prodtargethrs": "", + "rbac": { + "accounting": { + "exportlog": "", + "payables": "", + "payments": "", + "receivables": "" + }, + "bills": { + "delete": "", + "enter": "", + "list": "", + "reexport": "", + "view": "" + }, + "contracts": { + "create": "", + "detail": "", + "list": "" + }, + "courtesycar": { + "create": "", + "detail": "", + "list": "" + }, + "csi": { + "export": "", + "page": "" + }, + "employee_teams": { + "page": "" + }, + "employees": { + "page": "" + }, + "inventory": { + "delete": "", + "list": "" + }, + "jobs": { + "admin": "", + "available-list": "", + "checklist-view": "", + "close": "", + "create": "", + "deliver": "", + "detail": "", + "intake": "", + "list-active": "", + "list-all": "", + "list-ready": "", + "partsqueue": "", + "void": "" + }, + "owners": { + "detail": "", + "list": "" + }, + "payments": { + "enter": "", + "list": "" + }, + "phonebook": { + "edit": "", + "view": "" + }, + "production": { + "board": "", + "list": "" + }, + "schedule": { + "view": "" + }, + "scoreboard": { + "view": "" + }, + "shiftclock": { + "view": "" + }, + "shop": { + "config": "", + "dashboard": "", + "rbac": "", + "reportcenter": "", + "templates": "", + "vendors": "" + }, + "temporarydocs": { + "view": "" + }, + "timetickets": { + "edit": "", + "editcommitted": "", + "enter": "", + "list": "", + "shiftedit": "" + }, + "ttapprovals": { + "approve": "", + "view": "" + }, + "users": { + "editaccess": "" + } + }, + "responsibilitycenter": "", + "responsibilitycenter_accountdesc": "", + "responsibilitycenter_accountitem": "", + "responsibilitycenter_accountname": "", + "responsibilitycenter_accountnumber": "", + "responsibilitycenter_rate": "", + "responsibilitycenter_tax_rate": "", + "responsibilitycenter_tax_sur": "", + "responsibilitycenter_tax_thres": "", + "responsibilitycenter_tax_tier": "", + "responsibilitycenter_tax_type": "", + "responsibilitycenters": { + "ap": "", + "ar": "", + "ats": "", + "federal_tax": "", + "federal_tax_itc": "", + "gst_override": "", + "invoiceexemptcode": "", + "itemexemptcode": "", + "la1": "", + "la2": "", + "la3": "", + "la4": "", + "laa": "", + "lab": "", + "lad": "", + "lae": "", + "laf": "", + "lag": "", + "lam": "", + "lar": "", + "las": "", + "lau": "", + "local_tax": "", + "mapa": "", + "mash": "", + "paa": "", + "pac": "", + "pag": "", + "pal": "", + "pam": "", + "pan": "", + "pao": "", + "pap": "", + "par": "", + "pas": "", + "pasl": "", + "refund": "", + "sales_tax_codes": { + "code": "", + "description": "", + "federal": "", + "local": "", + "state": "" + }, + "state_tax": "", + "tow": "" + }, + "schedule_end_time": "", + "schedule_start_time": "", + "shopname": "", + "speedprint": { + "id": "", + "label": "", + "templates": "" + }, + "ss_configuration": { + "dailyhrslimit": "" + }, + "ssbuckets": { + "color": "", + "gte": "", + "id": "", + "label": "", + "lt": "", + "target": "" + }, + "state": "", + "state_tax_id": "", + "status": "", + "statuses": { + "active_statuses": "", + "additional_board_statuses": "", + "color": "", + "default_arrived": "", + "default_bo": "", + "default_canceled": "", + "default_completed": "", + "default_delivered": "", + "default_exported": "", + "default_imported": "", + "default_invoiced": "", + "default_ordered": "", + "default_quote": "", + "default_received": "", + "default_returned": "", + "default_scheduled": "", + "default_void": "", + "open_statuses": "", + "post_production_statuses": "", + "pre_production_statuses": "", + "production_colors": "", + "production_statuses": "", + "ready_statuses": "" + }, + "target_touchtime": "", + "timezone": "", + "tt_allow_post_to_invoiced": "", + "tt_enforce_hours_for_tech_console": "", + "use_fippa": "", + "use_paint_scale_data": "", + "uselocalmediaserver": "", + "website": "", + "zip_post": "" + }, + "labels": { + "2tiername": "", + "2tiersetup": "", + "2tiersource": "", + "accountingsetup": "", + "accountingtiers": "", + "alljobstatuses": "", + "allopenjobstatuses": "", + "apptcolors": "", + "businessinformation": "", + "checklists": "", + "csiq": "", + "customtemplates": "", + "defaultcostsmapping": "", + "defaultprofitsmapping": "", + "deliverchecklist": "", + "dms": { + "cdk": { + "controllist": "", + "payers": "" + }, + "cdk_dealerid": "", + "costsmapping": "", + "dms_allocations": "", + "pbs_serialnumber": "", + "profitsmapping": "", + "title": "" + }, + "emaillater": "", + "employee_teams": "", + "employees": "", + "estimators": "", + "filehandlers": "", + "insurancecos": "", + "intakechecklist": "", + "intellipay": "", + "intellipay_cash_discount": "", + "jobstatuses": "", + "laborrates": "", + "licensing": "", + "md_parts_scan": "", + "md_ro_guard": "", + "md_tasks_presets": "", + "md_to_emails": "", + "md_to_emails_emails": "", + "messagingpresets": "", + "notemplatesavailable": "", + "notespresets": "", + "orderstatuses": "", + "partslocations": "", + "partsscan": "", + "printlater": "", + "qbo": "", + "qbo_departmentid": "", + "qbo_usa": "", + "rbac": "", + "responsibilitycenters": { + "costs": "", + "profits": "", + "sales_tax_codes": "", + "tax_accounts": "", + "title": "", + "ttl_adjustment": "", + "ttl_tax_adjustment": "" + }, + "roguard": { + "title": "" + }, + "scheduling": "", + "scoreboardsetup": "", + "shopinfo": "", + "speedprint": "", + "ssbuckets": "", + "systemsettings": "", + "task-presets": "", + "workingdays": "" + }, + "successes": { + "areyousure": "", + "defaultviewcreated": "", + "save": "", + "unsavedchanges": "" + }, + "validation": { + "centermustexist": "", + "larsplit": "", + "useremailmustexist": "" + } + }, + "checklist": { + "actions": { + "printall": "" + }, + "errors": { + "complete": "", + "nochecklist": "" + }, + "labels": { + "addtoproduction": "", + "allow_text_message": "", + "checklist": "", + "printpack": "", + "removefromproduction": "" + }, + "successes": { + "completed": "" + } + }, + "contracts": { + "actions": { + "changerate": "", + "convertoro": "", + "decodelicense": "", + "find": "", + "printcontract": "", + "senddltoform": "" + }, + "errors": { + "fetchingjobinfo": "", + "returning": "", + "saving": "", + "selectjobandcar": "" + }, + "fields": { + "actax": "", + "actualreturn": "", + "agreementnumber": "", + "cc_cardholder": "", + "cc_expiry": "", + "cc_num": "", + "cleanupcharge": "", + "coverage": "", + "dailyfreekm": "", + "dailyrate": "", + "damage": "", + "damagewaiver": "", + "driver": "", + "driver_addr1": "", + "driver_addr2": "", + "driver_city": "", + "driver_dlexpiry": "", + "driver_dlnumber": "", + "driver_dlst": "", + "driver_dob": "", + "driver_fn": "", + "driver_ln": "", + "driver_ph1": "", + "driver_state": "", + "driver_zip": "", + "excesskmrate": "", + "federaltax": "", + "fuelin": "", + "fuelout": "", + "kmend": "", + "kmstart": "", + "length": "", + "localtax": "", + "refuelcharge": "", + "scheduledreturn": "", + "start": " ", + "statetax": "", + "status": "" + }, + "labels": { + "agreement": "", + "availablecars": "", + "cardueforservice": "", + "convertform": { + "applycleanupcharge": "", + "refuelqty": "" + }, + "correctdataonform": "", + "dateinpast": "", + "dlexpirebeforereturn": "", + "driverinformation": "", + "findcontract": "", + "findermodal": "", + "insuranceexpired": "", + "noteconvertedfrom": "", + "populatefromjob": "", + "rates": "", + "time": "", + "vehicle": "", + "waitingforscan": "" + }, + "status": { + "new": "", + "out": "", + "returned": "" + }, + "successes": { + "saved": "" + } + }, + "courtesycars": { + "actions": { + "new": "", + "return": "" + }, + "errors": { + "saving": "" + }, + "fields": { + "color": "", + "dailycost": "", + "damage": "", + "fleetnumber": "", + "fuel": "", + "insuranceexpires": "", + "leaseenddate": "", + "make": "", + "mileage": "", + "model": "", + "nextservicedate": "", + "nextservicekm": "", + "notes": "", + "plate": "", + "purchasedate": "", + "readiness": "", + "registrationexpires": "", + "serviceenddate": "", + "servicestartdate": "", + "status": "", + "vin": "", + "year": "" + }, + "labels": { + "courtesycar": "", + "fuel": { + "12": "", + "14": "", + "18": "", + "34": "", + "38": "", + "58": "", + "78": "", + "empty": "", + "full": "" + }, + "outwith": "", + "return": "", + "status": "", + "uniquefleet": "", + "usage": "", + "vehicle": "" + }, + "readiness": { + "notready": "", + "ready": "" + }, + "status": { + "in": "", + "inservice": "", + "leasereturn": "", + "out": "", + "sold": "", + "unavailable": "" + }, + "successes": { + "saved": "" + } + }, + "csi": { + "actions": { + "activate": "" + }, + "errors": { + "creating": "", + "notconfigured": "", + "notfoundsubtitle": "", + "notfoundtitle": "", + "surveycompletesubtitle": "", + "surveycompletetitle": "" + }, + "fields": { + "completedon": "", + "created_at": "", + "surveyid": "", + "validuntil": "" + }, + "labels": { + "copyright": "", + "greeting": "", + "intro": "", + "nologgedinuser": "", + "nologgedinuser_sub": "", + "noneselected": "", + "title": "" + }, + "successes": { + "created": "", + "submitted": "", + "submittedsub": "" + } + }, + "dashboard": { + "actions": { + "addcomponent": "" + }, + "errors": { + "refreshrequired": "", + "updatinglayout": "" + }, + "labels": { + "bodyhrs": "", + "dollarsinproduction": "", + "phone": "", + "prodhrs": "", + "refhrs": "" + }, + "titles": { + "joblifecycle": "", + "labhours": "", + "larhours": "", + "monthlyemployeeefficiency": "", + "monthlyjobcosting": "", + "monthlylaborsales": "", + "monthlypartssales": "", + "monthlyrevenuegraph": "", + "prodhrssummary": "", + "productiondollars": "", + "productionhours": "", + "projectedmonthlysales": "", + "scheduledindate": "", + "scheduledintoday": "", + "scheduledoutdate": "", + "scheduledouttoday": "", + "tasks": "" + } + }, + "dms": { + "errors": { + "alreadyexported": "" + }, + "labels": { + "refreshallocations": "" + } + }, + "documents": { + "actions": { + "delete": "", + "download": "", + "reassign": "", + "selectallimages": "", + "selectallotherdocuments": "" + }, + "errors": { + "deletes3": "Error al eliminar el documento del almacenamiento.", + "deleting": "", + "deleting_cloudinary": "", + "getpresignurl": "Error al obtener la URL prescrita para el documento. {{message}}", + "insert": "Incapaz de cargar el archivo. {{message}}", + "nodocuments": "No hay documentos", + "updating": "" + }, + "labels": { + "confirmdelete": "", + "doctype": "", + "newjobid": "", + "openinexplorer": "", + "optimizedimage": "", + "reassign_limitexceeded": "", + "reassign_limitexceeded_title": "", + "storageexceeded": "", + "storageexceeded_title": "", + "upload": "Subir", + "upload_limitexceeded": "", + "upload_limitexceeded_title": "", + "uploading": "", + "usage": "" + }, + "successes": { + "delete": "Documento eliminado con éxito.", + "edituploaded": "", + "insert": "Documento cargado con éxito.", + "updated": "" + } + }, + "emails": { + "errors": { + "notsent": "Correo electrónico no enviado Se encontró un error al enviar {{message}}" + }, + "fields": { + "cc": "", + "from": "", + "subject": "", + "to": "" + }, + "labels": { + "attachments": "", + "documents": "", + "emailpreview": "", + "generatingemail": "", + "pdfcopywillbeattached": "", + "preview": "" + }, + "successes": { + "sent": "Correo electrónico enviado con éxito." + } + }, + "employee_teams": { + "actions": { + "new": "", + "newmember": "" + }, + "fields": { + "active": "", + "employeeid": "", + "max_load": "", + "name": "", + "percentage": "" + } + }, + "employees": { + "actions": { + "addvacation": "", + "new": "Nuevo empleado", + "newrate": "" + }, + "errors": { + "delete": "Se encontró un error al eliminar al empleado. {{message}}", + "save": "Se encontró un error al salvar al empleado. {{message}}", + "validation": "Por favor verifique todos los campos.", + "validationtitle": "No se puede salvar al empleado." + }, + "fields": { + "active": "¿Activo?", + "base_rate": "Tasa básica", + "cost_center": "Centro de costos", + "employee_number": "Numero de empleado", + "external_id": "", + "first_name": "Nombre de pila", + "flat_rate": "Tarifa plana (deshabilitado es tiempo recto)", + "hire_date": "Fecha de contratación", + "last_name": "Apellido", + "pin": "", + "rate": "", + "termination_date": "Fecha de conclusión", + "user_email": "", + "vacation": { + "end": "", + "length": "", + "start": "" + } + }, + "labels": { + "actions": "", + "active": "", + "endmustbeafterstart": "", + "flat_rate": "", + "inactive": "", + "name": "", + "rate_type": "", + "status": "", + "straight_time": "" + }, + "successes": { + "delete": "Empleado eliminado con éxito.", + "save": "Empleado guardado con éxito.", + "vacationadded": "" + }, + "validation": { + "unique_employee_number": "" + } + }, + "eula": { + "buttons": { + "accept": "Accept EULA" + }, + "content": { + "never_scrolled": "You must scroll to the bottom of the Terms and Conditions before accepting." + }, + "errors": { + "acceptance": { + "description": "Something went wrong while accepting the EULA. Please try again.", + "message": "Eula Acceptance Error" + } + }, + "labels": { + "accepted_terms": "I accept the terms and conditions of this agreement.", + "address": "Address", + "business_name": "Legal Business Name", + "date_accepted": "Date Accepted", + "first_name": "First Name", + "last_name": "Last Name", + "phone_number": "Phone Number" + }, + "messages": { + "accepted_terms": "Please accept the terms and conditions of this agreement.", + "business_name": "Please enter your legal business name.", + "date_accepted": "Please enter Today's Date.", + "first_name": "Please enter your first name.", + "last_name": "Please enter your last name.", + "phone_number": "Please enter your phone number." + }, + "titles": { + "modal": "Terms and Conditions", + "upper_card": "Acknowledgement" + } + }, + "exportlogs": { + "fields": { + "createdat": "" + }, + "labels": { + "attempts": "", + "priorsuccesfulexport": "" + } + }, + "general": { + "actions": { + "add": "", + "autoupdate": "", + "calculate": "", + "cancel": "", + "clear": "", + "close": "", + "copied": "", + "copylink": "", + "create": "", + "defaults": "defaults", + "delay": "", + "delete": "Borrar", + "deleteall": "", + "deselectall": "", + "download": "", + "edit": "Editar", + "login": "", + "next": "", + "previous": "", + "print": "", + "refresh": "", + "remove": "", + "remove_alert": "", + "reset": " Restablecer a original.", + "resetpassword": "", + "save": "Salvar", + "saveandnew": "", + "saveas": "", + "selectall": "", + "send": "", + "sendbysms": "", + "senderrortosupport": "", + "submit": "", + "tryagain": "", + "view": "", + "viewreleasenotes": "" + }, + "errors": { + "fcm": "", + "notfound": "", + "sizelimit": "" + }, + "itemtypes": { + "contract": "", + "courtesycar": "", + "job": "", + "owner": "", + "vehicle": "" + }, + "labels": { + "actions": "Comportamiento", + "areyousure": "", + "barcode": "código de barras", + "cancel": "", + "clear": "", + "confirmpassword": "", + "created_at": "", + "date": "", + "datetime": "", + "email": "", + "errors": "", + "excel": "", + "exceptiontitle": "", + "friday": "", + "globalsearch": "", + "help": "", + "hours": "", + "in": "en", + "instanceconflictext": "", + "instanceconflictitle": "", + "item": "", + "label": "", + "loading": "Cargando...", + "loadingapp": "Cargando {{app}}", + "loadingshop": "Cargando datos de la tienda ...", + "loggingin": "Iniciando sesión ...", + "markedexported": "", + "media": "", + "message": "", + "monday": "", + "na": "N / A", + "newpassword": "", + "no": "", + "nointernet": "", + "nointernet_sub": "", + "none": "", + "out": "Afuera", + "password": "", + "passwordresetsuccess": "", + "passwordresetsuccess_sub": "", + "passwordresetvalidatesuccess": "", + "passwordresetvalidatesuccess_sub": "", + "passwordsdonotmatch": "", + "print": "", + "refresh": "", + "reports": "", + "required": "", + "saturday": "", + "search": "Buscar...", + "searchresults": "", + "selectdate": "", + "sendagain": "", + "sendby": "", + "signin": "", + "sms": "", + "status": "", + "sub_status": { + "expired": "" + }, + "successful": "", + "sunday": "", + "text": "", + "thursday": "", + "total": "", + "totals": "", + "tuesday": "", + "tvmode": "", + "unknown": "Desconocido", + "unsavedchanges": "", + "username": "", + "view": "", + "wednesday": "", + "yes": "" + }, + "languages": { + "english": "Inglés", + "french": "francés", + "spanish": "español" + }, + "messages": { + "exception": "", + "newversionmessage": "", + "newversiontitle": "", + "noacctfilepath": "", + "nofeatureaccess": "", + "noshop": "", + "notfoundsub": "", + "notfoundtitle": "", + "partnernotrunning": "", + "rbacunauth": "", + "unsavedchanges": "Usted tiene cambios no guardados.", + "unsavedchangespopup": "" + }, + "validation": { + "invalidemail": "Por favor introduzca una dirección de correo electrónico válida.", + "invalidphone": "", + "required": "Este campo es requerido." + } + }, + "help": { + "actions": { + "connect": "" + }, + "labels": { + "codeplacholder": "", + "rescuedesc": "", + "rescuetitle": "" + } + }, + "intake": { + "labels": { + "printpack": "" + } + }, + "inventory": { + "actions": { + "addtoinventory": "", + "addtoro": "", + "consumefrominventory": "", + "edit": "", + "new": "" + }, + "errors": { + "inserting": "" + }, + "fields": { + "comment": "", + "manualinvoicenumber": "", + "manualvendor": "" + }, + "labels": { + "consumedbyjob": "", + "deleteconfirm": "", + "frombillinvoicenumber": "", + "fromvendor": "", + "inventory": "", + "showall": "", + "showavailable": "" + }, + "successes": { + "deleted": "", + "inserted": "", + "updated": "" + } + }, + "job_lifecycle": { + "columns": { + "duration": "", + "end": "", + "human_readable": "", + "percentage": "", + "relative_end": "", + "relative_start": "", + "start": "", + "status": "", + "status_count": "", + "value": "" + }, + "content": { + "calculated_based_on": "", + "current_status_accumulated_time": "", + "data_unavailable": "", + "jobs_in_since": "", + "legend_title": "", + "loading": "", + "not_available": "", + "previous_status_accumulated_time": "", + "title": "", + "title_durations": "", + "title_loading": "", + "title_transitions": "" + }, + "errors": { + "fetch": "Error al obtener los datos del ciclo de vida del trabajo" + }, + "titles": { + "dashboard": "", + "top_durations": "" + } + }, + "job_payments": { + "buttons": { + "create_short_link": "", + "goback": "", + "proceedtopayment": "", + "refundpayment": "" + }, + "notifications": { + "error": { + "description": "", + "openingip": "", + "title": "" + } + }, + "titles": { + "amount": "", + "dateOfPayment": "", + "descriptions": "", + "hint": "", + "payer": "", + "payername": "", + "paymentid": "", + "paymentnum": "", + "paymenttype": "", + "refundamount": "", + "transactionid": "" + } + }, + "joblines": { + "actions": { + "assign_team": "", + "converttolabor": "", + "dispatchparts": "", + "new": "" + }, + "errors": { + "creating": "", + "updating": "" + }, + "fields": { + "act_price": "Precio actual", + "act_price_before_ppc": "", + "adjustment": "", + "ah_detail_line": "", + "amount": "", + "assigned_team": "", + "assigned_team_name": "", + "create_ppc": "", + "db_price": "Precio de base de datos", + "lbr_types": { + "LA1": "", + "LA2": "", + "LA3": "", + "LA4": "", + "LAA": "", + "LAB": "", + "LAD": "", + "LAE": "", + "LAF": "", + "LAG": "", + "LAM": "", + "LAR": "", + "LAS": "", + "LAU": "" + }, + "line_desc": "Descripción de línea", + "line_ind": "S#", + "line_no": "", + "location": "", + "mod_lb_hrs": "Horas laborales", + "mod_lbr_ty": "Tipo de trabajo", + "notes": "", + "oem_partno": "OEM parte #", + "op_code_desc": "", + "part_qty": "", + "part_type": "Tipo de parte", + "part_types": { + "CCC": "", + "CCD": "", + "CCDR": "", + "CCF": "", + "CCM": "", + "PAA": "", + "PAC": "", + "PAE": "", + "PAG": "", + "PAL": "", + "PAM": "", + "PAN": "", + "PAO": "", + "PAP": "", + "PAR": "", + "PAS": "", + "PASL": "" + }, + "profitcenter_labor": "", + "profitcenter_part": "", + "prt_dsmk_m": "", + "prt_dsmk_p": "", + "status": "Estado", + "tax_part": "", + "total": "", + "unq_seq": "Seq #" + }, + "labels": { + "adjustmenttobeadded": "", + "billref": "", + "convertedtolabor": "", + "edit": "Línea de edición", + "ioucreated": "", + "new": "Nueva línea", + "nostatus": "", + "presets": "" + }, + "successes": { + "created": "", + "saved": "", + "updated": "" + }, + "validations": { + "ahdetailonlyonuserdefinedtypes": "", + "hrsrequirediflbrtyp": "", + "requiredifparttype": "", + "zeropriceexistingpart": "" + } + }, + "jobs": { + "actions": { + "addDocuments": "Agregar documentos de trabajo", + "addNote": "Añadir la nota", + "addtopartsqueue": "", + "addtoproduction": "", + "addtoscoreboard": "", + "allocate": "", + "autoallocate": "", + "changefilehandler": "", + "changelaborrate": "", + "changestatus": "Cambiar Estado", + "changestimator": "", + "convert": "Convertir", + "createiou": "", + "deliver": "", + "dms": { + "addpayer": "", + "createnewcustomer": "", + "findmakemodelcode": "", + "getmakes": "", + "labels": { + "refreshallocations": "" + }, + "post": "", + "refetchmakesmodels": "", + "usegeneric": "", + "useselected": "" + }, + "dmsautoallocate": "", + "export": "", + "exportcustdata": "", + "exportselected": "", + "filterpartsonly": "", + "generatecsi": "", + "gotojob": "", + "intake": "", + "manualnew": "", + "mark": "", + "markasexported": "", + "markpstexempt": "", + "markpstexemptconfirm": "", + "postbills": "Contabilizar facturas", + "printCenter": "Centro de impresión", + "recalculate": "", + "reconcile": "", + "removefromproduction": "", + "schedule": "Programar", + "sendcsi": "", + "sendpartspricechange": "", + "sendtodms": "", + "sync": "", + "taxprofileoverride": "", + "taxprofileoverride_confirm": "", + "uninvoice": "", + "unvoid": "", + "viewchecklist": "", + "viewdetail": "" + }, + "errors": { + "addingtoproduction": "", + "cannotintake": "", + "closing": "", + "creating": "", + "deleted": "Error al eliminar el trabajo.", + "exporting": "", + "exporting-partner": "", + "invoicing": "", + "noaccess": "Este trabajo no existe o no tiene acceso a él.", + "nodamage": "", + "nodates": "No hay fechas especificadas para este trabajo.", + "nofinancial": "", + "nojobselected": "No hay trabajo seleccionado.", + "noowner": "Ningún propietario asociado.", + "novehicle": "No hay vehículo asociado.", + "partspricechange": "", + "saving": "Se encontró un error al guardar el registro.", + "scanimport": "", + "totalscalc": "", + "updating": "", + "validation": "Asegúrese de que todos los campos se ingresen correctamente.", + "validationtitle": "Error de validacion", + "voiding": "" + }, + "fields": { + "active_tasks": "", + "actual_completion": "Realización real", + "actual_delivery": "Entrega real", + "actual_in": "Real en", + "adjustment_bottom_line": "Ajustes", + "adjustmenthours": "", + "alt_transport": "", + "area_of_damage_impact": { + "10": "", + "11": "", + "12": "", + "13": "", + "14": "", + "15": "", + "16": "", + "25": "", + "26": "", + "27": "", + "28": "", + "34": "", + "01": "", + "02": "", + "03": "", + "04": "", + "05": "", + "06": "", + "07": "", + "08": "", + "09": "" + }, + "auto_add_ats": "", + "ca_bc_pvrt": "", + "ca_customer_gst": "", + "ca_gst_registrant": "", + "category": "", + "ccc": "", + "ccd": "", + "ccdr": "", + "ccf": "", + "ccm": "", + "cieca_id": "CIECA ID", + "cieca_pfl": { + "lbr_adjp": "", + "lbr_tax_in": "", + "lbr_taxp": "", + "lbr_tx_in1": "", + "lbr_tx_in2": "", + "lbr_tx_in3": "", + "lbr_tx_in4": "", + "lbr_tx_in5": "" + }, + "cieca_pfo": { + "stor_t_in1": "", + "stor_t_in2": "", + "stor_t_in3": "", + "stor_t_in4": "", + "stor_t_in5": "", + "tow_t_in1": "", + "tow_t_in2": "", + "tow_t_in3": "", + "tow_t_in4": "", + "tow_t_in5": "" + }, + "claim_total": "Reclamar total", + "class": "", + "clm_no": "Reclamación #", + "clm_total": "Reclamar total", + "comment": "", + "customerowing": "Cliente debido", + "date_estimated": "Fecha estimada", + "date_exported": "Exportado", + "date_invoiced": "Facturado", + "date_last_contacted": "", + "date_lost_sale": "", + "date_next_contact": "", + "date_open": "Abierto", + "date_rentalresp": "", + "date_repairstarted": "", + "date_scheduled": "Programado", + "date_towin": "", + "date_void": "", + "ded_amt": "Deducible", + "ded_note": "", + "ded_status": "Estado deducible", + "depreciation_taxes": "Depreciación / Impuestos", + "dms": { + "address": "", + "amount": "", + "center": "", + "control_type": { + "account_number": "" + }, + "cost": "", + "cost_dms_acctnumber": "", + "dms_make": "", + "dms_model": "", + "dms_model_override": "", + "dms_unsold": "", + "dms_wip_acctnumber": "", + "id": "", + "inservicedate": "", + "journal": "", + "lines": "", + "name1": "", + "payer": { + "amount": "", + "control_type": "", + "controlnumber": "", + "dms_acctnumber": "", + "name": "" + }, + "sale": "", + "sale_dms_acctnumber": "", + "story": "", + "vinowner": "" + }, + "dms_allocation": "", + "driveable": "", + "employee_body": "", + "employee_csr": "Representante de servicio al cliente.", + "employee_csr_writer": "", + "employee_prep": "", + "employee_refinish": "", + "est_addr1": "Dirección del tasador", + "est_co_nm": "Tasador", + "est_ct_fn": "Nombre del tasador", + "est_ct_ln": "Apellido del tasador", + "est_ea": "Correo electrónico del tasador", + "est_ph1": "Número de teléfono del tasador", + "federal_tax_payable": "Impuesto federal por pagar", + "federal_tax_rate": "", + "ins_addr1": "Dirección de Insurance Co.", + "ins_city": "Ciudad de seguros", + "ins_co_id": "ID de la compañía de seguros", + "ins_co_nm": "Nombre de la compañía de seguros", + "ins_co_nm_short": "", + "ins_ct_fn": "Nombre del controlador de archivos", + "ins_ct_ln": "Apellido del manejador de archivos", + "ins_ea": "Correo electrónico del controlador de archivos", + "ins_ph1": "File Handler Phone #", + "intake": { + "label": "", + "max": "", + "min": "", + "name": "", + "required": "", + "type": "" + }, + "invoice_final_note": "", + "kmin": "Kilometraje en", + "kmout": "Kilometraje", + "la1": "", + "la2": "", + "la3": "", + "la4": "", + "laa": "", + "lab": "", + "labor_rate_desc": "Nombre de la tasa laboral", + "lad": "", + "lae": "", + "laf": "", + "lag": "", + "lam": "", + "lar": "", + "las": "", + "lau": "", + "local_tax_rate": "", + "loss_date": "Fecha de pérdida", + "loss_desc": "", + "loss_of_use": "", + "lost_sale_reason": "", + "ma2s": "", + "ma3s": "", + "mabl": "", + "macs": "", + "mahw": "", + "mapa": "", + "mash": "", + "matd": "", + "materials": { + "MAPA": "", + "MASH": "", + "cal_maxdlr": "", + "cal_opcode": "", + "mat_adjp": "", + "mat_taxp": "", + "mat_tx_in1": "", + "mat_tx_in2": "", + "mat_tx_in3": "", + "mat_tx_in4": "", + "mat_tx_in5": "", + "materials": "", + "tax_ind": "" + }, + "other_amount_payable": "Otra cantidad a pagar", + "owner": "Propietario", + "owner_owing": "Cust. Debe", + "ownr_ea": "Email", + "ownr_ph1": "Teléfono 1", + "ownr_ph2": "", + "paa": "", + "pac": "", + "pae": "", + "pag": "", + "pal": "", + "pam": "", + "pan": "", + "pao": "", + "pap": "", + "par": "", + "parts_tax_rates": { + "prt_discp": "", + "prt_mktyp": "", + "prt_mkupp": "", + "prt_tax_in": "", + "prt_tax_rt": "", + "prt_tx_in1": "", + "prt_tx_in2": "", + "prt_tx_in3": "", + "prt_tx_in4": "", + "prt_tx_in5": "", + "prt_tx_ty1": "", + "prt_type": "" + }, + "partsstatus": "", + "pas": "", + "pay_date": "Fecha de Pay", + "phoneshort": "PH", + "po_number": "", + "policy_no": "Política #", + "ponumber": "numero postal", + "production_vars": { + "note": "" + }, + "qb_multiple_payers": { + "amount": "", + "name": "" + }, + "queued_for_parts": "", + "rate_ats": "", + "rate_la1": "Tarifa LA1", + "rate_la2": "Tarifa LA2", + "rate_la3": "Tarifa LA3", + "rate_la4": "Tarifa LA4", + "rate_laa": "Tasa de aluminio", + "rate_lab": "Tasa de trabajo", + "rate_lad": "Tasa de diagnóstico", + "rate_lae": "tarifa eléctrica", + "rate_laf": "Cuadros por segundo", + "rate_lag": "Tasa de vidrio", + "rate_lam": "Tasa mecánica", + "rate_lar": "Tasa de acabado", + "rate_las": "", + "rate_lau": "", + "rate_ma2s": "Velocidad de pintura de 2 etapas", + "rate_ma3s": "Tasa de pintura de 3 etapas", + "rate_mabl": "MABL ??", + "rate_macs": "MACS ??", + "rate_mahw": "Tasa de residuos peligrosos", + "rate_mapa": "Tasa de materiales de pintura", + "rate_mash": "Comprar material de tarifa", + "rate_matd": "Tasa de eliminación de neumáticos", + "referral_source_extra": "", + "referral_source_other": "", + "referralsource": "Fuente de referencia", + "regie_number": "N. ° de registro", + "repairtotal": "Reparación total", + "ro_number": "RO #", + "scheduled_completion": "Finalización programada", + "scheduled_delivery": "Entrega programada", + "scheduled_in": "Programado en", + "selling_dealer": "Distribuidor vendedor", + "selling_dealer_contact": "Contacto con el vendedor", + "servicecar": "Auto de servicio", + "servicing_dealer": "Distribuidor de servicio", + "servicing_dealer_contact": "Servicio Contacto con el concesionario", + "special_coverage_policy": "Política de cobertura especial", + "specialcoveragepolicy": "Política de cobertura especial", + "state_tax_rate": "", + "status": "Estado del trabajo", + "storage_payable": "Almacenamiento ", + "tax_lbr_rt": "", + "tax_levies_rt": "", + "tax_paint_mat_rt": "", + "tax_registration_number": "", + "tax_shop_mat_rt": "", + "tax_str_rt": "", + "tax_sub_rt": "", + "tax_tow_rt": "", + "towin": "", + "towing_payable": "Remolque a pagar", + "unitnumber": "Unidad #", + "updated_at": "Actualizado en", + "uploaded_by": "Subido por", + "vehicle": "Vehículo" + }, + "forms": { + "admindates": "", + "appraiserinfo": "", + "claiminfo": "", + "estdates": "", + "laborrates": "", + "lossinfo": "", + "other": "", + "repairdates": "", + "scheddates": "" + }, + "labels": { + "accountsreceivable": "", + "act_price_ppc": "", + "actual_completion_inferred": "", + "actual_delivery_inferred": "", + "actual_in_inferred": "", + "additionalpayeroverallocation": "", + "additionaltotal": "", + "adjustmentrate": "", + "adjustments": "", + "adminwarning": "", + "allocations": "", + "alreadyaddedtoscoreboard": "", + "alreadyclosed": "", + "appointmentconfirmation": "¿Enviar confirmación al cliente?", + "associationwarning": "", + "audit": "", + "available": "", + "availablejobs": "", + "ca_bc_pvrt": { + "days": "", + "rate": "" + }, + "ca_gst_all_if_null": "", + "calc_repair_days": "", + "calc_repair_days_tt": "", + "calc_scheuled_completion": "", + "cards": { + "customer": "Información al cliente", + "damage": "Área de Daño", + "dates": "fechas", + "documents": "Documentos recientes", + "estimator": "Estimador", + "filehandler": "File Handler", + "insurance": "detalles del seguro", + "more": "Más", + "notes": "Notas", + "parts": "Partes", + "totals": "Totales", + "vehicle": "Vehículo" + }, + "changeclass": "", + "checklistcompletedby": "", + "checklistdocuments": "", + "checklists": "", + "cieca_pfl": "", + "cieca_pfo": "", + "cieca_pft": "", + "closeconfirm": "", + "closejob": "", + "closingperiod": "", + "contracts": "", + "convertedtolabor": "", + "cost": "", + "cost_Additional": "", + "cost_labor": "", + "cost_parts": "", + "cost_sublet": "", + "costs": "", + "create": { + "jobinfo": "", + "newowner": "", + "newvehicle": "", + "novehicle": "", + "ownerinfo": "", + "vehicleinfo": "" + }, + "createiouwarning": "", + "creating_new_job": "Creando nuevo trabajo ...", + "deductible": { + "stands": "", + "waived": "" + }, + "deleteconfirm": "", + "deletedelivery": "", + "deleteintake": "", + "deliverchecklist": "", + "difference": "", + "diskscan": "", + "dms": { + "apexported": "", + "damageto": "", + "defaultstory": "", + "disablebillwip": "", + "invoicedatefuture": "", + "kmoutnotgreaterthankmin": "", + "logs": "", + "notallocated": "", + "postingform": "", + "totalallocated": "" + }, + "documents": "documentos", + "documents-images": "", + "documents-other": "", + "duplicateconfirm": "", + "emailaudit": "", + "employeeassignments": "", + "estimatelines": "", + "estimator": "", + "existing_jobs": "Empleos existentes", + "federal_tax_amt": "", + "gpdollars": "", + "gppercent": "", + "hrs_claimed": "", + "hrs_total": "", + "importnote": "", + "inproduction": "", + "intakechecklist": "", + "iou": "", + "job": "", + "jobcosting": "", + "jobtotals": "", + "labor_hrs": "", + "labor_rates_subtotal": "", + "laborallocations": "", + "labortotals": "", + "lines": "Líneas estimadas", + "local_tax_amt": "", + "mapa": "", + "markforreexport": "", + "mash": "", + "masterbypass": "", + "materials": { + "mapa": "" + }, + "missingprofileinfo": "", + "multipayers": "", + "net_repairs": "", + "notes": "Notas", + "othertotal": "", + "outstanding_ar": "", + "outstanding_credit_memos": "", + "outstanding_ppd": "", + "outstanding_reconciliation_discrep": "", + "outstanding_sublets": "", + "outstandinghours": "", + "override_header": "¿Anular encabezado estimado al importar?", + "ownerassociation": "", + "parts": "Partes", + "parts_lines": "", + "parts_received": "", + "parts_tax_rates": "", + "partsfilter": "", + "partssubletstotal": "", + "partstotal": "", + "performance": "", + "pimraryamountpayable": "", + "plitooltips": { + "billtotal": "", + "calculatedcreditsnotreceived": "", + "creditmemos": "", + "creditsnotreceived": "", + "discrep1": "", + "discrep2": "", + "discrep3": "", + "laboradj": "", + "partstotal": "", + "totalreturns": "" + }, + "ppc": "", + "ppdnotexported": "", + "profileadjustments": "", + "profitbypassrequired": "", + "profits": "", + "prt_dsmk_total": "", + "rates": "Tarifas", + "rates_subtotal": "", + "reconciliation": { + "billlinestotal": "", + "byassoc": "", + "byprice": "", + "clear": "", + "discrepancy": "", + "joblinestotal": "", + "multipleactprices": "", + "multiplebilllines": "", + "multiplebillsforactprice": "", + "removedpartsstrikethrough": "" + }, + "reconciliationheader": "", + "relatedros": "", + "remove_from_ar": "", + "returntotals": "", + "ro_guard": { + "enforce_ar": "", + "enforce_bills": "", + "enforce_cm": "", + "enforce_labor": "", + "enforce_ppd": "", + "enforce_profit": "", + "enforce_sublet": "", + "enforce_validation": "", + "enforced": "" + }, + "roguard": "", + "roguardwarnings": "", + "rosaletotal": "", + "sale_additional": "", + "sale_labor": "", + "sale_parts": "", + "sale_sublet": "", + "sales": "", + "savebeforeconversion": "", + "scheduledinchange": "", + "specialcoveragepolicy": "", + "state_tax_amt": "", + "subletsnotcompleted": "", + "subletstotal": "", + "subtotal": "", + "supplementnote": "", + "suspended": "", + "suspense": "", + "tasks": "", + "threshhold": "", + "total_cost": "", + "total_cust_payable": "", + "total_repairs": "", + "total_sales": "", + "total_sales_tax": "", + "totals": "", + "unvoidnote": "", + "update_scheduled_completion": "", + "vehicle_info": "Vehículo", + "vehicleassociation": "", + "viewallocations": "", + "voidjob": "", + "voidnote": "" + }, + "successes": { + "addedtoproduction": "", + "all_deleted": "{{count}} trabajos eliminados con éxito.", + "closed": "", + "converted": "Trabajo convertido con éxito.", + "created": "Trabajo creado con éxito. Click para ver.", + "creatednoclick": "", + "delete": "", + "deleted": "Trabajo eliminado con éxito.", + "duplicated": "", + "exported": "", + "invoiced": "", + "ioucreated": "", + "partsqueue": "", + "save": "Trabajo guardado con éxito.", + "savetitle": "Registro guardado con éxito.", + "supplemented": "Trabajo complementado con éxito.", + "updated": "", + "voided": "" + } + }, + "landing": { + "bigfeature": { + "subtitle": "", + "title": "" + }, + "footer": { + "company": { + "about": "", + "contact": "", + "disclaimers": "", + "name": "", + "privacypolicy": "" + }, + "io": { + "help": "", + "name": "", + "status": "" + }, + "slogan": "" + }, + "hero": { + "button": "", + "title": "" + }, + "labels": { + "features": "", + "managemyshop": "", + "pricing": "" + }, + "pricing": { + "basic": { + "name": "", + "sub": "" + }, + "essentials": { + "name": "", + "sub": "" + }, + "pricingtitle": "", + "pro": { + "name": "", + "sub": "" + }, + "title": "", + "unlimited": { + "name": "", + "sub": "" + } + } + }, + "menus": { + "currentuser": { + "languageselector": "idioma", + "profile": "Perfil" + }, + "header": { + "accounting": "", + "accounting-payables": "", + "accounting-payments": "", + "accounting-receivables": "", + "activejobs": "Empleos activos", + "all_tasks": "", + "alljobs": "", + "allpayments": "", + "availablejobs": "Trabajos disponibles", + "bills": "", + "courtesycars": "", + "courtesycars-all": "", + "courtesycars-contracts": "", + "courtesycars-newcontract": "", + "create_task": "", + "customers": "Clientes", + "dashboard": "", + "enterbills": "", + "entercardpayment": "", + "enterpayment": "", + "entertimeticket": "", + "export": "", + "export-logs": "", + "help": "", + "home": "Casa", + "inventory": "", + "jobs": "Trabajos", + "my_tasks": "", + "newjob": "", + "owners": "propietarios", + "parts-queue": "", + "phonebook": "", + "productionboard": "", + "productionlist": "", + "readyjobs": "", + "recent": "", + "reportcenter": "", + "rescueme": "", + "schedule": "Programar", + "scoreboard": "", + "search": { + "bills": "", + "jobs": "", + "owners": "", + "payments": "", + "phonebook": "", + "vehicles": "" + }, + "shiftclock": "", + "shop": "Mi tienda", + "shop_config": "Configuración", + "shop_csi": "", + "shop_templates": "", + "shop_vendors": "Vendedores", + "tasks": "", + "temporarydocs": "", + "timetickets": "", + "ttapprovals": "", + "vehicles": "Vehículos" + }, + "jobsactions": { + "admin": "", + "cancelallappointments": "", + "closejob": "", + "deletejob": "", + "duplicate": "", + "duplicatenolines": "", + "newcccontract": "", + "void": "" + }, + "jobsdetail": { + "claimdetail": "Detalles de la reclamación", + "dates": "fechas", + "financials": "", + "general": "", + "insurance": "", + "labor": "Labor", + "lifecycle": "", + "parts": "", + "partssublet": "Piezas / Subarrendamiento", + "rates": "", + "repairdata": "Datos de reparación", + "totals": "" + }, + "profilesidebar": { + "profile": "Mi perfil", + "shops": "Mis tiendas" + }, + "tech": { + "assignedjobs": "", + "claimtask": "", + "dispatchedparts": "", + "home": "", + "jobclockin": "", + "jobclockout": "", + "joblookup": "", + "login": "", + "logout": "", + "productionboard": "", + "productionlist": "", + "shiftclockin": "" + } + }, + "messaging": { + "actions": { + "link": "", + "new": "" + }, + "errors": { + "invalidphone": "", + "noattachedjobs": "", + "updatinglabel": "" + }, + "labels": { + "addlabel": "", + "archive": "", + "maxtenimages": "", + "messaging": "Mensajería", + "noallowtxt": "", + "nojobs": "", + "nopush": "", + "phonenumber": "", + "presets": "", + "recentonly": "", + "selectmedia": "", + "sentby": "", + "typeamessage": "Enviar un mensaje...", + "unarchive": "" + }, + "render": { + "conversation_list": "" + } + }, + "notes": { + "actions": { + "actions": "Comportamiento", + "deletenote": "Borrar nota", + "edit": "Editar nota", + "new": "Nueva nota", + "savetojobnotes": "" + }, + "errors": { + "inserting": "" + }, + "fields": { + "createdby": "Creado por", + "critical": "Crítico", + "private": "Privado", + "text": "Contenido", + "type": "", + "types": { + "customer": "", + "general": "", + "office": "", + "paint": "", + "parts": "", + "shop": "", + "supplement": "" + }, + "updatedat": "Actualizado en" + }, + "labels": { + "addtorelatedro": "", + "newnoteplaceholder": "Agrega una nota...", + "notetoadd": "", + "systemnotes": "", + "usernotes": "" + }, + "successes": { + "create": "Nota creada con éxito.", + "deleted": "Nota eliminada con éxito.", + "updated": "Nota actualizada con éxito." + } + }, + "owner": { + "labels": { + "noownerinfo": "" + } + }, + "owners": { + "actions": { + "update": "" + }, + "errors": { + "deleting": "", + "noaccess": "El registro no existe o no tiene acceso a él.", + "saving": "", + "selectexistingornew": "" + }, + "fields": { + "address": "Dirección", + "allow_text_message": "Permiso de texto?", + "name": "Nombre", + "note": "", + "ownr_addr1": "Dirección", + "ownr_addr2": "Dirección 2", + "ownr_city": "ciudad", + "ownr_co_nm": "", + "ownr_ctry": "País", + "ownr_ea": "Email", + "ownr_fn": "Nombre de pila", + "ownr_ln": "Apellido", + "ownr_ph1": "Teléfono 1", + "ownr_ph2": "", + "ownr_st": "Provincia del estado", + "ownr_title": "Título", + "ownr_zip": "código postal", + "preferred_contact": "Método de Contacto Preferido", + "tax_number": "" + }, + "forms": { + "address": "", + "contact": "", + "name": "" + }, + "labels": { + "create_new": "Crea un nuevo registro de propietario.", + "deleteconfirm": "", + "existing_owners": "Propietarios existentes", + "fromclaim": "", + "fromowner": "", + "relatedjobs": "", + "updateowner": "" + }, + "successes": { + "delete": "", + "save": "Propietario guardado con éxito." + } + }, + "parts": { + "actions": { + "order": "Pedido de piezas", + "orderinhouse": "" + } + }, + "parts_dispatch": { + "actions": { + "accept": "" + }, + "errors": { + "accepting": "", + "creating": "" + }, + "fields": { + "number": "", + "percent_accepted": "" + }, + "labels": { + "notyetdispatched": "", + "parts_dispatch": "" + } + }, + "parts_dispatch_lines": { + "fields": { + "accepted_at": "" + } + }, + "parts_orders": { + "actions": { + "backordered": "", + "receive": "", + "receivebill": "" + }, + "errors": { + "associatedbills": "", + "backordering": "", + "creating": "Se encontró un error al crear el pedido de piezas.", + "oec": "", + "saving": "", + "updating": "" + }, + "fields": { + "act_price": "", + "backordered_eta": "", + "backordered_on": "", + "cm_received": "", + "comments": "", + "cost": "", + "db_price": "", + "deliver_by": "", + "job_line_id": "", + "line_desc": "", + "line_remarks": "", + "lineremarks": "Comentarios de línea", + "oem_partno": "", + "order_date": "", + "order_number": "", + "orderedby": "", + "part_type": "", + "quantity": "", + "return": "", + "status": "" + }, + "labels": { + "allpartsto": "", + "confirmdelete": "", + "custompercent": "", + "discount": "", + "email": "Enviar por correo electrónico", + "inthisorder": "Partes en este pedido", + "is_quote": "", + "mark_as_received": "", + "newpartsorder": "", + "notyetordered": "", + "oec": "", + "order_type": "", + "orderhistory": "Historial de pedidos", + "parts_order": "", + "parts_orders": "", + "print": "Mostrar formulario impreso", + "receive": "", + "removefrompartsqueue": "", + "returnpartsorder": "", + "sublet_order": "" + }, + "successes": { + "created": "Pedido de piezas creado con éxito.", + "line_updated": "", + "received": "", + "return_created": "" + } + }, + "payments": { + "actions": { + "generatepaymentlink": "" + }, + "errors": { + "exporting": "", + "exporting-partner": "", + "inserting": "" + }, + "fields": { + "amount": "", + "created_at": "", + "date": "", + "exportedat": "", + "memo": "", + "payer": "", + "paymentnum": "", + "stripeid": "", + "transactionid": "", + "type": "" + }, + "labels": { + "balance": "", + "ca_bc_etf_table": "", + "customer": "", + "edit": "", + "electronicpayment": "", + "external": "", + "findermodal": "", + "insurance": "", + "markexported": "", + "markforreexport": "", + "new": "", + "signup": "", + "smspaymentreminder": "", + "title": "", + "totalpayments": "" + }, + "successes": { + "exported": "", + "markexported": "", + "markreexported": "", + "payment": "", + "paymentupdate": "", + "stripe": "" + } + }, + "phonebook": { + "actions": { + "new": "" + }, + "errors": { + "adding": "", + "saving": "" + }, + "fields": { + "address1": "", + "address2": "", + "category": "", + "city": "", + "company": "", + "country": "", + "email": "", + "fax": "", + "firstname": "", + "lastname": "", + "phone1": "", + "phone2": "", + "state": "" + }, + "labels": { + "noneselected": "", + "onenamerequired": "", + "vendorcategory": "" + }, + "successes": { + "added": "", + "deleted": "", + "saved": "" + } + }, + "printcenter": { + "appointments": { + "appointment_confirmation": "" + }, + "bills": { + "inhouse_invoice": "" + }, + "courtesycarcontract": { + "courtesy_car_contract": "", + "courtesy_car_impound": "", + "courtesy_car_inventory": "", + "courtesy_car_terms": "" + }, + "errors": { + "nocontexttype": "" + }, + "jobs": { + "3rdpartyfields": { + "addr1": "", + "addr2": "", + "addr3": "", + "attn": "", + "city": "", + "custgst": "", + "ded_amt": "", + "depreciation": "", + "other": "", + "ponumber": "", + "refnumber": "", + "sendtype": "", + "state": "", + "zip": "" + }, + "3rdpartypayer": "", + "ab_proof_of_loss": "", + "appointment_confirmation": "", + "appointment_reminder": "", + "casl_authorization": "", + "committed_timetickets_ro": "", + "coversheet_landscape": "", + "coversheet_portrait": "", + "csi_invitation": "", + "csi_invitation_action": "", + "diagnostic_authorization": "", + "dms_posting_sheet": "", + "envelope_return_address": "", + "estimate": "", + "estimate_detail": "", + "estimate_followup": "", + "express_repair_checklist": "", + "filing_coversheet_landscape": "", + "filing_coversheet_portrait": "", + "final_invoice": "", + "fippa_authorization": "", + "folder_label_multiple": "", + "glass_express_checklist": "", + "guarantee": "", + "individual_job_note": "", + "invoice_customer_payable": "", + "invoice_total_payable": "", + "iou_form": "", + "job_costing_ro": "", + "job_lifecycle_ro": "", + "job_notes": "", + "job_tasks": "", + "key_tag": "", + "labels": { + "count": "", + "labels": "", + "position": "" + }, + "lag_time_ro": "", + "mechanical_authorization": "", + "mpi_animal_checklist": "", + "mpi_eglass_auth": "", + "mpi_final_acct_sheet": "", + "mpi_final_repair_acct_sheet": "", + "paint_grid": "", + "parts_dispatch": "", + "parts_invoice_label_single": "", + "parts_label_multiple": "", + "parts_label_single": "", + "parts_list": "", + "parts_order": "", + "parts_order_confirmation": "", + "parts_order_history": "", + "parts_return_slip": "", + "payment_receipt": "", + "payment_request": "", + "payments_by_job": "", + "purchases_by_ro_detail": "", + "purchases_by_ro_summary": "", + "qc_sheet": "", + "rental_reservation": "", + "ro_totals": "", + "ro_with_description": "", + "sgi_certificate_of_repairs": "", + "sgi_windshield_auth": "", + "stolen_recovery_checklist": "", + "sublet_order": "", + "supplement_request": "", + "thank_you_ro": "", + "thirdpartypayer": "", + "timetickets_ro": "", + "vehicle_check_in": "", + "vehicle_delivery_check": "", + "window_tag": "", + "window_tag_sublet": "", + "work_authorization": "", + "worksheet_by_line_number": "", + "worksheet_sorted_by_operation": "", + "worksheet_sorted_by_operation_no_hours": "", + "worksheet_sorted_by_operation_part_type": "", + "worksheet_sorted_by_operation_type": "", + "worksheet_sorted_by_team": "" + }, + "labels": { + "groups": { + "authorization": "", + "financial": "", + "post": "", + "pre": "", + "ro": "", + "worksheet": "" + }, + "misc": "", + "repairorder": "", + "reportcentermodal": "", + "speedprint": "", + "title": "" + }, + "payments": { + "ca_bc_etf_table": "", + "exported_payroll": "" + }, + "special": { + "attendance_detail_csv": "" + }, + "subjects": { + "jobs": { + "individual_job_note": "", + "parts_dispatch": "", + "parts_order": "", + "parts_return_slip": "", + "sublet_order": "" + } + }, + "vendors": { + "purchases_by_vendor_detailed": "", + "purchases_by_vendor_summary": "" + } + }, + "production": { + "actions": { + "addcolumns": "", + "bodypriority-clear": "", + "bodypriority-set": "", + "detailpriority-clear": "", + "detailpriority-set": "", + "paintpriority-clear": "", + "paintpriority-set": "", + "remove": "", + "removecolumn": "", + "saveconfig": "", + "suspend": "", + "unsuspend": "" + }, + "constants": { + "main_profile": "" + }, + "errors": { + "boardupdate": "", + "name_exists": "", + "name_required": "", + "removing": "", + "settings": "" + }, + "labels": { + "actual_in": "", + "addnewprofile": "", + "alert": "", + "alertoff": "", + "alerton": "", + "alerts": "", + "ats": "", + "bodyhours": "", + "bodypriority": "", + "bodyshop": { + "labels": { + "qbo_departmentid": "", + "qbo_usa": "" + } + }, + "card_size": "", + "cardcolor": "", + "cardsettings": "", + "clm_no": "", + "comment": "", + "compact": "", + "detailpriority": "", + "employeeassignments": "", + "employeesearch": "", + "estimator": "", + "horizontal": "", + "ins_co_nm": "", + "jobdetail": "", + "kiosk_mode": "", + "laborhrs": "", + "legend": "", + "model_info": "", + "note": "", + "off": "", + "on": "", + "orientation": "", + "ownr_nm": "", + "paintpriority": "", + "partsstatus": "", + "production_note": "", + "refinishhours": "", + "scheduled_completion": "", + "selectview": "", + "stickyheader": "", + "sublets": "", + "subtotal": "", + "tall": "", + "tasks": "", + "totalhours": "", + "touchtime": "", + "vertical": "", + "viewname": "", + "wide": "" + }, + "options": { + "horizontal": "", + "large": "", + "medium": "", + "small": "", + "vertical": "" + }, + "settings": { + "board_settings": "", + "filters": { + "md_estimators": "", + "md_ins_cos": "" + }, + "filters_title": "", + "information": "", + "layout": "", + "statistics": { + "jobs_in_production": "", + "tasks_in_production": "", + "tasks_in_view": "", + "tasks_on_board": "", + "total_amount_in_production": "", + "total_amount_in_view": "", + "total_amount_on_board": "", + "total_hours_in_production": "", + "total_hours_in_view": "", + "total_hours_on_board": "", + "total_jobs_in_view": "", + "total_jobs_on_board": "", + "total_lab_in_production": "", + "total_lab_in_view": "", + "total_lab_on_board": "", + "total_lar_in_production": "", + "total_lar_in_view": "", + "total_lar_on_board": "" + }, + "statistics_title": "" + }, + "statistics": { + "currency_symbol": "", + "hours": "", + "jobs": "", + "jobs_in_production": "", + "tasks": "", + "tasks_in_production": "", + "tasks_in_view": "", + "tasks_on_board": "", + "total_amount_in_production": "", + "total_amount_in_view": "", + "total_amount_on_board": "", + "total_hours_in_production": "", + "total_hours_in_view": "", + "total_hours_on_board": "", + "total_jobs_in_view": "", + "total_jobs_on_board": "", + "total_lab_in_production": "", + "total_lab_in_view": "", + "total_lab_on_board": "", + "total_lar_in_production": "", + "total_lar_in_view": "", + "total_lar_on_board": "" + }, + "successes": { + "removed": "" + } + }, + "profile": { + "errors": { + "state": "Error al leer el estado de la página. Porfavor refresca." + }, + "labels": { + "activeshop": "" + }, + "successes": { + "updated": "" + } + }, + "reportcenter": { + "actions": { + "generate": "" + }, + "labels": { + "advanced_filters": "", + "advanced_filters_false": "", + "advanced_filters_filter_field": "", + "advanced_filters_filter_operator": "", + "advanced_filters_filter_value": "", + "advanced_filters_filters": "", + "advanced_filters_hide": "", + "advanced_filters_show": "", + "advanced_filters_sorter_direction": "", + "advanced_filters_sorter_field": "", + "advanced_filters_sorters": "", + "advanced_filters_true": "", + "dates": "", + "employee": "", + "filterson": "", + "generateasemail": "", + "groups": { + "customers": "", + "jobs": "", + "payroll": "", + "purchases": "", + "sales": "" + }, + "key": "", + "objects": { + "appointments": "", + "bills": "", + "csi": "", + "exportlogs": "", + "jobs": "", + "parts_orders": "", + "payments": "", + "scoreboard": "", + "tasks": "", + "timetickets": "" + }, + "vendor": "" + }, + "templates": { + "adp_payroll_flat": "", + "adp_payroll_straight": "", + "anticipated_revenue": "", + "ar_aging": "", + "attendance_detail": "", + "attendance_employee": "", + "attendance_summary": "", + "committed_timetickets": "", + "committed_timetickets_employee": "", + "committed_timetickets_summary": "", + "credits_not_received_date": "", + "credits_not_received_date_vendorid": "", + "csi": "", + "customer_list": "", + "cycle_time_analysis": "", + "estimates_written_converted": "", + "estimator_detail": "", + "estimator_summary": "", + "export_payables": "", + "export_payments": "", + "export_receivables": "", + "exported_gsr_by_ro": "", + "exported_gsr_by_ro_labor": "", + "gsr_by_atp": "", + "gsr_by_ats": "", + "gsr_by_category": "", + "gsr_by_csr": "", + "gsr_by_delivery_date": "", + "gsr_by_estimator": "", + "gsr_by_exported_date": "", + "gsr_by_ins_co": "", + "gsr_by_make": "", + "gsr_by_referral": "", + "gsr_by_ro": "", + "gsr_labor_only": "", + "hours_sold_detail_closed": "", + "hours_sold_detail_closed_csr": "", + "hours_sold_detail_closed_estimator": "", + "hours_sold_detail_closed_ins_co": "", + "hours_sold_detail_closed_status": "", + "hours_sold_detail_open": "", + "hours_sold_detail_open_csr": "", + "hours_sold_detail_open_estimator": "", + "hours_sold_detail_open_ins_co": "", + "hours_sold_detail_open_status": "", + "hours_sold_summary_closed": "", + "hours_sold_summary_closed_csr": "", + "hours_sold_summary_closed_estimator": "", + "hours_sold_summary_closed_ins_co": "", + "hours_sold_summary_closed_status": "", + "hours_sold_summary_open": "", + "hours_sold_summary_open_csr": "", + "hours_sold_summary_open_estimator": "", + "hours_sold_summary_open_ins_co": "", + "hours_sold_summary_open_status": "", + "job_costing_ro_csr": "", + "job_costing_ro_date_detail": "", + "job_costing_ro_date_summary": "", + "job_costing_ro_estimator": "", + "job_costing_ro_ins_co": "", + "job_lifecycle_date_detail": "", + "job_lifecycle_date_summary": "", + "jobs_completed_not_invoiced": "", + "jobs_invoiced_not_exported": "", + "jobs_reconcile": "", + "jobs_scheduled_completion": "", + "lag_time": "", + "load_level": "", + "lost_sales": "", + "open_orders": "", + "open_orders_csr": "", + "open_orders_estimator": "", + "open_orders_excel": "", + "open_orders_ins_co": "", + "open_orders_referral": "", + "open_orders_specific_csr": "", + "open_orders_status": "", + "parts_backorder": "", + "parts_not_recieved": "", + "parts_not_recieved_vendor": "", + "parts_received_not_scheduled": "", + "payments_by_date": "", + "payments_by_date_payment": "", + "payments_by_date_type": "", + "production_by_category": "", + "production_by_category_one": "", + "production_by_csr": "", + "production_by_last_name": "", + "production_by_repair_status": "", + "production_by_repair_status_one": "", + "production_by_ro": "", + "production_by_target_date": "", + "production_by_technician": "", + "production_by_technician_one": "", + "production_not_production_status": "", + "production_over_time": "", + "psr_by_make": "", + "purchase_return_ratio_grouped_by_vendor_detail": "", + "purchase_return_ratio_grouped_by_vendor_summary": "", + "purchases_by_cost_center_detail": "", + "purchases_by_cost_center_summary": "", + "purchases_by_date_range_detail": "", + "purchases_by_date_range_summary": "", + "purchases_by_ro_detail_date": "", + "purchases_by_ro_summary_date": "", + "purchases_by_vendor_detailed_date_range": "", + "purchases_by_vendor_summary_date_range": "", + "purchases_grouped_by_vendor_detailed": "", + "purchases_grouped_by_vendor_summary": "", + "returns_grouped_by_vendor_detailed": "", + "returns_grouped_by_vendor_summary": "", + "schedule": "", + "scheduled_parts_list": "", + "scoreboard_detail": "", + "scoreboard_summary": "", + "supplement_ratio_ins_co": "", + "tasks_date": "", + "tasks_date_employee": "", + "thank_you_date": "", + "timetickets": "", + "timetickets_employee": "", + "timetickets_summary": "", + "unclaimed_hrs": "", + "void_ros": "", + "work_in_progress_committed_labour": "", + "work_in_progress_jobs": "", + "work_in_progress_labour": "", + "work_in_progress_payables": "" + } + }, + "schedule": { + "labels": { + "atssummary": "", + "employeevacation": "", + "estimators": "", + "ins_co_nm_filter": "", + "intake": "", + "manual": "", + "manualevent": "" + } + }, + "scoreboard": { + "actions": { + "edit": "" + }, + "errors": { + "adding": "", + "removing": "", + "updating": "" + }, + "fields": { + "bodyhrs": "", + "date": "", + "painthrs": "" + }, + "labels": { + "allemployeetimetickets": "", + "asoftodaytarget": "", + "body": "", + "bodyabbrev": "", + "bodycharttitle": "", + "calendarperiod": "", + "combinedcharttitle": "", + "dailyactual": "", + "dailytarget": "", + "efficiencyoverperiod": "", + "entries": "", + "jobs": "", + "jobscompletednotinvoiced": "", + "lastmonth": "", + "lastweek": "", + "monthlytarget": "", + "priorweek": "", + "productivestatistics": "", + "productivetimeticketsoverdate": "", + "refinish": "", + "refinishabbrev": "", + "refinishcharttitle": "", + "targets": "", + "thismonth": "", + "thisweek": "", + "timetickets": "", + "timeticketsemployee": "", + "todateactual": "", + "total": "", + "totalhrs": "", + "totaloverperiod": "", + "weeklyactual": "", + "weeklytarget": "", + "workingdays": "" + }, + "successes": { + "added": "", + "removed": "", + "updated": "" + } + }, + "tasks": { + "actions": { + "edit": "", + "new": "" + }, + "buttons": { + "allTasks": "", + "complete": "", + "create": "", + "delete": "", + "edit": "", + "myTasks": "", + "refresh": "" + }, + "date_presets": { + "completion": "", + "day": "", + "days": "", + "delivery": "", + "next_week": "", + "one_month": "", + "three_months": "", + "three_weeks": "", + "today": "", + "tomorrow": "", + "two_weeks": "" + }, + "failures": { + "completed": "", + "created": "", + "deleted": "", + "updated": "" + }, + "fields": { + "actions": "", + "assigned_to": "", + "bill": "", + "billid": "", + "completed": "", + "created_at": "", + "created_by": "", + "description": "", + "due_date": "", + "job": { + "ro_number": "" + }, + "jobid": "", + "jobline": "", + "joblineid": "", + "parts_order": "", + "partsorderid": "", + "priorities": { + "high": "", + "low": "", + "medium": "" + }, + "priority": "", + "related_items": "", + "remind_at": "", + "title": "" + }, + "placeholders": { + "assigned_to": "", + "billid": "", + "description": "", + "jobid": "", + "joblineid": "", + "partsorderid": "" + }, + "successes": { + "completed": "", + "created": "", + "deleted": "", + "updated": "" + }, + "titles": { + "all_tasks": "", + "completed": "", + "deleted": "", + "job_tasks": "", + "mine": "", + "my_tasks": "" + }, + "validation": { + "due_at_error_message": "", + "remind_at_error_message": "" + } + }, + "tech": { + "fields": { + "employeeid": "", + "pin": "" + }, + "labels": { + "loggedin": "", + "notloggedin": "" + } + }, + "templates": { + "errors": { + "updating": "" + }, + "successes": { + "updated": "" + } + }, + "timetickets": { + "actions": { + "claimtasks": "", + "clockin": "", + "clockout": "", + "commit": "", + "commitone": "", + "enter": "", + "payall": "", + "printemployee": "", + "uncommit": "" + }, + "errors": { + "clockingin": "", + "clockingout": "", + "creating": "", + "deleting": "", + "noemployeeforuser": "", + "noemployeeforuser_sub": "", + "payall": "", + "shiftalreadyclockedon": "" + }, + "fields": { + "actualhrs": "", + "ciecacode": "", + "clockhours": "", + "clockoff": "", + "clockon": "", + "committed": "", + "committed_at": "", + "cost_center": "", + "created_by": "", + "date": "", + "efficiency": "", + "employee": "", + "employee_team": "", + "flat_rate": "", + "memo": "", + "productivehrs": "", + "ro_number": "", + "task_name": "" + }, + "labels": { + "alreadyclockedon": "", + "ambreak": "", + "amshift": "", + "claimtaskpreview": "", + "clockhours": "", + "clockintojob": "", + "deleteconfirm": "", + "edit": "", + "efficiency": "", + "flat_rate": "", + "jobhours": "", + "lunch": "", + "new": "", + "payrollclaimedtasks": "", + "pmbreak": "", + "pmshift": "", + "shift": "", + "shiftalreadyclockedon": "", + "straight_time": "", + "task": "", + "timetickets": "", + "unassigned": "", + "zeroactualnegativeprod": "" + }, + "successes": { + "clockedin": "", + "clockedout": "", + "committed": "", + "created": "", + "deleted": "", + "payall": "" + }, + "validation": { + "clockoffmustbeafterclockon": "", + "clockoffwithoutclockon": "", + "hoursenteredmorethanavailable": "", + "unassignedlines": "" + } + }, + "titles": { + "accounting-payables": "", + "accounting-payments": "", + "accounting-receivables": "", + "all_tasks": "", + "app": "", + "bc": { + "accounting-payables": "", + "accounting-payments": "", + "accounting-receivables": "", + "all_tasks": "", + "availablejobs": "", + "bills-list": "", + "contracts": "", + "contracts-create": "", + "contracts-detail": "", + "courtesycars": "", + "courtesycars-detail": "", + "courtesycars-new": "", + "dashboard": "", + "dms": "", + "export-logs": "", + "inventory": "", + "jobs": "", + "jobs-active": "", + "jobs-admin": "", + "jobs-all": "", + "jobs-checklist": "", + "jobs-close": "", + "jobs-deliver": "", + "jobs-detail": "", + "jobs-intake": "", + "jobs-new": "", + "jobs-ready": "", + "my_tasks": "", + "owner-detail": "", + "owners": "", + "parts-queue": "", + "payments-all": "", + "phonebook": "", + "productionboard": "", + "productionlist": "", + "profile": "", + "schedule": "", + "scoreboard": "", + "shop": "", + "shop-csi": "", + "shop-templates": "", + "shop-vendors": "", + "tasks": "", + "temporarydocs": "", + "timetickets": "", + "ttapprovals": "", + "vehicle-details": "", + "vehicles": "" + }, + "bills-list": "", + "contracts": "", + "contracts-create": "", + "contracts-detail": "", + "courtesycars": "", + "courtesycars-create": "", + "courtesycars-detail": "", + "dashboard": "", + "dms": "", + "export-logs": "", + "imexonline": "", + "inventory": "", + "jobs": "Todos los trabajos | {{app}}", + "jobs-admin": "", + "jobs-all": "", + "jobs-checklist": "", + "jobs-close": "", + "jobs-create": "", + "jobs-deliver": "", + "jobs-intake": "", + "jobsavailable": "Empleos disponibles | {{app}}", + "jobsdetail": "Trabajo {{ro_number}} | {{app}}", + "jobsdocuments": "Documentos de trabajo {{ro_number}} | {{app}}", + "manageroot": "Casa | {{app}}", + "my_tasks": "", + "owners": "Todos los propietarios | {{app}}", + "owners-detail": "", + "parts-queue": "", + "payments-all": "", + "phonebook": "", + "productionboard": "", + "productionlist": "", + "profile": "Mi perfil | {{app}}", + "promanager": "", + "readyjobs": "", + "resetpassword": "", + "resetpasswordvalidate": "", + "romeonline": "", + "schedule": "Horario | {{app}}", + "scoreboard": "", + "shop": "Mi tienda | {{app}}", + "shop-csi": "", + "shop-templates": "", + "shop_vendors": "Vendedores | {{app}}", + "tasks": "", + "techconsole": "{{app}}", + "techjobclock": "{{app}}", + "techjoblookup": "{{app}}", + "techshiftclock": "{{app}}", + "temporarydocs": "", + "timetickets": "", + "ttapprovals": "", + "vehicledetail": "Detalles del vehículo {{vehicle}} | {{app}}", + "vehicles": "Todos los vehiculos | {{app}}" + }, + "trello": { + "labels": { + "add_card": "", + "add_lane": "", + "cancel": "", + "delete_lane": "", + "description": "", + "label": "", + "lane_actions": "", + "title": "" + } + }, + "tt_approvals": { + "actions": { + "approveselected": "" + }, + "labels": { + "approval_queue_in_use": "", + "calculate": "" + } + }, + "user": { + "actions": { + "changepassword": "", + "signout": "desconectar", + "updateprofile": "Actualización del perfil" + }, + "errors": { + "updating": "" + }, + "fields": { + "authlevel": "", + "displayname": "Nombre para mostrar", + "email": "", + "photourl": "URL de avatar" + }, + "labels": { + "actions": "", + "changepassword": "", + "profileinfo": "" + }, + "successess": { + "passwordchanged": "" + } + }, + "users": { + "errors": { + "signinerror": { + "auth/user-disabled": "", + "auth/user-not-found": "", + "auth/wrong-password": "" + } + } + }, + "vehicles": { + "errors": { + "deleting": "", + "noaccess": "El vehículo no existe o usted no tiene acceso a él.", + "selectexistingornew": "", + "validation": "Asegúrese de que todos los campos se ingresen correctamente.", + "validationtitle": "Error de validacion" + }, + "fields": { + "description": "Descripcion del vehiculo", + "notes": "", + "plate_no": "Placa", + "plate_st": "Jurisdicción de placas", + "trim_color": "Recortar color", + "v_bstyle": "Tipo de cuerpo", + "v_color": "Color", + "v_cond": "condición", + "v_engine": "Motor", + "v_make_desc": "Hacer", + "v_makecode": "Hacer código", + "v_mldgcode": "Código de moldeo", + "v_model_desc": "Modelo", + "v_model_yr": "año", + "v_options": "Opciones", + "v_paint_codes": "Códigos de pintura", + "v_prod_dt": "Fecha de producción", + "v_stage": "Escenario", + "v_tone": "Tono", + "v_trimcode": "Código de recorte", + "v_type": "Tipo", + "v_vin": "V.I.N." + }, + "forms": { + "detail": "", + "misc": "", + "registration": "" + }, + "labels": { + "deleteconfirm": "", + "fromvehicle": "", + "novehinfo": "", + "relatedjobs": "", + "updatevehicle": "" + }, + "successes": { + "delete": "", + "save": "Vehículo guardado con éxito." + } + }, + "vendors": { + "actions": { + "addtophonebook": "", + "new": "Nuevo vendedor", + "newpreferredmake": "" + }, + "errors": { + "deleting": "Se encontró un error al eliminar el proveedor.", + "saving": "Se encontró un error al guardar el proveedor." + }, + "fields": { + "active": "", + "am": "", + "city": "ciudad", + "cost_center": "Centro de costos", + "country": "País", + "discount": "% De descuento", + "display_name": "Nombre para mostrar", + "dmsid": "", + "due_date": "Fecha de vencimiento del pago", + "email": "Email de contacto", + "favorite": "¿Favorito?", + "lkq": "", + "make": "", + "name": "Nombre del vendedor", + "oem": "", + "phone": "", + "prompt_discount": "Descuento pronto", + "state": "Provincia del estado", + "street1": "calle", + "street2": "Dirección 2", + "taxid": "Identificación del impuesto", + "terms": "Términos de pago", + "zip": "código postal" + }, + "labels": { + "noneselected": "Ningún vendedor está seleccionado.", + "preferredmakes": "", + "search": "Escriba el nombre de un proveedor" + }, + "successes": { + "deleted": "Proveedor eliminado correctamente.", + "saved": "Proveedor guardado con éxito." + }, + "validation": { + "unique_vendor_name": "" + } + } + } } diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 4a64bcc55..c506207e8 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -1,3601 +1,3603 @@ { - "translation": { - "allocations": { - "actions": { - "assign": "Attribuer" - }, - "errors": { - "deleting": "", - "saving": "", - "validation": "" - }, - "fields": { - "employee": "Alloué à" - }, - "successes": { - "deleted": "", - "save": "" - } - }, - "appointments": { - "actions": { - "block": "", - "calculate": "", - "cancel": "annuler", - "intake": "Admission", - "new": "Nouveau rendez-vous", - "preview": "", - "reschedule": "Replanifier", - "sendreminder": "", - "unblock": "", - "viewjob": "Voir le travail" - }, - "errors": { - "blocking": "", - "canceling": "Erreur lors de l'annulation du rendez-vous. {{message}}", - "saving": "Erreur lors de la planification du rendez-vous. {{message}}" - }, - "fields": { - "alt_transport": "", - "color": "", - "end": "", - "note": "", - "start": "", - "time": "", - "title": "Titre" - }, - "labels": { - "arrivedon": "Arrivé le:", - "arrivingjobs": "", - "blocked": "", - "cancelledappointment": "Rendez-vous annulé pour:", - "completingjobs": "", - "dataconsistency": "", - "expectedjobs": "", - "expectedprodhrs": "", - "history": "", - "inproduction": "", - "manualevent": "", - "noarrivingjobs": "", - "nocompletingjobs": "", - "nodateselected": "Aucune date n'a été sélectionnée.", - "priorappointments": "Rendez-vous précédents", - "reminder": "", - "scheduledfor": "Rendez-vous prévu pour:", - "severalerrorsfound": "", - "smartscheduling": "", - "smspaymentreminder": "", - "suggesteddates": "" - }, - "successes": { - "canceled": "Rendez-vous annulé avec succès.", - "created": "Rendez-vous planifié avec succès.", - "saved": "" - } - }, - "associations": { - "actions": { - "activate": "Activer" - }, - "fields": { - "active": "Actif?", - "shopname": "nom de la boutique" - }, - "labels": { - "actions": "actes" - } - }, - "audit": { - "fields": { - "cc": "", - "contents": "", - "created": "", - "operation": "", - "status": "", - "subject": "", - "to": "", - "useremail": "", - "values": "" - } - }, - "audit_trail": { - "messages": { - "admin_job_remove_from_ar": "", - "admin_jobmarkexported": "", - "admin_jobmarkforreexport": "", - "admin_jobuninvoice": "", - "admin_jobunvoid": "", - "alerttoggle": "", - "appointmentcancel": "", - "appointmentinsert": "", - "assignedlinehours": "", - "billdeleted": "", - "billposted": "", - "billupdated": "", - "failedpayment": "", - "jobassignmentchange": "", - "jobassignmentremoved": "", - "jobchecklist": "", - "jobclosedwithbypass": "", - "jobconverted": "", - "jobdelivery": "", - "jobexported": "", - "jobfieldchanged": "", - "jobimported": "", - "jobinproductionchange": "", - "jobintake": "", - "jobinvoiced": "", - "jobioucreated": "", - "jobmodifylbradj": "", - "jobnoteadded": "", - "jobnotedeleted": "", - "jobnoteupdated": "", - "jobspartsorder": "", - "jobspartsreturn": "", - "jobstatuschange": "", - "jobsupplement": "", - "jobsuspend": "", - "jobvoid": "", - "tasks_completed": "", - "tasks_created": "", - "tasks_deleted": "", - "tasks_uncompleted": "", - "tasks_undeleted": "", - "tasks_updated": "" - } - }, - "billlines": { - "actions": { - "newline": "" - }, - "fields": { - "actual_cost": "", - "actual_price": "", - "cost_center": "", - "federal_tax_applicable": "", - "jobline": "", - "line_desc": "", - "local_tax_applicable": "", - "location": "", - "quantity": "", - "state_tax_applicable": "" - }, - "labels": { - "deductedfromlbr": "", - "entered": "", - "from": "", - "mod_lbr_adjustment": "", - "other": "", - "reconciled": "", - "unreconciled": "" - }, - "validation": { - "atleastone": "" - } - }, - "bills": { - "actions": { - "deductallhours": "", - "edit": "", - "receive": "", - "return": "" - }, - "errors": { - "creating": "", - "deleting": "", - "existinginventoryline": "", - "exporting": "", - "exporting-partner": "", - "invalidro": "", - "invalidvendor": "", - "validation": "" - }, - "fields": { - "allpartslocation": "", - "date": "", - "exported": "", - "federal_tax_rate": "", - "invoice_number": "", - "is_credit_memo": "", - "is_credit_memo_short": "", - "local_tax_rate": "", - "ro_number": "", - "state_tax_rate": "", - "total": "", - "vendor": "", - "vendorname": "" - }, - "labels": { - "actions": "", - "bill_lines": "", - "bill_total": "", - "billcmtotal": "", - "bills": "", - "calculatedcreditsnotreceived": "", - "creditsnotreceived": "", - "creditsreceived": "", - "dedfromlbr": "", - "deleteconfirm": "", - "discrepancy": "", - "discrepwithcms": "", - "discrepwithlbradj": "", - "editadjwarning": "", - "entered_total": "", - "enteringcreditmemo": "", - "federal_tax": "", - "federal_tax_exempt": "", - "generatepartslabel": "", - "iouexists": "", - "local_tax": "", - "markexported": "", - "markforreexport": "", - "new": "", - "nobilllines": "", - "noneselected": "", - "onlycmforinvoiced": "", - "printlabels": "", - "retailtotal": "", - "returnfrombill": "", - "savewithdiscrepancy": "", - "state_tax": "", - "subtotal": "", - "totalreturns": "" - }, - "successes": { - "created": "", - "deleted": "", - "exported": "", - "markexported": "", - "reexport": "" - }, - "validation": { - "closingperiod": "", - "inventoryquantity": "", - "manualinhouse": "", - "unique_invoice_number": "" - } - }, - "bodyshop": { - "actions": { - "add_task_preset": "", - "addapptcolor": "", - "addbucket": "", - "addpartslocation": "", - "addpartsrule": "", - "addspeedprint": "", - "addtemplate": "", - "newlaborrate": "", - "newsalestaxcode": "", - "newstatus": "", - "testrender": "" - }, - "errors": { - "creatingdefaultview": "", - "loading": "Impossible de charger les détails de la boutique. Veuillez appeler le support technique.", - "saving": "" - }, - "fields": { - "ReceivableCustomField": "", - "address1": "", - "address2": "", - "appt_alt_transport": "", - "appt_colors": { - "color": "", - "label": "" - }, - "appt_length": "", - "attach_pdf_to_email": "", - "batchid": "", - "bill_allow_post_to_closed": "", - "bill_federal_tax_rate": "", - "bill_local_tax_rate": "", - "bill_state_tax_rate": "", - "city": "", - "closingperiod": "", - "companycode": "", - "country": "", - "dailybodytarget": "", - "dailypainttarget": "", - "default_adjustment_rate": "", - "deliver": { - "require_actual_delivery_date": "", - "templates": "" - }, - "dms": { - "apcontrol": "", - "appostingaccount": "", - "cashierid": "", - "default_journal": "", - "disablebillwip": "", - "disablecontactvehiclecreation": "", - "dms_acctnumber": "", - "dms_control_override": "", - "dms_wip_acctnumber": "", - "generic_customer_number": "", - "itc_federal": "", - "itc_local": "", - "itc_state": "", - "mappingname": "", - "sendmaterialscosting": "", - "srcco": "" - }, - "email": "", - "enforce_class": "", - "enforce_conversion_category": "", - "enforce_conversion_csr": "", - "enforce_referral": "", - "federal_tax_id": "", - "ignoreblockeddays": "", - "inhousevendorid": "", - "insurance_vendor_id": "", - "intake": { - "next_contact_hours": "", - "templates": "" - }, - "intellipay_config": { - "cash_discount_percentage": "", - "enable_cash_discount": "" - }, - "invoice_federal_tax_rate": "", - "invoice_local_tax_rate": "", - "invoice_state_tax_rate": "", - "jc_hourly_rates": { - "mapa": "", - "mash": "" - }, - "last_name_first": "", - "lastnumberworkingdays": "", - "localmediaserverhttp": "", - "localmediaservernetwork": "", - "localmediatoken": "", - "logo_img_footer_margin": "", - "logo_img_header_margin": "", - "logo_img_path": "", - "logo_img_path_height": "", - "logo_img_path_width": "", - "md_categories": "", - "md_ccc_rates": "", - "md_classes": "", - "md_ded_notes": "", - "md_email_cc": "", - "md_from_emails": "", - "md_functionality_toggles": { - "parts_queue_toggle": "" - }, - "md_hour_split": { - "paint": "", - "prep": "" - }, - "md_ins_co": { - "city": "", - "name": "", - "private": "", - "state": "", - "street1": "", - "street2": "", - "zip": "" - }, - "md_jobline_presets": "", - "md_lost_sale_reasons": "", - "md_parts_order_comment": "", - "md_parts_scan": { - "expression": "", - "flags": "" - }, - "md_payment_types": "", - "md_referral_sources": "", - "md_ro_guard": { - "enabled": "", - "enforce_ar": "", - "enforce_bills": "", - "enforce_cm": "", - "enforce_labor": "", - "enforce_ppd": "", - "enforce_profit": "", - "enforce_sublet": "", - "masterbypass": "", - "totalgppercent_minimum": "" - }, - "md_tasks_presets": { - "enable_tasks": "", - "hourstype": "", - "memo": "", - "name": "", - "nextstatus": "", - "percent": "", - "use_approvals": "" - }, - "messaginglabel": "", - "messagingtext": "", - "noteslabel": "", - "notestext": "", - "partslocation": "", - "phone": "", - "prodtargethrs": "", - "rbac": { - "accounting": { - "exportlog": "", - "payables": "", - "payments": "", - "receivables": "" - }, - "bills": { - "delete": "", - "enter": "", - "list": "", - "reexport": "", - "view": "" - }, - "contracts": { - "create": "", - "detail": "", - "list": "" - }, - "courtesycar": { - "create": "", - "detail": "", - "list": "" - }, - "csi": { - "export": "", - "page": "" - }, - "employee_teams": { - "page": "" - }, - "employees": { - "page": "" - }, - "inventory": { - "delete": "", - "list": "" - }, - "jobs": { - "admin": "", - "available-list": "", - "checklist-view": "", - "close": "", - "create": "", - "deliver": "", - "detail": "", - "intake": "", - "list-active": "", - "list-all": "", - "list-ready": "", - "partsqueue": "", - "void": "" - }, - "owners": { - "detail": "", - "list": "" - }, - "payments": { - "enter": "", - "list": "" - }, - "phonebook": { - "edit": "", - "view": "" - }, - "production": { - "board": "", - "list": "" - }, - "schedule": { - "view": "" - }, - "scoreboard": { - "view": "" - }, - "shiftclock": { - "view": "" - }, - "shop": { - "config": "", - "dashboard": "", - "rbac": "", - "reportcenter": "", - "templates": "", - "vendors": "" - }, - "temporarydocs": { - "view": "" - }, - "timetickets": { - "edit": "", - "editcommitted": "", - "enter": "", - "list": "", - "shiftedit": "" - }, - "ttapprovals": { - "approve": "", - "view": "" - }, - "users": { - "editaccess": "" - } - }, - "responsibilitycenter": "", - "responsibilitycenter_accountdesc": "", - "responsibilitycenter_accountitem": "", - "responsibilitycenter_accountname": "", - "responsibilitycenter_accountnumber": "", - "responsibilitycenter_rate": "", - "responsibilitycenter_tax_rate": "", - "responsibilitycenter_tax_sur": "", - "responsibilitycenter_tax_thres": "", - "responsibilitycenter_tax_tier": "", - "responsibilitycenter_tax_type": "", - "responsibilitycenters": { - "ap": "", - "ar": "", - "ats": "", - "federal_tax": "", - "federal_tax_itc": "", - "gst_override": "", - "invoiceexemptcode": "", - "itemexemptcode": "", - "la1": "", - "la2": "", - "la3": "", - "la4": "", - "laa": "", - "lab": "", - "lad": "", - "lae": "", - "laf": "", - "lag": "", - "lam": "", - "lar": "", - "las": "", - "lau": "", - "local_tax": "", - "mapa": "", - "mash": "", - "paa": "", - "pac": "", - "pag": "", - "pal": "", - "pam": "", - "pan": "", - "pao": "", - "pap": "", - "par": "", - "pas": "", - "pasl": "", - "refund": "", - "sales_tax_codes": { - "code": "", - "description": "", - "federal": "", - "local": "", - "state": "" - }, - "state_tax": "", - "tow": "" - }, - "schedule_end_time": "", - "schedule_start_time": "", - "shopname": "", - "speedprint": { - "id": "", - "label": "", - "templates": "" - }, - "ss_configuration": { - "dailyhrslimit": "" - }, - "ssbuckets": { - "color": "", - "gte": "", - "id": "", - "label": "", - "lt": "", - "target": "" - }, - "state": "", - "state_tax_id": "", - "status": "", - "statuses": { - "active_statuses": "", - "additional_board_statuses": "", - "color": "", - "default_arrived": "", - "default_bo": "", - "default_canceled": "", - "default_completed": "", - "default_delivered": "", - "default_exported": "", - "default_imported": "", - "default_invoiced": "", - "default_ordered": "", - "default_quote": "", - "default_received": "", - "default_returned": "", - "default_scheduled": "", - "default_void": "", - "open_statuses": "", - "post_production_statuses": "", - "pre_production_statuses": "", - "production_colors": "", - "production_statuses": "", - "ready_statuses": "" - }, - "target_touchtime": "", - "timezone": "", - "tt_allow_post_to_invoiced": "", - "tt_enforce_hours_for_tech_console": "", - "use_fippa": "", - "use_paint_scale_data": "", - "uselocalmediaserver": "", - "website": "", - "zip_post": "" - }, - "labels": { - "2tiername": "", - "2tiersetup": "", - "2tiersource": "", - "accountingsetup": "", - "accountingtiers": "", - "alljobstatuses": "", - "allopenjobstatuses": "", - "apptcolors": "", - "businessinformation": "", - "checklists": "", - "csiq": "", - "customtemplates": "", - "defaultcostsmapping": "", - "defaultprofitsmapping": "", - "deliverchecklist": "", - "dms": { - "cdk": { - "controllist": "", - "payers": "" - }, - "cdk_dealerid": "", - "costsmapping": "", - "dms_allocations": "", - "pbs_serialnumber": "", - "profitsmapping": "", - "title": "" - }, - "emaillater": "", - "employee_teams": "", - "employees": "", - "estimators": "", - "filehandlers": "", - "insurancecos": "", - "intakechecklist": "", - "intellipay": "", - "intellipay_cash_discount": "", - "jobstatuses": "", - "laborrates": "", - "licensing": "", - "md_parts_scan": "", - "md_ro_guard": "", - "md_tasks_presets": "", - "md_to_emails": "", - "md_to_emails_emails": "", - "messagingpresets": "", - "notemplatesavailable": "", - "notespresets": "", - "orderstatuses": "", - "partslocations": "", - "partsscan": "", - "printlater": "", - "qbo": "", - "qbo_departmentid": "", - "qbo_usa": "", - "rbac": "", - "responsibilitycenters": { - "costs": "", - "profits": "", - "sales_tax_codes": "", - "tax_accounts": "", - "title": "" - }, - "roguard": { - "title": "" - }, - "scheduling": "", - "scoreboardsetup": "", - "shopinfo": "", - "speedprint": "", - "ssbuckets": "", - "systemsettings": "", - "task-presets": "", - "workingdays": "" - }, - "successes": { - "areyousure": "", - "defaultviewcreated": "", - "save": "", - "unsavedchanges": "" - }, - "validation": { - "centermustexist": "", - "larsplit": "", - "useremailmustexist": "" - } - }, - "checklist": { - "actions": { - "printall": "" - }, - "errors": { - "complete": "", - "nochecklist": "" - }, - "labels": { - "addtoproduction": "", - "allow_text_message": "", - "checklist": "", - "printpack": "", - "removefromproduction": "" - }, - "successes": { - "completed": "" - } - }, - "contracts": { - "actions": { - "changerate": "", - "convertoro": "", - "decodelicense": "", - "find": "", - "printcontract": "", - "senddltoform": "" - }, - "errors": { - "fetchingjobinfo": "", - "returning": "", - "saving": "", - "selectjobandcar": "" - }, - "fields": { - "actax": "", - "actualreturn": "", - "agreementnumber": "", - "cc_cardholder": "", - "cc_expiry": "", - "cc_num": "", - "cleanupcharge": "", - "coverage": "", - "dailyfreekm": "", - "dailyrate": "", - "damage": "", - "damagewaiver": "", - "driver": "", - "driver_addr1": "", - "driver_addr2": "", - "driver_city": "", - "driver_dlexpiry": "", - "driver_dlnumber": "", - "driver_dlst": "", - "driver_dob": "", - "driver_fn": "", - "driver_ln": "", - "driver_ph1": "", - "driver_state": "", - "driver_zip": "", - "excesskmrate": "", - "federaltax": "", - "fuelin": "", - "fuelout": "", - "kmend": "", - "kmstart": "", - "length": "", - "localtax": "", - "refuelcharge": "", - "scheduledreturn": "", - "start": "", - "statetax": "", - "status": "" - }, - "labels": { - "agreement": "", - "availablecars": "", - "cardueforservice": "", - "convertform": { - "applycleanupcharge": "", - "refuelqty": "" - }, - "correctdataonform": "", - "dateinpast": "", - "dlexpirebeforereturn": "", - "driverinformation": "", - "findcontract": "", - "findermodal": "", - "insuranceexpired": "", - "noteconvertedfrom": "", - "populatefromjob": "", - "rates": "", - "time": "", - "vehicle": "", - "waitingforscan": "" - }, - "status": { - "new": "", - "out": "", - "returned": "" - }, - "successes": { - "saved": "" - } - }, - "courtesycars": { - "actions": { - "new": "", - "return": "" - }, - "errors": { - "saving": "" - }, - "fields": { - "color": "", - "dailycost": "", - "damage": "", - "fleetnumber": "", - "fuel": "", - "insuranceexpires": "", - "leaseenddate": "", - "make": "", - "mileage": "", - "model": "", - "nextservicedate": "", - "nextservicekm": "", - "notes": "", - "plate": "", - "purchasedate": "", - "readiness": "", - "registrationexpires": "", - "serviceenddate": "", - "servicestartdate": "", - "status": "", - "vin": "", - "year": "" - }, - "labels": { - "courtesycar": "", - "fuel": { - "12": "", - "14": "", - "18": "", - "34": "", - "38": "", - "58": "", - "78": "", - "empty": "", - "full": "" - }, - "outwith": "", - "return": "", - "status": "", - "uniquefleet": "", - "usage": "", - "vehicle": "" - }, - "readiness": { - "notready": "", - "ready": "" - }, - "status": { - "in": "", - "inservice": "", - "leasereturn": "", - "out": "", - "sold": "", - "unavailable": "" - }, - "successes": { - "saved": "" - } - }, - "csi": { - "actions": { - "activate": "" - }, - "errors": { - "creating": "", - "notconfigured": "", - "notfoundsubtitle": "", - "notfoundtitle": "", - "surveycompletesubtitle": "", - "surveycompletetitle": "" - }, - "fields": { - "completedon": "", - "created_at": "", - "surveyid": "", - "validuntil": "" - }, - "labels": { - "copyright": "", - "greeting": "", - "intro": "", - "nologgedinuser": "", - "nologgedinuser_sub": "", - "noneselected": "", - "title": "" - }, - "successes": { - "created": "", - "submitted": "", - "submittedsub": "" - } - }, - "dashboard": { - "actions": { - "addcomponent": "" - }, - "errors": { - "refreshrequired": "", - "updatinglayout": "" - }, - "labels": { - "bodyhrs": "", - "dollarsinproduction": "", - "phone": "", - "prodhrs": "", - "refhrs": "" - }, - "titles": { - "joblifecycle": "", - "labhours": "", - "larhours": "", - "monthlyemployeeefficiency": "", - "monthlyjobcosting": "", - "monthlylaborsales": "", - "monthlypartssales": "", - "monthlyrevenuegraph": "", - "prodhrssummary": "", - "productiondollars": "", - "productionhours": "", - "projectedmonthlysales": "", - "scheduledindate": "", - "scheduledintoday": "", - "scheduledoutdate": "", - "scheduledouttoday": "", - "tasks": "" - } - }, - "dms": { - "errors": { - "alreadyexported": "" - }, - "labels": { - "refreshallocations": "" - } - }, - "documents": { - "actions": { - "delete": "", - "download": "", - "reassign": "", - "selectallimages": "", - "selectallotherdocuments": "" - }, - "errors": { - "deletes3": "Erreur lors de la suppression du document du stockage.", - "deleting": "", - "deleting_cloudinary": "", - "getpresignurl": "Erreur lors de l'obtention de l'URL présignée pour le document. {{message}}", - "insert": "Incapable de télécharger le fichier. {{message}}", - "nodocuments": "Il n'y a pas de documents.", - "updating": "" - }, - "labels": { - "confirmdelete": "", - "doctype": "", - "newjobid": "", - "openinexplorer": "", - "optimizedimage": "", - "reassign_limitexceeded": "", - "reassign_limitexceeded_title": "", - "storageexceeded": "", - "storageexceeded_title": "", - "upload": "Télécharger", - "upload_limitexceeded": "", - "upload_limitexceeded_title": "", - "uploading": "", - "usage": "" - }, - "successes": { - "delete": "Le document a bien été supprimé.", - "edituploaded": "", - "insert": "Document téléchargé avec succès.", - "updated": "" - } - }, - "emails": { - "errors": { - "notsent": "Courriel non envoyé. Erreur rencontrée lors de l'envoi de {{message}}" - }, - "fields": { - "cc": "", - "from": "", - "subject": "", - "to": "" - }, - "labels": { - "attachments": "", - "documents": "", - "emailpreview": "", - "generatingemail": "", - "pdfcopywillbeattached": "", - "preview": "" - }, - "successes": { - "sent": "E-mail envoyé avec succès." - } - }, - "employee_teams": { - "actions": { - "new": "", - "newmember": "" - }, - "fields": { - "active": "", - "employeeid": "", - "max_load": "", - "name": "", - "percentage": "" - } - }, - "employees": { - "actions": { - "addvacation": "", - "new": "Nouvel employé", - "newrate": "" - }, - "errors": { - "delete": "Erreur rencontrée lors de la suppression de l'employé. {{message}}", - "save": "Une erreur s'est produite lors de l'enregistrement de l'employé. {{message}}", - "validation": "Veuillez cocher tous les champs.", - "validationtitle": "Impossible d'enregistrer l'employé." - }, - "fields": { - "active": "Actif?", - "base_rate": "Taux de base", - "cost_center": "Centre de coûts", - "employee_number": "Numéro d'employé", - "external_id": "", - "first_name": "Prénom", - "flat_rate": "Taux fixe (désactivé est le temps normal)", - "hire_date": "Date d'embauche", - "last_name": "Nom de famille", - "pin": "", - "rate": "", - "termination_date": "Date de résiliation", - "user_email": "", - "vacation": { - "end": "", - "length": "", - "start": "" - } - }, - "labels": { - "actions": "", - "active": "", - "endmustbeafterstart": "", - "flat_rate": "", - "inactive": "", - "name": "", - "rate_type": "", - "status": "", - "straight_time": "" - }, - "successes": { - "delete": "L'employé a bien été supprimé.", - "save": "L'employé a enregistré avec succès.", - "vacationadded": "" - }, - "validation": { - "unique_employee_number": "" - } - }, - "eula": { - "buttons": { - "accept": "Accept EULA" - }, - "content": { - "never_scrolled": "You must scroll to the bottom of the Terms and Conditions before accepting." - }, - "errors": { - "acceptance": { - "description": "Something went wrong while accepting the EULA. Please try again.", - "message": "Eula Acceptance Error" - } - }, - "labels": { - "accepted_terms": "I accept the terms and conditions of this agreement.", - "address": "Address", - "business_name": "Legal Business Name", - "date_accepted": "Date Accepted", - "first_name": "First Name", - "last_name": "Last Name", - "phone_number": "Phone Number" - }, - "messages": { - "accepted_terms": "Please accept the terms and conditions of this agreement.", - "business_name": "Please enter your legal business name.", - "date_accepted": "Please enter Today's Date.", - "first_name": "Please enter your first name.", - "last_name": "Please enter your last name.", - "phone_number": "Please enter your phone number." - }, - "titles": { - "modal": "Terms and Conditions", - "upper_card": "Acknowledgement" - } - }, - "exportlogs": { - "fields": { - "createdat": "" - }, - "labels": { - "attempts": "", - "priorsuccesfulexport": "" - } - }, - "general": { - "actions": { - "add": "", - "autoupdate": "", - "calculate": "", - "cancel": "", - "clear": "", - "close": "", - "copied": "", - "copylink": "", - "create": "", - "defaults": "", - "delay": "", - "delete": "Effacer", - "deleteall": "", - "deselectall": "", - "download": "", - "edit": "modifier", - "login": "", - "next": "", - "previous": "", - "print": "", - "refresh": "", - "remove": "", - "remove_alert": "", - "reset": " Rétablir l'original.", - "resetpassword": "", - "save": "sauvegarder", - "saveandnew": "", - "saveas": "", - "selectall": "", - "send": "", - "sendbysms": "", - "senderrortosupport": "", - "submit": "", - "tryagain": "", - "view": "", - "viewreleasenotes": "" - }, - "errors": { - "fcm": "", - "notfound": "", - "sizelimit": "" - }, - "itemtypes": { - "contract": "", - "courtesycar": "", - "job": "", - "owner": "", - "vehicle": "" - }, - "labels": { - "actions": "actes", - "areyousure": "", - "barcode": "code à barre", - "cancel": "", - "clear": "", - "confirmpassword": "", - "created_at": "", - "date": "", - "datetime": "", - "email": "", - "errors": "", - "excel": "", - "exceptiontitle": "", - "friday": "", - "globalsearch": "", - "help": "", - "hours": "", - "in": "dans", - "instanceconflictext": "", - "instanceconflictitle": "", - "item": "", - "label": "", - "loading": "Chargement...", - "loadingapp": "Chargement de {{app}}", - "loadingshop": "Chargement des données de la boutique ...", - "loggingin": "Vous connecter ...", - "markedexported": "", - "media": "", - "message": "", - "monday": "", - "na": "N / A", - "newpassword": "", - "no": "", - "nointernet": "", - "nointernet_sub": "", - "none": "", - "out": "En dehors", - "password": "", - "passwordresetsuccess": "", - "passwordresetsuccess_sub": "", - "passwordresetvalidatesuccess": "", - "passwordresetvalidatesuccess_sub": "", - "passwordsdonotmatch": "", - "print": "", - "refresh": "", - "reports": "", - "required": "", - "saturday": "", - "search": "Chercher...", - "searchresults": "", - "selectdate": "", - "sendagain": "", - "sendby": "", - "signin": "", - "sms": "", - "status": "", - "sub_status": { - "expired": "" - }, - "successful": "", - "sunday": "", - "text": "", - "thursday": "", - "total": "", - "totals": "", - "tuesday": "", - "tvmode": "", - "unknown": "Inconnu", - "unsavedchanges": "", - "username": "", - "view": "", - "wednesday": "", - "yes": "" - }, - "languages": { - "english": "Anglais", - "french": "Francais", - "spanish": "Espanol" - }, - "messages": { - "exception": "", - "newversionmessage": "", - "newversiontitle": "", - "noacctfilepath": "", - "nofeatureaccess": "", - "noshop": "", - "notfoundsub": "", - "notfoundtitle": "", - "partnernotrunning": "", - "rbacunauth": "", - "unsavedchanges": "Vous avez des changements non enregistrés.", - "unsavedchangespopup": "" - }, - "validation": { - "invalidemail": "S'il vous plaît entrer un email valide.", - "invalidphone": "", - "required": "Ce champ est requis." - } - }, - "help": { - "actions": { - "connect": "" - }, - "labels": { - "codeplacholder": "", - "rescuedesc": "", - "rescuetitle": "" - } - }, - "intake": { - "labels": { - "printpack": "" - } - }, - "inventory": { - "actions": { - "addtoinventory": "", - "addtoro": "", - "consumefrominventory": "", - "edit": "", - "new": "" - }, - "errors": { - "inserting": "" - }, - "fields": { - "comment": "", - "manualinvoicenumber": "", - "manualvendor": "" - }, - "labels": { - "consumedbyjob": "", - "deleteconfirm": "", - "frombillinvoicenumber": "", - "fromvendor": "", - "inventory": "", - "showall": "", - "showavailable": "" - }, - "successes": { - "deleted": "", - "inserted": "", - "updated": "" - } - }, - "job_lifecycle": { - "columns": { - "duration": "", - "end": "", - "human_readable": "", - "percentage": "", - "relative_end": "", - "relative_start": "", - "start": "", - "status": "", - "status_count": "", - "value": "" - }, - "content": { - "calculated_based_on": "", - "current_status_accumulated_time": "", - "data_unavailable": "", - "jobs_in_since": "", - "legend_title": "", - "loading": "", - "not_available": "", - "previous_status_accumulated_time": "", - "title": "", - "title_durations": "", - "title_loading": "", - "title_transitions": "" - }, - "errors": { - "fetch": "Erreur lors de l'obtention des données du cycle de vie des tâches" - }, - "titles": { - "dashboard": "", - "top_durations": "" - } - }, - "job_payments": { - "buttons": { - "create_short_link": "", - "goback": "", - "proceedtopayment": "", - "refundpayment": "" - }, - "notifications": { - "error": { - "description": "", - "openingip": "", - "title": "" - } - }, - "titles": { - "amount": "", - "dateOfPayment": "", - "descriptions": "", - "hint": "", - "payer": "", - "payername": "", - "paymentid": "", - "paymentnum": "", - "paymenttype": "", - "refundamount": "", - "transactionid": "" - } - }, - "joblines": { - "actions": { - "assign_team": "", - "converttolabor": "", - "dispatchparts": "", - "new": "" - }, - "errors": { - "creating": "", - "updating": "" - }, - "fields": { - "act_price": "Prix actuel", - "act_price_before_ppc": "", - "adjustment": "", - "ah_detail_line": "", - "amount": "", - "assigned_team": "", - "assigned_team_name": "", - "create_ppc": "", - "db_price": "Prix de la base de données", - "lbr_types": { - "LA1": "", - "LA2": "", - "LA3": "", - "LA4": "", - "LAA": "", - "LAB": "", - "LAD": "", - "LAE": "", - "LAF": "", - "LAG": "", - "LAM": "", - "LAR": "", - "LAS": "", - "LAU": "" - }, - "line_desc": "Description de la ligne", - "line_ind": "S#", - "line_no": "", - "location": "", - "mod_lb_hrs": "Heures de travail", - "mod_lbr_ty": "Type de travail", - "notes": "", - "oem_partno": "Pièce OEM #", - "op_code_desc": "", - "part_qty": "", - "part_type": "Type de pièce", - "part_types": { - "CCC": "", - "CCD": "", - "CCDR": "", - "CCF": "", - "CCM": "", - "PAA": "", - "PAC": "", - "PAE": "", - "PAG": "", - "PAL": "", - "PAM": "", - "PAN": "", - "PAO": "", - "PAP": "", - "PAR": "", - "PAS": "", - "PASL": "" - }, - "profitcenter_labor": "", - "profitcenter_part": "", - "prt_dsmk_m": "", - "prt_dsmk_p": "", - "status": "Statut", - "tax_part": "", - "total": "", - "unq_seq": "Seq #" - }, - "labels": { - "adjustmenttobeadded": "", - "billref": "", - "convertedtolabor": "", - "edit": "Ligne d'édition", - "ioucreated": "", - "new": "Nouvelle ligne", - "nostatus": "", - "presets": "" - }, - "successes": { - "created": "", - "saved": "", - "updated": "" - }, - "validations": { - "ahdetailonlyonuserdefinedtypes": "", - "hrsrequirediflbrtyp": "", - "requiredifparttype": "", - "zeropriceexistingpart": "" - } - }, - "jobs": { - "actions": { - "addDocuments": "Ajouter des documents de travail", - "addNote": "Ajouter une note", - "addtopartsqueue": "", - "addtoproduction": "", - "addtoscoreboard": "", - "allocate": "", - "autoallocate": "", - "changefilehandler": "", - "changelaborrate": "", - "changestatus": "Changer le statut", - "changestimator": "", - "convert": "Convertir", - "createiou": "", - "deliver": "", - "dms": { - "addpayer": "", - "createnewcustomer": "", - "findmakemodelcode": "", - "getmakes": "", - "labels": { - "refreshallocations": "" - }, - "post": "", - "refetchmakesmodels": "", - "usegeneric": "", - "useselected": "" - }, - "dmsautoallocate": "", - "export": "", - "exportcustdata": "", - "exportselected": "", - "filterpartsonly": "", - "generatecsi": "", - "gotojob": "", - "intake": "", - "manualnew": "", - "mark": "", - "markasexported": "", - "markpstexempt": "", - "markpstexemptconfirm": "", - "postbills": "Poster des factures", - "printCenter": "Centre d'impression", - "recalculate": "", - "reconcile": "", - "removefromproduction": "", - "schedule": "Programme", - "sendcsi": "", - "sendpartspricechange": "", - "sendtodms": "", - "sync": "", - "taxprofileoverride": "", - "taxprofileoverride_confirm": "", - "uninvoice": "", - "unvoid": "", - "viewchecklist": "", - "viewdetail": "" - }, - "errors": { - "addingtoproduction": "", - "cannotintake": "", - "closing": "", - "creating": "", - "deleted": "Erreur lors de la suppression du travail.", - "exporting": "", - "exporting-partner": "", - "invoicing": "", - "noaccess": "Ce travail n'existe pas ou vous n'y avez pas accès.", - "nodamage": "", - "nodates": "Aucune date spécifiée pour ce travail.", - "nofinancial": "", - "nojobselected": "Aucun travail n'est sélectionné.", - "noowner": "Aucun propriétaire associé.", - "novehicle": "Aucun véhicule associé.", - "partspricechange": "", - "saving": "Erreur rencontrée lors de la sauvegarde de l'enregistrement.", - "scanimport": "", - "totalscalc": "", - "updating": "", - "validation": "Veuillez vous assurer que tous les champs sont correctement entrés.", - "validationtitle": "Erreur de validation", - "voiding": "" - }, - "fields": { - "active_tasks": "", - "actual_completion": "Achèvement réel", - "actual_delivery": "Livraison réelle", - "actual_in": "En réel", - "adjustment_bottom_line": "Ajustements", - "adjustmenthours": "", - "alt_transport": "", - "area_of_damage_impact": { - "10": "", - "11": "", - "12": "", - "13": "", - "14": "", - "15": "", - "16": "", - "25": "", - "26": "", - "27": "", - "28": "", - "34": "", - "01": "", - "02": "", - "03": "", - "04": "", - "05": "", - "06": "", - "07": "", - "08": "", - "09": "" - }, - "auto_add_ats": "", - "ca_bc_pvrt": "", - "ca_customer_gst": "", - "ca_gst_registrant": "", - "category": "", - "ccc": "", - "ccd": "", - "ccdr": "", - "ccf": "", - "ccm": "", - "cieca_id": "CIECA ID", - "cieca_pfl": { - "lbr_adjp": "", - "lbr_tax_in": "", - "lbr_taxp": "", - "lbr_tx_in1": "", - "lbr_tx_in2": "", - "lbr_tx_in3": "", - "lbr_tx_in4": "", - "lbr_tx_in5": "" - }, - "cieca_pfo": { - "stor_t_in1": "", - "stor_t_in2": "", - "stor_t_in3": "", - "stor_t_in4": "", - "stor_t_in5": "", - "tow_t_in1": "", - "tow_t_in2": "", - "tow_t_in3": "", - "tow_t_in4": "", - "tow_t_in5": "" - }, - "claim_total": "Total réclamation", - "class": "", - "clm_no": "Prétendre #", - "clm_total": "Total réclamation", - "comment": "", - "customerowing": "Client propriétaire", - "date_estimated": "Date estimée", - "date_exported": "Exportés", - "date_invoiced": "Facturé", - "date_last_contacted": "", - "date_lost_sale": "", - "date_next_contact": "", - "date_open": "Ouvrir", - "date_rentalresp": "", - "date_repairstarted": "", - "date_scheduled": "Prévu", - "date_towin": "", - "date_void": "", - "ded_amt": "Déductible", - "ded_note": "", - "ded_status": "Statut de franchise", - "depreciation_taxes": "Amortissement / taxes", - "dms": { - "address": "", - "amount": "", - "center": "", - "control_type": { - "account_number": "" - }, - "cost": "", - "cost_dms_acctnumber": "", - "dms_make": "", - "dms_model": "", - "dms_model_override": "", - "dms_unsold": "", - "dms_wip_acctnumber": "", - "id": "", - "inservicedate": "", - "journal": "", - "lines": "", - "name1": "", - "payer": { - "amount": "", - "control_type": "", - "controlnumber": "", - "dms_acctnumber": "", - "name": "" - }, - "sale": "", - "sale_dms_acctnumber": "", - "story": "", - "vinowner": "" - }, - "dms_allocation": "", - "driveable": "", - "employee_body": "", - "employee_csr": "représentant du service à la clientèle", - "employee_csr_writer": "", - "employee_prep": "", - "employee_refinish": "", - "est_addr1": "Adresse de l'évaluateur", - "est_co_nm": "Expert", - "est_ct_fn": "Prénom de l'évaluateur", - "est_ct_ln": "Nom de l'évaluateur", - "est_ea": "Courriel de l'évaluateur", - "est_ph1": "Numéro de téléphone de l'évaluateur", - "federal_tax_payable": "Impôt fédéral à payer", - "federal_tax_rate": "", - "ins_addr1": "Adresse Insurance Co.", - "ins_city": "Insurance City", - "ins_co_id": "ID de la compagnie d'assurance", - "ins_co_nm": "Nom de la compagnie d'assurance", - "ins_co_nm_short": "", - "ins_ct_fn": "Prénom du gestionnaire de fichiers", - "ins_ct_ln": "Nom du gestionnaire de fichiers", - "ins_ea": "Courriel du gestionnaire de fichiers", - "ins_ph1": "Numéro de téléphone du gestionnaire de fichiers", - "intake": { - "label": "", - "max": "", - "min": "", - "name": "", - "required": "", - "type": "" - }, - "invoice_final_note": "", - "kmin": "Kilométrage en", - "kmout": "Kilométrage hors", - "la1": "", - "la2": "", - "la3": "", - "la4": "", - "laa": "", - "lab": "", - "labor_rate_desc": "Nom du taux de main-d'œuvre", - "lad": "", - "lae": "", - "laf": "", - "lag": "", - "lam": "", - "lar": "", - "las": "", - "lau": "", - "local_tax_rate": "", - "loss_date": "Date de perte", - "loss_desc": "", - "loss_of_use": "", - "lost_sale_reason": "", - "ma2s": "", - "ma3s": "", - "mabl": "", - "macs": "", - "mahw": "", - "mapa": "", - "mash": "", - "matd": "", - "materials": { - "MAPA": "", - "MASH": "", - "cal_maxdlr": "", - "cal_opcode": "", - "mat_adjp": "", - "mat_taxp": "", - "mat_tx_in1": "", - "mat_tx_in2": "", - "mat_tx_in3": "", - "mat_tx_in4": "", - "mat_tx_in5": "", - "materials": "", - "tax_ind": "" - }, - "other_amount_payable": "Autre montant à payer", - "owner": "Propriétaire", - "owner_owing": "Cust. Owes", - "ownr_ea": "Email", - "ownr_ph1": "Téléphone 1", - "ownr_ph2": "", - "paa": "", - "pac": "", - "pae": "", - "pag": "", - "pal": "", - "pam": "", - "pan": "", - "pao": "", - "pap": "", - "par": "", - "parts_tax_rates": { - "prt_discp": "", - "prt_mktyp": "", - "prt_mkupp": "", - "prt_tax_in": "", - "prt_tax_rt": "", - "prt_tx_in1": "", - "prt_tx_in2": "", - "prt_tx_in3": "", - "prt_tx_in4": "", - "prt_tx_in5": "", - "prt_tx_ty1": "", - "prt_type": "" - }, - "partsstatus": "", - "pas": "", - "pay_date": "Date d'Pay", - "phoneshort": "PH", - "po_number": "", - "policy_no": "Politique #", - "ponumber": "Numéro de bon de commande", - "production_vars": { - "note": "" - }, - "qb_multiple_payers": { - "amount": "", - "name": "" - }, - "queued_for_parts": "", - "rate_ats": "", - "rate_la1": "Taux LA1", - "rate_la2": "Taux LA2", - "rate_la3": "Taux LA3", - "rate_la4": "Taux LA4", - "rate_laa": "Taux d'aluminium", - "rate_lab": "Taux de la main-d'œuvre", - "rate_lad": "Taux de diagnostic", - "rate_lae": "Tarif électrique", - "rate_laf": "Taux de trame", - "rate_lag": "Taux de verre", - "rate_lam": "Taux mécanique", - "rate_lar": "Taux de finition", - "rate_las": "", - "rate_lau": "Taux d'aluminium", - "rate_ma2s": "Taux de peinture en 2 étapes", - "rate_ma3s": "Taux de peinture en 3 étapes", - "rate_mabl": "MABL ??", - "rate_macs": "MACS ??", - "rate_mahw": "Taux de déchets dangereux", - "rate_mapa": "Taux de matériaux de peinture", - "rate_mash": "Tarif du matériel de la boutique", - "rate_matd": "Taux d'élimination des pneus", - "referral_source_extra": "", - "referral_source_other": "", - "referralsource": "Source de référence", - "regie_number": "Enregistrement #", - "repairtotal": "Réparation totale", - "ro_number": "RO #", - "scheduled_completion": "Achèvement planifié", - "scheduled_delivery": "Livraison programmée", - "scheduled_in": "Planifié dans", - "selling_dealer": "Revendeur vendeur", - "selling_dealer_contact": "Contacter le revendeur", - "servicecar": "Voiture de service", - "servicing_dealer": "Concessionnaire", - "servicing_dealer_contact": "Contacter le concessionnaire", - "special_coverage_policy": "Politique de couverture spéciale", - "specialcoveragepolicy": "Politique de couverture spéciale", - "state_tax_rate": "", - "status": "Statut de l'emploi", - "storage_payable": "Stockage", - "tax_lbr_rt": "", - "tax_levies_rt": "", - "tax_paint_mat_rt": "", - "tax_registration_number": "", - "tax_shop_mat_rt": "", - "tax_str_rt": "", - "tax_sub_rt": "", - "tax_tow_rt": "", - "towin": "", - "towing_payable": "Remorquage à payer", - "unitnumber": "Unité #", - "updated_at": "Mis à jour à", - "uploaded_by": "Telechargé par", - "vehicle": "Véhicule" - }, - "forms": { - "admindates": "", - "appraiserinfo": "", - "claiminfo": "", - "estdates": "", - "laborrates": "", - "lossinfo": "", - "other": "", - "repairdates": "", - "scheddates": "" - }, - "labels": { - "accountsreceivable": "", - "act_price_ppc": "", - "actual_completion_inferred": "", - "actual_delivery_inferred": "", - "actual_in_inferred": "", - "additionalpayeroverallocation": "", - "additionaltotal": "", - "adjustmentrate": "", - "adjustments": "", - "adminwarning": "", - "allocations": "", - "alreadyaddedtoscoreboard": "", - "alreadyclosed": "", - "appointmentconfirmation": "Envoyer une confirmation au client?", - "associationwarning": "", - "audit": "", - "available": "", - "availablejobs": "", - "ca_bc_pvrt": { - "days": "", - "rate": "" - }, - "ca_gst_all_if_null": "", - "calc_repair_days": "", - "calc_repair_days_tt": "", - "calc_scheuled_completion": "", - "cards": { - "customer": "Informations client", - "damage": "Zone de dommages", - "dates": "Rendez-vous", - "documents": "Documents récents", - "estimator": "Estimateur", - "filehandler": "Gestionnaire de fichiers", - "insurance": "Détails de l'assurance", - "more": "Plus", - "notes": "Remarques", - "parts": "les pièces", - "totals": "Totaux", - "vehicle": "Véhicule" - }, - "changeclass": "", - "checklistcompletedby": "", - "checklistdocuments": "", - "checklists": "", - "cieca_pfl": "", - "cieca_pfo": "", - "cieca_pft": "", - "closeconfirm": "", - "closejob": "", - "closingperiod": "", - "contracts": "", - "convertedtolabor": "", - "cost": "", - "cost_Additional": "", - "cost_labor": "", - "cost_parts": "", - "cost_sublet": "", - "costs": "", - "create": { - "jobinfo": "", - "newowner": "", - "newvehicle": "", - "novehicle": "", - "ownerinfo": "", - "vehicleinfo": "" - }, - "createiouwarning": "", - "creating_new_job": "Création d'un nouvel emploi ...", - "deductible": { - "stands": "", - "waived": "" - }, - "deleteconfirm": "", - "deletedelivery": "", - "deleteintake": "", - "deliverchecklist": "", - "difference": "", - "diskscan": "", - "dms": { - "apexported": "", - "damageto": "", - "defaultstory": "", - "disablebillwip": "", - "invoicedatefuture": "", - "kmoutnotgreaterthankmin": "", - "logs": "", - "notallocated": "", - "postingform": "", - "totalallocated": "" - }, - "documents": "Les documents", - "documents-images": "", - "documents-other": "", - "duplicateconfirm": "", - "emailaudit": "", - "employeeassignments": "", - "estimatelines": "", - "estimator": "", - "existing_jobs": "Emplois existants", - "federal_tax_amt": "", - "gpdollars": "", - "gppercent": "", - "hrs_claimed": "", - "hrs_total": "", - "importnote": "", - "inproduction": "", - "intakechecklist": "", - "iou": "", - "job": "", - "jobcosting": "", - "jobtotals": "", - "labor_hrs": "", - "labor_rates_subtotal": "", - "laborallocations": "", - "labortotals": "", - "lines": "Estimer les lignes", - "local_tax_amt": "", - "mapa": "", - "markforreexport": "", - "mash": "", - "masterbypass": "", - "materials": { - "mapa": "" - }, - "missingprofileinfo": "", - "multipayers": "", - "net_repairs": "", - "notes": "Remarques", - "othertotal": "", - "outstanding_ar": "", - "outstanding_credit_memos": "", - "outstanding_ppd": "", - "outstanding_reconciliation_discrep": "", - "outstanding_sublets": "", - "outstandinghours": "", - "override_header": "Remplacer l'en-tête d'estimation à l'importation?", - "ownerassociation": "", - "parts": "les pièces", - "parts_lines": "", - "parts_received": "", - "parts_tax_rates": "", - "partsfilter": "", - "partssubletstotal": "", - "partstotal": "", - "performance": "", - "pimraryamountpayable": "", - "plitooltips": { - "billtotal": "", - "calculatedcreditsnotreceived": "", - "creditmemos": "", - "creditsnotreceived": "", - "discrep1": "", - "discrep2": "", - "discrep3": "", - "laboradj": "", - "partstotal": "", - "totalreturns": "" - }, - "ppc": "", - "ppdnotexported": "", - "profileadjustments": "", - "profitbypassrequired": "", - "profits": "", - "prt_dsmk_total": "", - "rates": "Les taux", - "rates_subtotal": "", - "reconciliation": { - "billlinestotal": "", - "byassoc": "", - "byprice": "", - "clear": "", - "discrepancy": "", - "joblinestotal": "", - "multipleactprices": "", - "multiplebilllines": "", - "multiplebillsforactprice": "", - "removedpartsstrikethrough": "" - }, - "reconciliationheader": "", - "relatedros": "", - "remove_from_ar": "", - "returntotals": "", - "ro_guard": { - "enforce_ar": "", - "enforce_bills": "", - "enforce_cm": "", - "enforce_labor": "", - "enforce_ppd": "", - "enforce_profit": "", - "enforce_sublet": "", - "enforce_validation": "", - "enforced": "" - }, - "roguard": "", - "roguardwarnings": "", - "rosaletotal": "", - "sale_additional": "", - "sale_labor": "", - "sale_parts": "", - "sale_sublet": "", - "sales": "", - "savebeforeconversion": "", - "scheduledinchange": "", - "specialcoveragepolicy": "", - "state_tax_amt": "", - "subletsnotcompleted": "", - "subletstotal": "", - "subtotal": "", - "supplementnote": "", - "suspended": "", - "suspense": "", - "tasks": "", - "threshhold": "", - "total_cost": "", - "total_cust_payable": "", - "total_repairs": "", - "total_sales": "", - "total_sales_tax": "", - "totals": "", - "unvoidnote": "", - "update_scheduled_completion": "", - "vehicle_info": "Véhicule", - "vehicleassociation": "", - "viewallocations": "", - "voidjob": "", - "voidnote": "" - }, - "successes": { - "addedtoproduction": "", - "all_deleted": "{{count}} travaux supprimés avec succès.", - "closed": "", - "converted": "Travail converti avec succès.", - "created": "Le travail a été créé avec succès. Clique pour voir.", - "creatednoclick": "", - "delete": "", - "deleted": "Le travail a bien été supprimé.", - "duplicated": "", - "exported": "", - "invoiced": "", - "ioucreated": "", - "partsqueue": "", - "save": "Le travail a été enregistré avec succès.", - "savetitle": "Enregistrement enregistré avec succès.", - "supplemented": "Travail complété avec succès.", - "updated": "", - "voided": "" - } - }, - "landing": { - "bigfeature": { - "subtitle": "", - "title": "" - }, - "footer": { - "company": { - "about": "", - "contact": "", - "disclaimers": "", - "name": "", - "privacypolicy": "" - }, - "io": { - "help": "", - "name": "", - "status": "" - }, - "slogan": "" - }, - "hero": { - "button": "", - "title": "" - }, - "labels": { - "features": "", - "managemyshop": "", - "pricing": "" - }, - "pricing": { - "basic": { - "name": "", - "sub": "" - }, - "essentials": { - "name": "", - "sub": "" - }, - "pricingtitle": "", - "pro": { - "name": "", - "sub": "" - }, - "title": "", - "unlimited": { - "name": "", - "sub": "" - } - } - }, - "menus": { - "currentuser": { - "languageselector": "La langue", - "profile": "Profil" - }, - "header": { - "accounting": "", - "accounting-payables": "", - "accounting-payments": "", - "accounting-receivables": "", - "activejobs": "Emplois actifs", - "all_tasks": "", - "alljobs": "", - "allpayments": "", - "availablejobs": "Emplois disponibles", - "bills": "", - "courtesycars": "", - "courtesycars-all": "", - "courtesycars-contracts": "", - "courtesycars-newcontract": "", - "create_task": "", - "customers": "Les clients", - "dashboard": "", - "enterbills": "", - "entercardpayment": "", - "enterpayment": "", - "entertimeticket": "", - "export": "", - "export-logs": "", - "help": "", - "home": "Accueil", - "inventory": "", - "jobs": "Emplois", - "my_tasks": "", - "newjob": "", - "owners": "Propriétaires", - "parts-queue": "", - "phonebook": "", - "productionboard": "", - "productionlist": "", - "readyjobs": "", - "recent": "", - "reportcenter": "", - "rescueme": "", - "schedule": "Programme", - "scoreboard": "", - "search": { - "bills": "", - "jobs": "", - "owners": "", - "payments": "", - "phonebook": "", - "vehicles": "" - }, - "shiftclock": "", - "shop": "Mon magasin", - "shop_config": "Configuration", - "shop_csi": "", - "shop_templates": "", - "shop_vendors": "Vendeurs", - "tasks": "", - "temporarydocs": "", - "timetickets": "", - "ttapprovals": "", - "vehicles": "Véhicules" - }, - "jobsactions": { - "admin": "", - "cancelallappointments": "", - "closejob": "", - "deletejob": "", - "duplicate": "", - "duplicatenolines": "", - "newcccontract": "", - "void": "" - }, - "jobsdetail": { - "claimdetail": "Détails de la réclamation", - "dates": "Rendez-vous", - "financials": "", - "general": "", - "insurance": "", - "labor": "La main d'oeuvre", - "lifecycle": "", - "parts": "", - "partssublet": "Pièces / Sous-location", - "rates": "", - "repairdata": "Données de réparation", - "totals": "" - }, - "profilesidebar": { - "profile": "Mon profil", - "shops": "Mes boutiques" - }, - "tech": { - "assignedjobs": "", - "claimtask": "", - "dispatchedparts": "", - "home": "", - "jobclockin": "", - "jobclockout": "", - "joblookup": "", - "login": "", - "logout": "", - "productionboard": "", - "productionlist": "", - "shiftclockin": "" - } - }, - "messaging": { - "actions": { - "link": "", - "new": "" - }, - "errors": { - "invalidphone": "", - "noattachedjobs": "", - "updatinglabel": "" - }, - "labels": { - "addlabel": "", - "archive": "", - "maxtenimages": "", - "messaging": "Messagerie", - "noallowtxt": "", - "nojobs": "", - "nopush": "", - "phonenumber": "", - "presets": "", - "recentonly": "", - "selectmedia": "", - "sentby": "", - "typeamessage": "Envoyer un message...", - "unarchive": "" - }, - "render": { - "conversation_list": "" - } - }, - "notes": { - "actions": { - "actions": "actes", - "deletenote": "Supprimer la note", - "edit": "Note éditée", - "new": "Nouvelle note", - "savetojobnotes": "" - }, - "errors": { - "inserting": "" - }, - "fields": { - "createdby": "Créé par", - "critical": "Critique", - "private": "privé", - "text": "Contenu", - "type": "", - "types": { - "customer": "", - "general": "", - "office": "", - "paint": "", - "parts": "", - "shop": "", - "supplement": "" - }, - "updatedat": "Mis à jour à" - }, - "labels": { - "addtorelatedro": "", - "newnoteplaceholder": "Ajouter une note...", - "notetoadd": "", - "systemnotes": "", - "usernotes": "" - }, - "successes": { - "create": "Remarque créée avec succès.", - "deleted": "Remarque supprimée avec succès.", - "updated": "Remarque mise à jour avec succès." - } - }, - "owner": { - "labels": { - "noownerinfo": "" - } - }, - "owners": { - "actions": { - "update": "" - }, - "errors": { - "deleting": "", - "noaccess": "L'enregistrement n'existe pas ou vous n'y avez pas accès.", - "saving": "", - "selectexistingornew": "" - }, - "fields": { - "address": "Adresse", - "allow_text_message": "Autorisation de texte?", - "name": "Prénom", - "note": "", - "ownr_addr1": "Adresse", - "ownr_addr2": "Adresse 2 ", - "ownr_city": "Ville", - "ownr_co_nm": "", - "ownr_ctry": "Pays", - "ownr_ea": "Email", - "ownr_fn": "Prénom", - "ownr_ln": "Nom de famille", - "ownr_ph1": "Téléphone 1", - "ownr_ph2": "", - "ownr_st": "Etat / Province", - "ownr_title": "Titre", - "ownr_zip": "Zip / code postal", - "preferred_contact": "Méthode de contact préférée", - "tax_number": "" - }, - "forms": { - "address": "", - "contact": "", - "name": "" - }, - "labels": { - "create_new": "Créez un nouvel enregistrement de propriétaire.", - "deleteconfirm": "", - "existing_owners": "Propriétaires existants", - "fromclaim": "", - "fromowner": "", - "relatedjobs": "", - "updateowner": "" - }, - "successes": { - "delete": "", - "save": "Le propriétaire a bien enregistré." - } - }, - "parts": { - "actions": { - "order": "Commander des pièces", - "orderinhouse": "" - } - }, - "parts_dispatch": { - "actions": { - "accept": "" - }, - "errors": { - "accepting": "", - "creating": "" - }, - "fields": { - "number": "", - "percent_accepted": "" - }, - "labels": { - "notyetdispatched": "", - "parts_dispatch": "" - } - }, - "parts_dispatch_lines": { - "fields": { - "accepted_at": "" - } - }, - "parts_orders": { - "actions": { - "backordered": "", - "receive": "", - "receivebill": "" - }, - "errors": { - "associatedbills": "", - "backordering": "", - "creating": "Erreur rencontrée lors de la création de la commande de pièces.", - "oec": "", - "saving": "", - "updating": "" - }, - "fields": { - "act_price": "", - "backordered_eta": "", - "backordered_on": "", - "cm_received": "", - "comments": "", - "cost": "", - "db_price": "", - "deliver_by": "", - "job_line_id": "", - "line_desc": "", - "line_remarks": "", - "lineremarks": "Remarques sur la ligne", - "oem_partno": "", - "order_date": "", - "order_number": "", - "orderedby": "", - "part_type": "", - "quantity": "", - "return": "", - "status": "" - }, - "labels": { - "allpartsto": "", - "confirmdelete": "", - "custompercent": "", - "discount": "", - "email": "Envoyé par email", - "inthisorder": "Pièces dans cette commande", - "is_quote": "", - "mark_as_received": "", - "newpartsorder": "", - "notyetordered": "", - "oec": "", - "order_type": "", - "orderhistory": "Historique des commandes", - "parts_order": "", - "parts_orders": "", - "print": "Afficher le formulaire imprimé", - "receive": "", - "removefrompartsqueue": "", - "returnpartsorder": "", - "sublet_order": "" - }, - "successes": { - "created": "Commande de pièces créée avec succès.", - "line_updated": "", - "received": "", - "return_created": "" - } - }, - "payments": { - "actions": { - "generatepaymentlink": "" - }, - "errors": { - "exporting": "", - "exporting-partner": "", - "inserting": "" - }, - "fields": { - "amount": "", - "created_at": "", - "date": "", - "exportedat": "", - "memo": "", - "payer": "", - "paymentnum": "", - "stripeid": "", - "transactionid": "", - "type": "" - }, - "labels": { - "balance": "", - "ca_bc_etf_table": "", - "customer": "", - "edit": "", - "electronicpayment": "", - "external": "", - "findermodal": "", - "insurance": "", - "markexported": "", - "markforreexport": "", - "new": "", - "signup": "", - "smspaymentreminder": "", - "title": "", - "totalpayments": "" - }, - "successes": { - "exported": "", - "markexported": "", - "markreexported": "", - "payment": "", - "paymentupdate": "", - "stripe": "" - } - }, - "phonebook": { - "actions": { - "new": "" - }, - "errors": { - "adding": "", - "saving": "" - }, - "fields": { - "address1": "", - "address2": "", - "category": "", - "city": "", - "company": "", - "country": "", - "email": "", - "fax": "", - "firstname": "", - "lastname": "", - "phone1": "", - "phone2": "", - "state": "" - }, - "labels": { - "noneselected": "", - "onenamerequired": "", - "vendorcategory": "" - }, - "successes": { - "added": "", - "deleted": "", - "saved": "" - } - }, - "printcenter": { - "appointments": { - "appointment_confirmation": "" - }, - "bills": { - "inhouse_invoice": "" - }, - "courtesycarcontract": { - "courtesy_car_contract": "", - "courtesy_car_impound": "", - "courtesy_car_inventory": "", - "courtesy_car_terms": "" - }, - "errors": { - "nocontexttype": "" - }, - "jobs": { - "3rdpartyfields": { - "addr1": "", - "addr2": "", - "addr3": "", - "attn": "", - "city": "", - "custgst": "", - "ded_amt": "", - "depreciation": "", - "other": "", - "ponumber": "", - "refnumber": "", - "sendtype": "", - "state": "", - "zip": "" - }, - "3rdpartypayer": "", - "ab_proof_of_loss": "", - "appointment_confirmation": "", - "appointment_reminder": "", - "casl_authorization": "", - "committed_timetickets_ro": "", - "coversheet_landscape": "", - "coversheet_portrait": "", - "csi_invitation": "", - "csi_invitation_action": "", - "diagnostic_authorization": "", - "dms_posting_sheet": "", - "envelope_return_address": "", - "estimate": "", - "estimate_detail": "", - "estimate_followup": "", - "express_repair_checklist": "", - "filing_coversheet_landscape": "", - "filing_coversheet_portrait": "", - "final_invoice": "", - "fippa_authorization": "", - "folder_label_multiple": "", - "glass_express_checklist": "", - "guarantee": "", - "individual_job_note": "", - "invoice_customer_payable": "", - "invoice_total_payable": "", - "iou_form": "", - "job_costing_ro": "", - "job_lifecycle_ro": "", - "job_notes": "", - "job_tasks": "", - "key_tag": "", - "labels": { - "count": "", - "labels": "", - "position": "" - }, - "lag_time_ro": "", - "mechanical_authorization": "", - "mpi_animal_checklist": "", - "mpi_eglass_auth": "", - "mpi_final_acct_sheet": "", - "mpi_final_repair_acct_sheet": "", - "paint_grid": "", - "parts_dispatch": "", - "parts_invoice_label_single": "", - "parts_label_multiple": "", - "parts_label_single": "", - "parts_list": "", - "parts_order": "", - "parts_order_confirmation": "", - "parts_order_history": "", - "parts_return_slip": "", - "payment_receipt": "", - "payment_request": "", - "payments_by_job": "", - "purchases_by_ro_detail": "", - "purchases_by_ro_summary": "", - "qc_sheet": "", - "rental_reservation": "", - "ro_totals": "", - "ro_with_description": "", - "sgi_certificate_of_repairs": "", - "sgi_windshield_auth": "", - "stolen_recovery_checklist": "", - "sublet_order": "", - "supplement_request": "", - "thank_you_ro": "", - "thirdpartypayer": "", - "timetickets_ro": "", - "vehicle_check_in": "", - "vehicle_delivery_check": "", - "window_tag": "", - "window_tag_sublet": "", - "work_authorization": "", - "worksheet_by_line_number": "", - "worksheet_sorted_by_operation": "", - "worksheet_sorted_by_operation_no_hours": "", - "worksheet_sorted_by_operation_part_type": "", - "worksheet_sorted_by_operation_type": "", - "worksheet_sorted_by_team": "" - }, - "labels": { - "groups": { - "authorization": "", - "financial": "", - "post": "", - "pre": "", - "ro": "", - "worksheet": "" - }, - "misc": "", - "repairorder": "", - "reportcentermodal": "", - "speedprint": "", - "title": "" - }, - "payments": { - "ca_bc_etf_table": "", - "exported_payroll": "" - }, - "special": { - "attendance_detail_csv": "" - }, - "subjects": { - "jobs": { - "individual_job_note": "", - "parts_dispatch": "", - "parts_order": "", - "parts_return_slip": "", - "sublet_order": "" - } - }, - "vendors": { - "purchases_by_vendor_detailed": "", - "purchases_by_vendor_summary": "" - } - }, - "production": { - "actions": { - "addcolumns": "", - "bodypriority-clear": "", - "bodypriority-set": "", - "detailpriority-clear": "", - "detailpriority-set": "", - "paintpriority-clear": "", - "paintpriority-set": "", - "remove": "", - "removecolumn": "", - "saveconfig": "", - "suspend": "", - "unsuspend": "" - }, - "constants": { - "main_profile": "" - }, - "errors": { - "boardupdate": "", - "name_exists": "", - "name_required": "", - "removing": "", - "settings": "" - }, - "labels": { - "actual_in": "", - "addnewprofile": "", - "alert": "", - "alertoff": "", - "alerton": "", - "alerts": "", - "ats": "", - "bodyhours": "", - "bodypriority": "", - "bodyshop": { - "labels": { - "qbo_departmentid": "", - "qbo_usa": "" - } - }, - "card_size": "", - "cardcolor": "", - "cardsettings": "", - "clm_no": "", - "comment": "", - "compact": "", - "detailpriority": "", - "employeeassignments": "", - "employeesearch": "", - "estimator": "", - "horizontal": "", - "ins_co_nm": "", - "jobdetail": "", - "kiosk_mode": "", - "laborhrs": "", - "legend": "", - "model_info": "", - "note": "", - "off": "", - "on": "", - "orientation": "", - "ownr_nm": "", - "paintpriority": "", - "partsstatus": "", - "production_note": "", - "refinishhours": "", - "scheduled_completion": "", - "selectview": "", - "stickyheader": "", - "sublets": "", - "subtotal": "", - "tall": "", - "tasks": "", - "totalhours": "", - "touchtime": "", - "vertical": "", - "viewname": "", - "wide": "" - }, - "options": { - "horizontal": "", - "large": "", - "medium": "", - "small": "", - "vertical": "" - }, - "settings": { - "board_settings": "", - "filters": { - "md_estimators": "", - "md_ins_cos": "" - }, - "filters_title": "", - "information": "", - "layout": "", - "statistics": { - "jobs_in_production": "", - "tasks_in_production": "", - "tasks_on_board": "", - "tasks_in_view": "", - "total_amount_in_production": "", - "total_amount_on_board": "", - "total_amount_in_view": "", - "total_hours_in_production": "", - "total_hours_on_board": "", - "total_hours_in_view": "", - "total_jobs_on_board": "", - "total_jobs_in_view": "", - "total_lab_in_production": "", - "total_lab_on_board": "", - "total_lab_in_view": "", - "total_lar_in_production": "", - "total_lar_on_board": "", - "total_lar_in_view": "" - }, - "statistics_title": "" - }, - "statistics": { - "currency_symbol": "", - "hours": "", - "jobs": "", - "jobs_in_production": "", - "tasks": "", - "tasks_in_production": "", - "tasks_on_board": "", - "tasks_in_view": "", - "total_amount_in_production": "", - "total_amount_on_board": "", - "total_amount_in_view": "", - "total_hours_in_production": "", - "total_hours_on_board": "", - "total_hours_in_view": "", - "total_jobs_on_board": "", - "total_jobs_in_view": "", - "total_lab_in_production": "", - "total_lab_on_board": "", - "total_lab_in_view": "", - "total_lar_in_production": "", - "total_lar_on_board": "", - "total_lar_in_view": "" - }, - "successes": { - "removed": "" - } - }, - "profile": { - "errors": { - "state": "Erreur lors de la lecture de l'état de la page. Rafraichissez, s'il vous plait." - }, - "labels": { - "activeshop": "" - }, - "successes": { - "updated": "" - } - }, - "reportcenter": { - "actions": { - "generate": "" - }, - "labels": { - "advanced_filters": "", - "advanced_filters_false": "", - "advanced_filters_filter_field": "", - "advanced_filters_filter_operator": "", - "advanced_filters_filter_value": "", - "advanced_filters_filters": "", - "advanced_filters_hide": "", - "advanced_filters_show": "", - "advanced_filters_sorter_direction": "", - "advanced_filters_sorter_field": "", - "advanced_filters_sorters": "", - "advanced_filters_true": "", - "dates": "", - "employee": "", - "filterson": "", - "generateasemail": "", - "groups": { - "customers": "", - "jobs": "", - "payroll": "", - "purchases": "", - "sales": "" - }, - "key": "", - "objects": { - "appointments": "", - "bills": "", - "csi": "", - "exportlogs": "", - "jobs": "", - "parts_orders": "", - "payments": "", - "scoreboard": "", - "tasks": "", - "timetickets": "" - }, - "vendor": "" - }, - "templates": { - "adp_payroll_flat": "", - "adp_payroll_straight": "", - "anticipated_revenue": "", - "ar_aging": "", - "attendance_detail": "", - "attendance_employee": "", - "attendance_summary": "", - "committed_timetickets": "", - "committed_timetickets_employee": "", - "committed_timetickets_summary": "", - "credits_not_received_date": "", - "credits_not_received_date_vendorid": "", - "csi": "", - "customer_list": "", - "cycle_time_analysis": "", - "estimates_written_converted": "", - "estimator_detail": "", - "estimator_summary": "", - "export_payables": "", - "export_payments": "", - "export_receivables": "", - "exported_gsr_by_ro": "", - "exported_gsr_by_ro_labor": "", - "gsr_by_atp": "", - "gsr_by_ats": "", - "gsr_by_category": "", - "gsr_by_csr": "", - "gsr_by_delivery_date": "", - "gsr_by_estimator": "", - "gsr_by_exported_date": "", - "gsr_by_ins_co": "", - "gsr_by_make": "", - "gsr_by_referral": "", - "gsr_by_ro": "", - "gsr_labor_only": "", - "hours_sold_detail_closed": "", - "hours_sold_detail_closed_csr": "", - "hours_sold_detail_closed_estimator": "", - "hours_sold_detail_closed_ins_co": "", - "hours_sold_detail_closed_status": "", - "hours_sold_detail_open": "", - "hours_sold_detail_open_csr": "", - "hours_sold_detail_open_estimator": "", - "hours_sold_detail_open_ins_co": "", - "hours_sold_detail_open_status": "", - "hours_sold_summary_closed": "", - "hours_sold_summary_closed_csr": "", - "hours_sold_summary_closed_estimator": "", - "hours_sold_summary_closed_ins_co": "", - "hours_sold_summary_closed_status": "", - "hours_sold_summary_open": "", - "hours_sold_summary_open_csr": "", - "hours_sold_summary_open_estimator": "", - "hours_sold_summary_open_ins_co": "", - "hours_sold_summary_open_status": "", - "job_costing_ro_csr": "", - "job_costing_ro_date_detail": "", - "job_costing_ro_date_summary": "", - "job_costing_ro_estimator": "", - "job_costing_ro_ins_co": "", - "job_lifecycle_date_detail": "", - "job_lifecycle_date_summary": "", - "jobs_completed_not_invoiced": "", - "jobs_invoiced_not_exported": "", - "jobs_reconcile": "", - "jobs_scheduled_completion": "", - "lag_time": "", - "load_level": "", - "lost_sales": "", - "open_orders": "", - "open_orders_csr": "", - "open_orders_estimator": "", - "open_orders_excel": "", - "open_orders_ins_co": "", - "open_orders_referral": "", - "open_orders_specific_csr": "", - "open_orders_status": "", - "parts_backorder": "", - "parts_not_recieved": "", - "parts_not_recieved_vendor": "", - "parts_received_not_scheduled": "", - "payments_by_date": "", - "payments_by_date_payment": "", - "payments_by_date_type": "", - "production_by_category": "", - "production_by_category_one": "", - "production_by_csr": "", - "production_by_last_name": "", - "production_by_repair_status": "", - "production_by_repair_status_one": "", - "production_by_ro": "", - "production_by_target_date": "", - "production_by_technician": "", - "production_by_technician_one": "", - "production_not_production_status": "", - "production_over_time": "", - "psr_by_make": "", - "purchase_return_ratio_grouped_by_vendor_detail": "", - "purchase_return_ratio_grouped_by_vendor_summary": "", - "purchases_by_cost_center_detail": "", - "purchases_by_cost_center_summary": "", - "purchases_by_date_range_detail": "", - "purchases_by_date_range_summary": "", - "purchases_by_ro_detail_date": "", - "purchases_by_ro_summary_date": "", - "purchases_by_vendor_detailed_date_range": "", - "purchases_by_vendor_summary_date_range": "", - "purchases_grouped_by_vendor_detailed": "", - "purchases_grouped_by_vendor_summary": "", - "returns_grouped_by_vendor_detailed": "", - "returns_grouped_by_vendor_summary": "", - "schedule": "", - "scheduled_parts_list": "", - "scoreboard_detail": "", - "scoreboard_summary": "", - "supplement_ratio_ins_co": "", - "tasks_date": "", - "tasks_date_employee": "", - "thank_you_date": "", - "timetickets": "", - "timetickets_employee": "", - "timetickets_summary": "", - "unclaimed_hrs": "", - "void_ros": "", - "work_in_progress_committed_labour": "", - "work_in_progress_jobs": "", - "work_in_progress_labour": "", - "work_in_progress_payables": "" - } - }, - "schedule": { - "labels": { - "atssummary": "", - "employeevacation": "", - "estimators": "", - "ins_co_nm_filter": "", - "intake": "", - "manual": "", - "manualevent": "" - } - }, - "scoreboard": { - "actions": { - "edit": "" - }, - "errors": { - "adding": "", - "removing": "", - "updating": "" - }, - "fields": { - "bodyhrs": "", - "date": "", - "painthrs": "" - }, - "labels": { - "allemployeetimetickets": "", - "asoftodaytarget": "", - "body": "", - "bodyabbrev": "", - "bodycharttitle": "", - "calendarperiod": "", - "combinedcharttitle": "", - "dailyactual": "", - "dailytarget": "", - "efficiencyoverperiod": "", - "entries": "", - "jobs": "", - "jobscompletednotinvoiced": "", - "lastmonth": "", - "lastweek": "", - "monthlytarget": "", - "priorweek": "", - "productivestatistics": "", - "productivetimeticketsoverdate": "", - "refinish": "", - "refinishabbrev": "", - "refinishcharttitle": "", - "targets": "", - "thismonth": "", - "thisweek": "", - "timetickets": "", - "timeticketsemployee": "", - "todateactual": "", - "total": "", - "totalhrs": "", - "totaloverperiod": "", - "weeklyactual": "", - "weeklytarget": "", - "workingdays": "" - }, - "successes": { - "added": "", - "removed": "", - "updated": "" - } - }, - "tasks": { - "actions": { - "edit": "", - "new": "" - }, - "buttons": { - "allTasks": "", - "complete": "", - "create": "", - "delete": "", - "edit": "", - "myTasks": "", - "refresh": "" - }, - "date_presets": { - "completion": "", - "day": "", - "days": "", - "delivery": "", - "next_week": "", - "one_month": "", - "three_months": "", - "three_weeks": "", - "today": "", - "tomorrow": "", - "two_weeks": "" - }, - "failures": { - "completed": "", - "created": "", - "deleted": "", - "updated": "" - }, - "fields": { - "actions": "", - "assigned_to": "", - "bill": "", - "billid": "", - "completed": "", - "created_at": "", - "created_by": "", - "description": "", - "due_date": "", - "job": { - "ro_number": "" - }, - "jobid": "", - "jobline": "", - "joblineid": "", - "parts_order": "", - "partsorderid": "", - "priorities": { - "high": "", - "low": "", - "medium": "" - }, - "priority": "", - "related_items": "", - "remind_at": "", - "title": "" - }, - "placeholders": { - "assigned_to": "", - "billid": "", - "description": "", - "jobid": "", - "joblineid": "", - "partsorderid": "" - }, - "successes": { - "completed": "", - "created": "", - "deleted": "", - "updated": "" - }, - "titles": { - "all_tasks": "", - "completed": "", - "deleted": "", - "job_tasks": "", - "mine": "", - "my_tasks": "" - }, - "validation": { - "due_at_error_message": "", - "remind_at_error_message": "" - } - }, - "tech": { - "fields": { - "employeeid": "", - "pin": "" - }, - "labels": { - "loggedin": "", - "notloggedin": "" - } - }, - "templates": { - "errors": { - "updating": "" - }, - "successes": { - "updated": "" - } - }, - "timetickets": { - "actions": { - "claimtasks": "", - "clockin": "", - "clockout": "", - "commit": "", - "commitone": "", - "enter": "", - "payall": "", - "printemployee": "", - "uncommit": "" - }, - "errors": { - "clockingin": "", - "clockingout": "", - "creating": "", - "deleting": "", - "noemployeeforuser": "", - "noemployeeforuser_sub": "", - "payall": "", - "shiftalreadyclockedon": "" - }, - "fields": { - "actualhrs": "", - "ciecacode": "", - "clockhours": "", - "clockoff": "", - "clockon": "", - "committed": "", - "committed_at": "", - "cost_center": "", - "created_by": "", - "date": "", - "efficiency": "", - "employee": "", - "employee_team": "", - "flat_rate": "", - "memo": "", - "productivehrs": "", - "ro_number": "", - "task_name": "" - }, - "labels": { - "alreadyclockedon": "", - "ambreak": "", - "amshift": "", - "claimtaskpreview": "", - "clockhours": "", - "clockintojob": "", - "deleteconfirm": "", - "edit": "", - "efficiency": "", - "flat_rate": "", - "jobhours": "", - "lunch": "", - "new": "", - "payrollclaimedtasks": "", - "pmbreak": "", - "pmshift": "", - "shift": "", - "shiftalreadyclockedon": "", - "straight_time": "", - "task": "", - "timetickets": "", - "unassigned": "", - "zeroactualnegativeprod": "" - }, - "successes": { - "clockedin": "", - "clockedout": "", - "committed": "", - "created": "", - "deleted": "", - "payall": "" - }, - "validation": { - "clockoffmustbeafterclockon": "", - "clockoffwithoutclockon": "", - "hoursenteredmorethanavailable": "", - "unassignedlines": "" - } - }, - "titles": { - "accounting-payables": "", - "accounting-payments": "", - "accounting-receivables": "", - "all_tasks": "", - "app": "", - "bc": { - "accounting-payables": "", - "accounting-payments": "", - "accounting-receivables": "", - "all_tasks": "", - "availablejobs": "", - "bills-list": "", - "contracts": "", - "contracts-create": "", - "contracts-detail": "", - "courtesycars": "", - "courtesycars-detail": "", - "courtesycars-new": "", - "dashboard": "", - "dms": "", - "export-logs": "", - "inventory": "", - "jobs": "", - "jobs-active": "", - "jobs-admin": "", - "jobs-all": "", - "jobs-checklist": "", - "jobs-close": "", - "jobs-deliver": "", - "jobs-detail": "", - "jobs-intake": "", - "jobs-new": "", - "jobs-ready": "", - "my_tasks": "", - "owner-detail": "", - "owners": "", - "parts-queue": "", - "payments-all": "", - "phonebook": "", - "productionboard": "", - "productionlist": "", - "profile": "", - "schedule": "", - "scoreboard": "", - "shop": "", - "shop-csi": "", - "shop-templates": "", - "shop-vendors": "", - "tasks": "", - "temporarydocs": "", - "timetickets": "", - "ttapprovals": "", - "vehicle-details": "", - "vehicles": "" - }, - "bills-list": "", - "contracts": "", - "contracts-create": "", - "contracts-detail": "", - "courtesycars": "", - "courtesycars-create": "", - "courtesycars-detail": "", - "dashboard": "", - "dms": "", - "export-logs": "", - "imexonline": "", - "inventory": "", - "jobs": "Tous les emplois | {{app}}", - "jobs-admin": "", - "jobs-all": "", - "jobs-checklist": "", - "jobs-close": "", - "jobs-create": "", - "jobs-deliver": "", - "jobs-intake": "", - "jobsavailable": "Emplois disponibles | {{app}}", - "jobsdetail": "Travail {{ro_number}} | {{app}}", - "jobsdocuments": "Documents de travail {{ro_number}} | {{app}}", - "manageroot": "Accueil | {{app}}", - "my_tasks": "", - "owners": "Tous les propriétaires | {{app}}", - "owners-detail": "", - "parts-queue": "", - "payments-all": "", - "phonebook": "", - "productionboard": "", - "productionlist": "", - "profile": "Mon profil | {{app}}", - "promanager": "", - "readyjobs": "", - "resetpassword": "", - "resetpasswordvalidate": "", - "romeonline": "", - "schedule": "Horaire | {{app}}", - "scoreboard": "", - "shop": "Mon magasin | {{app}}", - "shop-csi": "", - "shop-templates": "", - "shop_vendors": "Vendeurs | {{app}}", - "tasks": "", - "techconsole": "{{app}}", - "techjobclock": "{{app}}", - "techjoblookup": "{{app}}", - "techshiftclock": "{{app}}", - "temporarydocs": "", - "timetickets": "", - "ttapprovals": "", - "vehicledetail": "Détails du véhicule {{vehicle} | {{app}}", - "vehicles": "Tous les véhicules | {{app}}" - }, - "trello": { - "labels": { - "add_card": "", - "add_lane": "", - "cancel": "", - "delete_lane": "", - "description": "", - "label": "", - "lane_actions": "", - "title": "" - } - }, - "tt_approvals": { - "actions": { - "approveselected": "" - }, - "labels": { - "approval_queue_in_use": "", - "calculate": "" - } - }, - "user": { - "actions": { - "changepassword": "", - "signout": "Déconnexion", - "updateprofile": "Mettre à jour le profil" - }, - "errors": { - "updating": "" - }, - "fields": { - "authlevel": "", - "displayname": "Afficher un nom", - "email": "", - "photourl": "URL de l'avatar" - }, - "labels": { - "actions": "", - "changepassword": "", - "profileinfo": "" - }, - "successess": { - "passwordchanged": "" - } - }, - "users": { - "errors": { - "signinerror": { - "auth/user-disabled": "", - "auth/user-not-found": "", - "auth/wrong-password": "" - } - } - }, - "vehicles": { - "errors": { - "deleting": "", - "noaccess": "Le véhicule n'existe pas ou vous n'y avez pas accès.", - "selectexistingornew": "", - "validation": "Veuillez vous assurer que tous les champs sont correctement entrés.", - "validationtitle": "Erreur de validation" - }, - "fields": { - "description": "Description du véhicule", - "notes": "", - "plate_no": "Plaque d'immatriculation", - "plate_st": "Juridiction de la plaque", - "trim_color": "Couleur de garniture", - "v_bstyle": "Style corporel", - "v_color": "Couleur", - "v_cond": "Etat", - "v_engine": "moteur", - "v_make_desc": "Faire", - "v_makecode": "Faire du code", - "v_mldgcode": "Code de moulage", - "v_model_desc": "Modèle", - "v_model_yr": "année", - "v_options": "Les options", - "v_paint_codes": "Codes de peinture", - "v_prod_dt": "Date de production", - "v_stage": "Étape", - "v_tone": "ton", - "v_trimcode": "Code de coupe", - "v_type": "Type", - "v_vin": "V.I.N." - }, - "forms": { - "detail": "", - "misc": "", - "registration": "" - }, - "labels": { - "deleteconfirm": "", - "fromvehicle": "", - "novehinfo": "", - "relatedjobs": "", - "updatevehicle": "" - }, - "successes": { - "delete": "", - "save": "Le véhicule a été enregistré avec succès." - } - }, - "vendors": { - "actions": { - "addtophonebook": "", - "new": "Nouveau vendeur", - "newpreferredmake": "" - }, - "errors": { - "deleting": "Erreur rencontrée lors de la suppression du fournisseur.", - "saving": "Erreur rencontrée lors de l'enregistrement du fournisseur." - }, - "fields": { - "active": "", - "am": "", - "city": "Ville", - "cost_center": "Centre de coûts", - "country": "Pays", - "discount": "Remise %", - "display_name": "Afficher un nom", - "dmsid": "", - "due_date": "Date limite de paiement", - "email": "Email du contact", - "favorite": "Préféré?", - "lkq": "", - "make": "", - "name": "Nom du vendeur", - "oem": "", - "phone": "", - "prompt_discount": "Remise rapide%", - "state": "Etat / Province", - "street1": "rue", - "street2": "Adresse 2 ", - "taxid": "Identifiant de taxe", - "terms": "Modalités de paiement", - "zip": "Zip / code postal" - }, - "labels": { - "noneselected": "Aucun fournisseur n'est sélectionné.", - "preferredmakes": "", - "search": "Tapez le nom d'un vendeur" - }, - "successes": { - "deleted": "Le fournisseur a bien été supprimé.", - "saved": "Le fournisseur a bien enregistré." - }, - "validation": { - "unique_vendor_name": "" - } - } - } + "translation": { + "allocations": { + "actions": { + "assign": "Attribuer" + }, + "errors": { + "deleting": "", + "saving": "", + "validation": "" + }, + "fields": { + "employee": "Alloué à" + }, + "successes": { + "deleted": "", + "save": "" + } + }, + "appointments": { + "actions": { + "block": "", + "calculate": "", + "cancel": "annuler", + "intake": "Admission", + "new": "Nouveau rendez-vous", + "preview": "", + "reschedule": "Replanifier", + "sendreminder": "", + "unblock": "", + "viewjob": "Voir le travail" + }, + "errors": { + "blocking": "", + "canceling": "Erreur lors de l'annulation du rendez-vous. {{message}}", + "saving": "Erreur lors de la planification du rendez-vous. {{message}}" + }, + "fields": { + "alt_transport": "", + "color": "", + "end": "", + "note": "", + "start": "", + "time": "", + "title": "Titre" + }, + "labels": { + "arrivedon": "Arrivé le:", + "arrivingjobs": "", + "blocked": "", + "cancelledappointment": "Rendez-vous annulé pour:", + "completingjobs": "", + "dataconsistency": "", + "expectedjobs": "", + "expectedprodhrs": "", + "history": "", + "inproduction": "", + "manualevent": "", + "noarrivingjobs": "", + "nocompletingjobs": "", + "nodateselected": "Aucune date n'a été sélectionnée.", + "priorappointments": "Rendez-vous précédents", + "reminder": "", + "scheduledfor": "Rendez-vous prévu pour:", + "severalerrorsfound": "", + "smartscheduling": "", + "smspaymentreminder": "", + "suggesteddates": "" + }, + "successes": { + "canceled": "Rendez-vous annulé avec succès.", + "created": "Rendez-vous planifié avec succès.", + "saved": "" + } + }, + "associations": { + "actions": { + "activate": "Activer" + }, + "fields": { + "active": "Actif?", + "shopname": "nom de la boutique" + }, + "labels": { + "actions": "actes" + } + }, + "audit": { + "fields": { + "cc": "", + "contents": "", + "created": "", + "operation": "", + "status": "", + "subject": "", + "to": "", + "useremail": "", + "values": "" + } + }, + "audit_trail": { + "messages": { + "admin_job_remove_from_ar": "", + "admin_jobmarkexported": "", + "admin_jobmarkforreexport": "", + "admin_jobuninvoice": "", + "admin_jobunvoid": "", + "alerttoggle": "", + "appointmentcancel": "", + "appointmentinsert": "", + "assignedlinehours": "", + "billdeleted": "", + "billposted": "", + "billupdated": "", + "failedpayment": "", + "jobassignmentchange": "", + "jobassignmentremoved": "", + "jobchecklist": "", + "jobclosedwithbypass": "", + "jobconverted": "", + "jobdelivery": "", + "jobexported": "", + "jobfieldchanged": "", + "jobimported": "", + "jobinproductionchange": "", + "jobintake": "", + "jobinvoiced": "", + "jobioucreated": "", + "jobmodifylbradj": "", + "jobnoteadded": "", + "jobnotedeleted": "", + "jobnoteupdated": "", + "jobspartsorder": "", + "jobspartsreturn": "", + "jobstatuschange": "", + "jobsupplement": "", + "jobsuspend": "", + "jobvoid": "", + "tasks_completed": "", + "tasks_created": "", + "tasks_deleted": "", + "tasks_uncompleted": "", + "tasks_undeleted": "", + "tasks_updated": "" + } + }, + "billlines": { + "actions": { + "newline": "" + }, + "fields": { + "actual_cost": "", + "actual_price": "", + "cost_center": "", + "federal_tax_applicable": "", + "jobline": "", + "line_desc": "", + "local_tax_applicable": "", + "location": "", + "quantity": "", + "state_tax_applicable": "" + }, + "labels": { + "deductedfromlbr": "", + "entered": "", + "from": "", + "mod_lbr_adjustment": "", + "other": "", + "reconciled": "", + "unreconciled": "" + }, + "validation": { + "atleastone": "" + } + }, + "bills": { + "actions": { + "deductallhours": "", + "edit": "", + "receive": "", + "return": "" + }, + "errors": { + "creating": "", + "deleting": "", + "existinginventoryline": "", + "exporting": "", + "exporting-partner": "", + "invalidro": "", + "invalidvendor": "", + "validation": "" + }, + "fields": { + "allpartslocation": "", + "date": "", + "exported": "", + "federal_tax_rate": "", + "invoice_number": "", + "is_credit_memo": "", + "is_credit_memo_short": "", + "local_tax_rate": "", + "ro_number": "", + "state_tax_rate": "", + "total": "", + "vendor": "", + "vendorname": "" + }, + "labels": { + "actions": "", + "bill_lines": "", + "bill_total": "", + "billcmtotal": "", + "bills": "", + "calculatedcreditsnotreceived": "", + "creditsnotreceived": "", + "creditsreceived": "", + "dedfromlbr": "", + "deleteconfirm": "", + "discrepancy": "", + "discrepwithcms": "", + "discrepwithlbradj": "", + "editadjwarning": "", + "entered_total": "", + "enteringcreditmemo": "", + "federal_tax": "", + "federal_tax_exempt": "", + "generatepartslabel": "", + "iouexists": "", + "local_tax": "", + "markexported": "", + "markforreexport": "", + "new": "", + "nobilllines": "", + "noneselected": "", + "onlycmforinvoiced": "", + "printlabels": "", + "retailtotal": "", + "returnfrombill": "", + "savewithdiscrepancy": "", + "state_tax": "", + "subtotal": "", + "totalreturns": "" + }, + "successes": { + "created": "", + "deleted": "", + "exported": "", + "markexported": "", + "reexport": "" + }, + "validation": { + "closingperiod": "", + "inventoryquantity": "", + "manualinhouse": "", + "unique_invoice_number": "" + } + }, + "bodyshop": { + "actions": { + "add_task_preset": "", + "addapptcolor": "", + "addbucket": "", + "addpartslocation": "", + "addpartsrule": "", + "addspeedprint": "", + "addtemplate": "", + "newlaborrate": "", + "newsalestaxcode": "", + "newstatus": "", + "testrender": "" + }, + "errors": { + "creatingdefaultview": "", + "loading": "Impossible de charger les détails de la boutique. Veuillez appeler le support technique.", + "saving": "" + }, + "fields": { + "ReceivableCustomField": "", + "address1": "", + "address2": "", + "appt_alt_transport": "", + "appt_colors": { + "color": "", + "label": "" + }, + "appt_length": "", + "attach_pdf_to_email": "", + "batchid": "", + "bill_allow_post_to_closed": "", + "bill_federal_tax_rate": "", + "bill_local_tax_rate": "", + "bill_state_tax_rate": "", + "city": "", + "closingperiod": "", + "companycode": "", + "country": "", + "dailybodytarget": "", + "dailypainttarget": "", + "default_adjustment_rate": "", + "deliver": { + "require_actual_delivery_date": "", + "templates": "" + }, + "dms": { + "apcontrol": "", + "appostingaccount": "", + "cashierid": "", + "default_journal": "", + "disablebillwip": "", + "disablecontactvehiclecreation": "", + "dms_acctnumber": "", + "dms_control_override": "", + "dms_wip_acctnumber": "", + "generic_customer_number": "", + "itc_federal": "", + "itc_local": "", + "itc_state": "", + "mappingname": "", + "sendmaterialscosting": "", + "srcco": "" + }, + "email": "", + "enforce_class": "", + "enforce_conversion_category": "", + "enforce_conversion_csr": "", + "enforce_referral": "", + "federal_tax_id": "", + "ignoreblockeddays": "", + "inhousevendorid": "", + "insurance_vendor_id": "", + "intake": { + "next_contact_hours": "", + "templates": "" + }, + "intellipay_config": { + "cash_discount_percentage": "", + "enable_cash_discount": "" + }, + "invoice_federal_tax_rate": "", + "invoice_local_tax_rate": "", + "invoice_state_tax_rate": "", + "jc_hourly_rates": { + "mapa": "", + "mash": "" + }, + "last_name_first": "", + "lastnumberworkingdays": "", + "localmediaserverhttp": "", + "localmediaservernetwork": "", + "localmediatoken": "", + "logo_img_footer_margin": "", + "logo_img_header_margin": "", + "logo_img_path": "", + "logo_img_path_height": "", + "logo_img_path_width": "", + "md_categories": "", + "md_ccc_rates": "", + "md_classes": "", + "md_ded_notes": "", + "md_email_cc": "", + "md_from_emails": "", + "md_functionality_toggles": { + "parts_queue_toggle": "" + }, + "md_hour_split": { + "paint": "", + "prep": "" + }, + "md_ins_co": { + "city": "", + "name": "", + "private": "", + "state": "", + "street1": "", + "street2": "", + "zip": "" + }, + "md_jobline_presets": "", + "md_lost_sale_reasons": "", + "md_parts_order_comment": "", + "md_parts_scan": { + "expression": "", + "flags": "" + }, + "md_payment_types": "", + "md_referral_sources": "", + "md_ro_guard": { + "enabled": "", + "enforce_ar": "", + "enforce_bills": "", + "enforce_cm": "", + "enforce_labor": "", + "enforce_ppd": "", + "enforce_profit": "", + "enforce_sublet": "", + "masterbypass": "", + "totalgppercent_minimum": "" + }, + "md_tasks_presets": { + "enable_tasks": "", + "hourstype": "", + "memo": "", + "name": "", + "nextstatus": "", + "percent": "", + "use_approvals": "" + }, + "messaginglabel": "", + "messagingtext": "", + "noteslabel": "", + "notestext": "", + "partslocation": "", + "phone": "", + "prodtargethrs": "", + "rbac": { + "accounting": { + "exportlog": "", + "payables": "", + "payments": "", + "receivables": "" + }, + "bills": { + "delete": "", + "enter": "", + "list": "", + "reexport": "", + "view": "" + }, + "contracts": { + "create": "", + "detail": "", + "list": "" + }, + "courtesycar": { + "create": "", + "detail": "", + "list": "" + }, + "csi": { + "export": "", + "page": "" + }, + "employee_teams": { + "page": "" + }, + "employees": { + "page": "" + }, + "inventory": { + "delete": "", + "list": "" + }, + "jobs": { + "admin": "", + "available-list": "", + "checklist-view": "", + "close": "", + "create": "", + "deliver": "", + "detail": "", + "intake": "", + "list-active": "", + "list-all": "", + "list-ready": "", + "partsqueue": "", + "void": "" + }, + "owners": { + "detail": "", + "list": "" + }, + "payments": { + "enter": "", + "list": "" + }, + "phonebook": { + "edit": "", + "view": "" + }, + "production": { + "board": "", + "list": "" + }, + "schedule": { + "view": "" + }, + "scoreboard": { + "view": "" + }, + "shiftclock": { + "view": "" + }, + "shop": { + "config": "", + "dashboard": "", + "rbac": "", + "reportcenter": "", + "templates": "", + "vendors": "" + }, + "temporarydocs": { + "view": "" + }, + "timetickets": { + "edit": "", + "editcommitted": "", + "enter": "", + "list": "", + "shiftedit": "" + }, + "ttapprovals": { + "approve": "", + "view": "" + }, + "users": { + "editaccess": "" + } + }, + "responsibilitycenter": "", + "responsibilitycenter_accountdesc": "", + "responsibilitycenter_accountitem": "", + "responsibilitycenter_accountname": "", + "responsibilitycenter_accountnumber": "", + "responsibilitycenter_rate": "", + "responsibilitycenter_tax_rate": "", + "responsibilitycenter_tax_sur": "", + "responsibilitycenter_tax_thres": "", + "responsibilitycenter_tax_tier": "", + "responsibilitycenter_tax_type": "", + "responsibilitycenters": { + "ap": "", + "ar": "", + "ats": "", + "federal_tax": "", + "federal_tax_itc": "", + "gst_override": "", + "invoiceexemptcode": "", + "itemexemptcode": "", + "la1": "", + "la2": "", + "la3": "", + "la4": "", + "laa": "", + "lab": "", + "lad": "", + "lae": "", + "laf": "", + "lag": "", + "lam": "", + "lar": "", + "las": "", + "lau": "", + "local_tax": "", + "mapa": "", + "mash": "", + "paa": "", + "pac": "", + "pag": "", + "pal": "", + "pam": "", + "pan": "", + "pao": "", + "pap": "", + "par": "", + "pas": "", + "pasl": "", + "refund": "", + "sales_tax_codes": { + "code": "", + "description": "", + "federal": "", + "local": "", + "state": "" + }, + "state_tax": "", + "tow": "" + }, + "schedule_end_time": "", + "schedule_start_time": "", + "shopname": "", + "speedprint": { + "id": "", + "label": "", + "templates": "" + }, + "ss_configuration": { + "dailyhrslimit": "" + }, + "ssbuckets": { + "color": "", + "gte": "", + "id": "", + "label": "", + "lt": "", + "target": "" + }, + "state": "", + "state_tax_id": "", + "status": "", + "statuses": { + "active_statuses": "", + "additional_board_statuses": "", + "color": "", + "default_arrived": "", + "default_bo": "", + "default_canceled": "", + "default_completed": "", + "default_delivered": "", + "default_exported": "", + "default_imported": "", + "default_invoiced": "", + "default_ordered": "", + "default_quote": "", + "default_received": "", + "default_returned": "", + "default_scheduled": "", + "default_void": "", + "open_statuses": "", + "post_production_statuses": "", + "pre_production_statuses": "", + "production_colors": "", + "production_statuses": "", + "ready_statuses": "" + }, + "target_touchtime": "", + "timezone": "", + "tt_allow_post_to_invoiced": "", + "tt_enforce_hours_for_tech_console": "", + "use_fippa": "", + "use_paint_scale_data": "", + "uselocalmediaserver": "", + "website": "", + "zip_post": "" + }, + "labels": { + "2tiername": "", + "2tiersetup": "", + "2tiersource": "", + "accountingsetup": "", + "accountingtiers": "", + "alljobstatuses": "", + "allopenjobstatuses": "", + "apptcolors": "", + "businessinformation": "", + "checklists": "", + "csiq": "", + "customtemplates": "", + "defaultcostsmapping": "", + "defaultprofitsmapping": "", + "deliverchecklist": "", + "dms": { + "cdk": { + "controllist": "", + "payers": "" + }, + "cdk_dealerid": "", + "costsmapping": "", + "dms_allocations": "", + "pbs_serialnumber": "", + "profitsmapping": "", + "title": "" + }, + "emaillater": "", + "employee_teams": "", + "employees": "", + "estimators": "", + "filehandlers": "", + "insurancecos": "", + "intakechecklist": "", + "intellipay": "", + "intellipay_cash_discount": "", + "jobstatuses": "", + "laborrates": "", + "licensing": "", + "md_parts_scan": "", + "md_ro_guard": "", + "md_tasks_presets": "", + "md_to_emails": "", + "md_to_emails_emails": "", + "messagingpresets": "", + "notemplatesavailable": "", + "notespresets": "", + "orderstatuses": "", + "partslocations": "", + "partsscan": "", + "printlater": "", + "qbo": "", + "qbo_departmentid": "", + "qbo_usa": "", + "rbac": "", + "responsibilitycenters": { + "costs": "", + "profits": "", + "sales_tax_codes": "", + "tax_accounts": "", + "title": "", + "ttl_adjustment": "", + "ttl_tax_adjustment": "" + }, + "roguard": { + "title": "" + }, + "scheduling": "", + "scoreboardsetup": "", + "shopinfo": "", + "speedprint": "", + "ssbuckets": "", + "systemsettings": "", + "task-presets": "", + "workingdays": "" + }, + "successes": { + "areyousure": "", + "defaultviewcreated": "", + "save": "", + "unsavedchanges": "" + }, + "validation": { + "centermustexist": "", + "larsplit": "", + "useremailmustexist": "" + } + }, + "checklist": { + "actions": { + "printall": "" + }, + "errors": { + "complete": "", + "nochecklist": "" + }, + "labels": { + "addtoproduction": "", + "allow_text_message": "", + "checklist": "", + "printpack": "", + "removefromproduction": "" + }, + "successes": { + "completed": "" + } + }, + "contracts": { + "actions": { + "changerate": "", + "convertoro": "", + "decodelicense": "", + "find": "", + "printcontract": "", + "senddltoform": "" + }, + "errors": { + "fetchingjobinfo": "", + "returning": "", + "saving": "", + "selectjobandcar": "" + }, + "fields": { + "actax": "", + "actualreturn": "", + "agreementnumber": "", + "cc_cardholder": "", + "cc_expiry": "", + "cc_num": "", + "cleanupcharge": "", + "coverage": "", + "dailyfreekm": "", + "dailyrate": "", + "damage": "", + "damagewaiver": "", + "driver": "", + "driver_addr1": "", + "driver_addr2": "", + "driver_city": "", + "driver_dlexpiry": "", + "driver_dlnumber": "", + "driver_dlst": "", + "driver_dob": "", + "driver_fn": "", + "driver_ln": "", + "driver_ph1": "", + "driver_state": "", + "driver_zip": "", + "excesskmrate": "", + "federaltax": "", + "fuelin": "", + "fuelout": "", + "kmend": "", + "kmstart": "", + "length": "", + "localtax": "", + "refuelcharge": "", + "scheduledreturn": "", + "start": "", + "statetax": "", + "status": "" + }, + "labels": { + "agreement": "", + "availablecars": "", + "cardueforservice": "", + "convertform": { + "applycleanupcharge": "", + "refuelqty": "" + }, + "correctdataonform": "", + "dateinpast": "", + "dlexpirebeforereturn": "", + "driverinformation": "", + "findcontract": "", + "findermodal": "", + "insuranceexpired": "", + "noteconvertedfrom": "", + "populatefromjob": "", + "rates": "", + "time": "", + "vehicle": "", + "waitingforscan": "" + }, + "status": { + "new": "", + "out": "", + "returned": "" + }, + "successes": { + "saved": "" + } + }, + "courtesycars": { + "actions": { + "new": "", + "return": "" + }, + "errors": { + "saving": "" + }, + "fields": { + "color": "", + "dailycost": "", + "damage": "", + "fleetnumber": "", + "fuel": "", + "insuranceexpires": "", + "leaseenddate": "", + "make": "", + "mileage": "", + "model": "", + "nextservicedate": "", + "nextservicekm": "", + "notes": "", + "plate": "", + "purchasedate": "", + "readiness": "", + "registrationexpires": "", + "serviceenddate": "", + "servicestartdate": "", + "status": "", + "vin": "", + "year": "" + }, + "labels": { + "courtesycar": "", + "fuel": { + "12": "", + "14": "", + "18": "", + "34": "", + "38": "", + "58": "", + "78": "", + "empty": "", + "full": "" + }, + "outwith": "", + "return": "", + "status": "", + "uniquefleet": "", + "usage": "", + "vehicle": "" + }, + "readiness": { + "notready": "", + "ready": "" + }, + "status": { + "in": "", + "inservice": "", + "leasereturn": "", + "out": "", + "sold": "", + "unavailable": "" + }, + "successes": { + "saved": "" + } + }, + "csi": { + "actions": { + "activate": "" + }, + "errors": { + "creating": "", + "notconfigured": "", + "notfoundsubtitle": "", + "notfoundtitle": "", + "surveycompletesubtitle": "", + "surveycompletetitle": "" + }, + "fields": { + "completedon": "", + "created_at": "", + "surveyid": "", + "validuntil": "" + }, + "labels": { + "copyright": "", + "greeting": "", + "intro": "", + "nologgedinuser": "", + "nologgedinuser_sub": "", + "noneselected": "", + "title": "" + }, + "successes": { + "created": "", + "submitted": "", + "submittedsub": "" + } + }, + "dashboard": { + "actions": { + "addcomponent": "" + }, + "errors": { + "refreshrequired": "", + "updatinglayout": "" + }, + "labels": { + "bodyhrs": "", + "dollarsinproduction": "", + "phone": "", + "prodhrs": "", + "refhrs": "" + }, + "titles": { + "joblifecycle": "", + "labhours": "", + "larhours": "", + "monthlyemployeeefficiency": "", + "monthlyjobcosting": "", + "monthlylaborsales": "", + "monthlypartssales": "", + "monthlyrevenuegraph": "", + "prodhrssummary": "", + "productiondollars": "", + "productionhours": "", + "projectedmonthlysales": "", + "scheduledindate": "", + "scheduledintoday": "", + "scheduledoutdate": "", + "scheduledouttoday": "", + "tasks": "" + } + }, + "dms": { + "errors": { + "alreadyexported": "" + }, + "labels": { + "refreshallocations": "" + } + }, + "documents": { + "actions": { + "delete": "", + "download": "", + "reassign": "", + "selectallimages": "", + "selectallotherdocuments": "" + }, + "errors": { + "deletes3": "Erreur lors de la suppression du document du stockage.", + "deleting": "", + "deleting_cloudinary": "", + "getpresignurl": "Erreur lors de l'obtention de l'URL présignée pour le document. {{message}}", + "insert": "Incapable de télécharger le fichier. {{message}}", + "nodocuments": "Il n'y a pas de documents.", + "updating": "" + }, + "labels": { + "confirmdelete": "", + "doctype": "", + "newjobid": "", + "openinexplorer": "", + "optimizedimage": "", + "reassign_limitexceeded": "", + "reassign_limitexceeded_title": "", + "storageexceeded": "", + "storageexceeded_title": "", + "upload": "Télécharger", + "upload_limitexceeded": "", + "upload_limitexceeded_title": "", + "uploading": "", + "usage": "" + }, + "successes": { + "delete": "Le document a bien été supprimé.", + "edituploaded": "", + "insert": "Document téléchargé avec succès.", + "updated": "" + } + }, + "emails": { + "errors": { + "notsent": "Courriel non envoyé. Erreur rencontrée lors de l'envoi de {{message}}" + }, + "fields": { + "cc": "", + "from": "", + "subject": "", + "to": "" + }, + "labels": { + "attachments": "", + "documents": "", + "emailpreview": "", + "generatingemail": "", + "pdfcopywillbeattached": "", + "preview": "" + }, + "successes": { + "sent": "E-mail envoyé avec succès." + } + }, + "employee_teams": { + "actions": { + "new": "", + "newmember": "" + }, + "fields": { + "active": "", + "employeeid": "", + "max_load": "", + "name": "", + "percentage": "" + } + }, + "employees": { + "actions": { + "addvacation": "", + "new": "Nouvel employé", + "newrate": "" + }, + "errors": { + "delete": "Erreur rencontrée lors de la suppression de l'employé. {{message}}", + "save": "Une erreur s'est produite lors de l'enregistrement de l'employé. {{message}}", + "validation": "Veuillez cocher tous les champs.", + "validationtitle": "Impossible d'enregistrer l'employé." + }, + "fields": { + "active": "Actif?", + "base_rate": "Taux de base", + "cost_center": "Centre de coûts", + "employee_number": "Numéro d'employé", + "external_id": "", + "first_name": "Prénom", + "flat_rate": "Taux fixe (désactivé est le temps normal)", + "hire_date": "Date d'embauche", + "last_name": "Nom de famille", + "pin": "", + "rate": "", + "termination_date": "Date de résiliation", + "user_email": "", + "vacation": { + "end": "", + "length": "", + "start": "" + } + }, + "labels": { + "actions": "", + "active": "", + "endmustbeafterstart": "", + "flat_rate": "", + "inactive": "", + "name": "", + "rate_type": "", + "status": "", + "straight_time": "" + }, + "successes": { + "delete": "L'employé a bien été supprimé.", + "save": "L'employé a enregistré avec succès.", + "vacationadded": "" + }, + "validation": { + "unique_employee_number": "" + } + }, + "eula": { + "buttons": { + "accept": "Accept EULA" + }, + "content": { + "never_scrolled": "You must scroll to the bottom of the Terms and Conditions before accepting." + }, + "errors": { + "acceptance": { + "description": "Something went wrong while accepting the EULA. Please try again.", + "message": "Eula Acceptance Error" + } + }, + "labels": { + "accepted_terms": "I accept the terms and conditions of this agreement.", + "address": "Address", + "business_name": "Legal Business Name", + "date_accepted": "Date Accepted", + "first_name": "First Name", + "last_name": "Last Name", + "phone_number": "Phone Number" + }, + "messages": { + "accepted_terms": "Please accept the terms and conditions of this agreement.", + "business_name": "Please enter your legal business name.", + "date_accepted": "Please enter Today's Date.", + "first_name": "Please enter your first name.", + "last_name": "Please enter your last name.", + "phone_number": "Please enter your phone number." + }, + "titles": { + "modal": "Terms and Conditions", + "upper_card": "Acknowledgement" + } + }, + "exportlogs": { + "fields": { + "createdat": "" + }, + "labels": { + "attempts": "", + "priorsuccesfulexport": "" + } + }, + "general": { + "actions": { + "add": "", + "autoupdate": "", + "calculate": "", + "cancel": "", + "clear": "", + "close": "", + "copied": "", + "copylink": "", + "create": "", + "defaults": "", + "delay": "", + "delete": "Effacer", + "deleteall": "", + "deselectall": "", + "download": "", + "edit": "modifier", + "login": "", + "next": "", + "previous": "", + "print": "", + "refresh": "", + "remove": "", + "remove_alert": "", + "reset": " Rétablir l'original.", + "resetpassword": "", + "save": "sauvegarder", + "saveandnew": "", + "saveas": "", + "selectall": "", + "send": "", + "sendbysms": "", + "senderrortosupport": "", + "submit": "", + "tryagain": "", + "view": "", + "viewreleasenotes": "" + }, + "errors": { + "fcm": "", + "notfound": "", + "sizelimit": "" + }, + "itemtypes": { + "contract": "", + "courtesycar": "", + "job": "", + "owner": "", + "vehicle": "" + }, + "labels": { + "actions": "actes", + "areyousure": "", + "barcode": "code à barre", + "cancel": "", + "clear": "", + "confirmpassword": "", + "created_at": "", + "date": "", + "datetime": "", + "email": "", + "errors": "", + "excel": "", + "exceptiontitle": "", + "friday": "", + "globalsearch": "", + "help": "", + "hours": "", + "in": "dans", + "instanceconflictext": "", + "instanceconflictitle": "", + "item": "", + "label": "", + "loading": "Chargement...", + "loadingapp": "Chargement de {{app}}", + "loadingshop": "Chargement des données de la boutique ...", + "loggingin": "Vous connecter ...", + "markedexported": "", + "media": "", + "message": "", + "monday": "", + "na": "N / A", + "newpassword": "", + "no": "", + "nointernet": "", + "nointernet_sub": "", + "none": "", + "out": "En dehors", + "password": "", + "passwordresetsuccess": "", + "passwordresetsuccess_sub": "", + "passwordresetvalidatesuccess": "", + "passwordresetvalidatesuccess_sub": "", + "passwordsdonotmatch": "", + "print": "", + "refresh": "", + "reports": "", + "required": "", + "saturday": "", + "search": "Chercher...", + "searchresults": "", + "selectdate": "", + "sendagain": "", + "sendby": "", + "signin": "", + "sms": "", + "status": "", + "sub_status": { + "expired": "" + }, + "successful": "", + "sunday": "", + "text": "", + "thursday": "", + "total": "", + "totals": "", + "tuesday": "", + "tvmode": "", + "unknown": "Inconnu", + "unsavedchanges": "", + "username": "", + "view": "", + "wednesday": "", + "yes": "" + }, + "languages": { + "english": "Anglais", + "french": "Francais", + "spanish": "Espanol" + }, + "messages": { + "exception": "", + "newversionmessage": "", + "newversiontitle": "", + "noacctfilepath": "", + "nofeatureaccess": "", + "noshop": "", + "notfoundsub": "", + "notfoundtitle": "", + "partnernotrunning": "", + "rbacunauth": "", + "unsavedchanges": "Vous avez des changements non enregistrés.", + "unsavedchangespopup": "" + }, + "validation": { + "invalidemail": "S'il vous plaît entrer un email valide.", + "invalidphone": "", + "required": "Ce champ est requis." + } + }, + "help": { + "actions": { + "connect": "" + }, + "labels": { + "codeplacholder": "", + "rescuedesc": "", + "rescuetitle": "" + } + }, + "intake": { + "labels": { + "printpack": "" + } + }, + "inventory": { + "actions": { + "addtoinventory": "", + "addtoro": "", + "consumefrominventory": "", + "edit": "", + "new": "" + }, + "errors": { + "inserting": "" + }, + "fields": { + "comment": "", + "manualinvoicenumber": "", + "manualvendor": "" + }, + "labels": { + "consumedbyjob": "", + "deleteconfirm": "", + "frombillinvoicenumber": "", + "fromvendor": "", + "inventory": "", + "showall": "", + "showavailable": "" + }, + "successes": { + "deleted": "", + "inserted": "", + "updated": "" + } + }, + "job_lifecycle": { + "columns": { + "duration": "", + "end": "", + "human_readable": "", + "percentage": "", + "relative_end": "", + "relative_start": "", + "start": "", + "status": "", + "status_count": "", + "value": "" + }, + "content": { + "calculated_based_on": "", + "current_status_accumulated_time": "", + "data_unavailable": "", + "jobs_in_since": "", + "legend_title": "", + "loading": "", + "not_available": "", + "previous_status_accumulated_time": "", + "title": "", + "title_durations": "", + "title_loading": "", + "title_transitions": "" + }, + "errors": { + "fetch": "Erreur lors de l'obtention des données du cycle de vie des tâches" + }, + "titles": { + "dashboard": "", + "top_durations": "" + } + }, + "job_payments": { + "buttons": { + "create_short_link": "", + "goback": "", + "proceedtopayment": "", + "refundpayment": "" + }, + "notifications": { + "error": { + "description": "", + "openingip": "", + "title": "" + } + }, + "titles": { + "amount": "", + "dateOfPayment": "", + "descriptions": "", + "hint": "", + "payer": "", + "payername": "", + "paymentid": "", + "paymentnum": "", + "paymenttype": "", + "refundamount": "", + "transactionid": "" + } + }, + "joblines": { + "actions": { + "assign_team": "", + "converttolabor": "", + "dispatchparts": "", + "new": "" + }, + "errors": { + "creating": "", + "updating": "" + }, + "fields": { + "act_price": "Prix actuel", + "act_price_before_ppc": "", + "adjustment": "", + "ah_detail_line": "", + "amount": "", + "assigned_team": "", + "assigned_team_name": "", + "create_ppc": "", + "db_price": "Prix de la base de données", + "lbr_types": { + "LA1": "", + "LA2": "", + "LA3": "", + "LA4": "", + "LAA": "", + "LAB": "", + "LAD": "", + "LAE": "", + "LAF": "", + "LAG": "", + "LAM": "", + "LAR": "", + "LAS": "", + "LAU": "" + }, + "line_desc": "Description de la ligne", + "line_ind": "S#", + "line_no": "", + "location": "", + "mod_lb_hrs": "Heures de travail", + "mod_lbr_ty": "Type de travail", + "notes": "", + "oem_partno": "Pièce OEM #", + "op_code_desc": "", + "part_qty": "", + "part_type": "Type de pièce", + "part_types": { + "CCC": "", + "CCD": "", + "CCDR": "", + "CCF": "", + "CCM": "", + "PAA": "", + "PAC": "", + "PAE": "", + "PAG": "", + "PAL": "", + "PAM": "", + "PAN": "", + "PAO": "", + "PAP": "", + "PAR": "", + "PAS": "", + "PASL": "" + }, + "profitcenter_labor": "", + "profitcenter_part": "", + "prt_dsmk_m": "", + "prt_dsmk_p": "", + "status": "Statut", + "tax_part": "", + "total": "", + "unq_seq": "Seq #" + }, + "labels": { + "adjustmenttobeadded": "", + "billref": "", + "convertedtolabor": "", + "edit": "Ligne d'édition", + "ioucreated": "", + "new": "Nouvelle ligne", + "nostatus": "", + "presets": "" + }, + "successes": { + "created": "", + "saved": "", + "updated": "" + }, + "validations": { + "ahdetailonlyonuserdefinedtypes": "", + "hrsrequirediflbrtyp": "", + "requiredifparttype": "", + "zeropriceexistingpart": "" + } + }, + "jobs": { + "actions": { + "addDocuments": "Ajouter des documents de travail", + "addNote": "Ajouter une note", + "addtopartsqueue": "", + "addtoproduction": "", + "addtoscoreboard": "", + "allocate": "", + "autoallocate": "", + "changefilehandler": "", + "changelaborrate": "", + "changestatus": "Changer le statut", + "changestimator": "", + "convert": "Convertir", + "createiou": "", + "deliver": "", + "dms": { + "addpayer": "", + "createnewcustomer": "", + "findmakemodelcode": "", + "getmakes": "", + "labels": { + "refreshallocations": "" + }, + "post": "", + "refetchmakesmodels": "", + "usegeneric": "", + "useselected": "" + }, + "dmsautoallocate": "", + "export": "", + "exportcustdata": "", + "exportselected": "", + "filterpartsonly": "", + "generatecsi": "", + "gotojob": "", + "intake": "", + "manualnew": "", + "mark": "", + "markasexported": "", + "markpstexempt": "", + "markpstexemptconfirm": "", + "postbills": "Poster des factures", + "printCenter": "Centre d'impression", + "recalculate": "", + "reconcile": "", + "removefromproduction": "", + "schedule": "Programme", + "sendcsi": "", + "sendpartspricechange": "", + "sendtodms": "", + "sync": "", + "taxprofileoverride": "", + "taxprofileoverride_confirm": "", + "uninvoice": "", + "unvoid": "", + "viewchecklist": "", + "viewdetail": "" + }, + "errors": { + "addingtoproduction": "", + "cannotintake": "", + "closing": "", + "creating": "", + "deleted": "Erreur lors de la suppression du travail.", + "exporting": "", + "exporting-partner": "", + "invoicing": "", + "noaccess": "Ce travail n'existe pas ou vous n'y avez pas accès.", + "nodamage": "", + "nodates": "Aucune date spécifiée pour ce travail.", + "nofinancial": "", + "nojobselected": "Aucun travail n'est sélectionné.", + "noowner": "Aucun propriétaire associé.", + "novehicle": "Aucun véhicule associé.", + "partspricechange": "", + "saving": "Erreur rencontrée lors de la sauvegarde de l'enregistrement.", + "scanimport": "", + "totalscalc": "", + "updating": "", + "validation": "Veuillez vous assurer que tous les champs sont correctement entrés.", + "validationtitle": "Erreur de validation", + "voiding": "" + }, + "fields": { + "active_tasks": "", + "actual_completion": "Achèvement réel", + "actual_delivery": "Livraison réelle", + "actual_in": "En réel", + "adjustment_bottom_line": "Ajustements", + "adjustmenthours": "", + "alt_transport": "", + "area_of_damage_impact": { + "10": "", + "11": "", + "12": "", + "13": "", + "14": "", + "15": "", + "16": "", + "25": "", + "26": "", + "27": "", + "28": "", + "34": "", + "01": "", + "02": "", + "03": "", + "04": "", + "05": "", + "06": "", + "07": "", + "08": "", + "09": "" + }, + "auto_add_ats": "", + "ca_bc_pvrt": "", + "ca_customer_gst": "", + "ca_gst_registrant": "", + "category": "", + "ccc": "", + "ccd": "", + "ccdr": "", + "ccf": "", + "ccm": "", + "cieca_id": "CIECA ID", + "cieca_pfl": { + "lbr_adjp": "", + "lbr_tax_in": "", + "lbr_taxp": "", + "lbr_tx_in1": "", + "lbr_tx_in2": "", + "lbr_tx_in3": "", + "lbr_tx_in4": "", + "lbr_tx_in5": "" + }, + "cieca_pfo": { + "stor_t_in1": "", + "stor_t_in2": "", + "stor_t_in3": "", + "stor_t_in4": "", + "stor_t_in5": "", + "tow_t_in1": "", + "tow_t_in2": "", + "tow_t_in3": "", + "tow_t_in4": "", + "tow_t_in5": "" + }, + "claim_total": "Total réclamation", + "class": "", + "clm_no": "Prétendre #", + "clm_total": "Total réclamation", + "comment": "", + "customerowing": "Client propriétaire", + "date_estimated": "Date estimée", + "date_exported": "Exportés", + "date_invoiced": "Facturé", + "date_last_contacted": "", + "date_lost_sale": "", + "date_next_contact": "", + "date_open": "Ouvrir", + "date_rentalresp": "", + "date_repairstarted": "", + "date_scheduled": "Prévu", + "date_towin": "", + "date_void": "", + "ded_amt": "Déductible", + "ded_note": "", + "ded_status": "Statut de franchise", + "depreciation_taxes": "Amortissement / taxes", + "dms": { + "address": "", + "amount": "", + "center": "", + "control_type": { + "account_number": "" + }, + "cost": "", + "cost_dms_acctnumber": "", + "dms_make": "", + "dms_model": "", + "dms_model_override": "", + "dms_unsold": "", + "dms_wip_acctnumber": "", + "id": "", + "inservicedate": "", + "journal": "", + "lines": "", + "name1": "", + "payer": { + "amount": "", + "control_type": "", + "controlnumber": "", + "dms_acctnumber": "", + "name": "" + }, + "sale": "", + "sale_dms_acctnumber": "", + "story": "", + "vinowner": "" + }, + "dms_allocation": "", + "driveable": "", + "employee_body": "", + "employee_csr": "représentant du service à la clientèle", + "employee_csr_writer": "", + "employee_prep": "", + "employee_refinish": "", + "est_addr1": "Adresse de l'évaluateur", + "est_co_nm": "Expert", + "est_ct_fn": "Prénom de l'évaluateur", + "est_ct_ln": "Nom de l'évaluateur", + "est_ea": "Courriel de l'évaluateur", + "est_ph1": "Numéro de téléphone de l'évaluateur", + "federal_tax_payable": "Impôt fédéral à payer", + "federal_tax_rate": "", + "ins_addr1": "Adresse Insurance Co.", + "ins_city": "Insurance City", + "ins_co_id": "ID de la compagnie d'assurance", + "ins_co_nm": "Nom de la compagnie d'assurance", + "ins_co_nm_short": "", + "ins_ct_fn": "Prénom du gestionnaire de fichiers", + "ins_ct_ln": "Nom du gestionnaire de fichiers", + "ins_ea": "Courriel du gestionnaire de fichiers", + "ins_ph1": "Numéro de téléphone du gestionnaire de fichiers", + "intake": { + "label": "", + "max": "", + "min": "", + "name": "", + "required": "", + "type": "" + }, + "invoice_final_note": "", + "kmin": "Kilométrage en", + "kmout": "Kilométrage hors", + "la1": "", + "la2": "", + "la3": "", + "la4": "", + "laa": "", + "lab": "", + "labor_rate_desc": "Nom du taux de main-d'œuvre", + "lad": "", + "lae": "", + "laf": "", + "lag": "", + "lam": "", + "lar": "", + "las": "", + "lau": "", + "local_tax_rate": "", + "loss_date": "Date de perte", + "loss_desc": "", + "loss_of_use": "", + "lost_sale_reason": "", + "ma2s": "", + "ma3s": "", + "mabl": "", + "macs": "", + "mahw": "", + "mapa": "", + "mash": "", + "matd": "", + "materials": { + "MAPA": "", + "MASH": "", + "cal_maxdlr": "", + "cal_opcode": "", + "mat_adjp": "", + "mat_taxp": "", + "mat_tx_in1": "", + "mat_tx_in2": "", + "mat_tx_in3": "", + "mat_tx_in4": "", + "mat_tx_in5": "", + "materials": "", + "tax_ind": "" + }, + "other_amount_payable": "Autre montant à payer", + "owner": "Propriétaire", + "owner_owing": "Cust. Owes", + "ownr_ea": "Email", + "ownr_ph1": "Téléphone 1", + "ownr_ph2": "", + "paa": "", + "pac": "", + "pae": "", + "pag": "", + "pal": "", + "pam": "", + "pan": "", + "pao": "", + "pap": "", + "par": "", + "parts_tax_rates": { + "prt_discp": "", + "prt_mktyp": "", + "prt_mkupp": "", + "prt_tax_in": "", + "prt_tax_rt": "", + "prt_tx_in1": "", + "prt_tx_in2": "", + "prt_tx_in3": "", + "prt_tx_in4": "", + "prt_tx_in5": "", + "prt_tx_ty1": "", + "prt_type": "" + }, + "partsstatus": "", + "pas": "", + "pay_date": "Date d'Pay", + "phoneshort": "PH", + "po_number": "", + "policy_no": "Politique #", + "ponumber": "Numéro de bon de commande", + "production_vars": { + "note": "" + }, + "qb_multiple_payers": { + "amount": "", + "name": "" + }, + "queued_for_parts": "", + "rate_ats": "", + "rate_la1": "Taux LA1", + "rate_la2": "Taux LA2", + "rate_la3": "Taux LA3", + "rate_la4": "Taux LA4", + "rate_laa": "Taux d'aluminium", + "rate_lab": "Taux de la main-d'œuvre", + "rate_lad": "Taux de diagnostic", + "rate_lae": "Tarif électrique", + "rate_laf": "Taux de trame", + "rate_lag": "Taux de verre", + "rate_lam": "Taux mécanique", + "rate_lar": "Taux de finition", + "rate_las": "", + "rate_lau": "Taux d'aluminium", + "rate_ma2s": "Taux de peinture en 2 étapes", + "rate_ma3s": "Taux de peinture en 3 étapes", + "rate_mabl": "MABL ??", + "rate_macs": "MACS ??", + "rate_mahw": "Taux de déchets dangereux", + "rate_mapa": "Taux de matériaux de peinture", + "rate_mash": "Tarif du matériel de la boutique", + "rate_matd": "Taux d'élimination des pneus", + "referral_source_extra": "", + "referral_source_other": "", + "referralsource": "Source de référence", + "regie_number": "Enregistrement #", + "repairtotal": "Réparation totale", + "ro_number": "RO #", + "scheduled_completion": "Achèvement planifié", + "scheduled_delivery": "Livraison programmée", + "scheduled_in": "Planifié dans", + "selling_dealer": "Revendeur vendeur", + "selling_dealer_contact": "Contacter le revendeur", + "servicecar": "Voiture de service", + "servicing_dealer": "Concessionnaire", + "servicing_dealer_contact": "Contacter le concessionnaire", + "special_coverage_policy": "Politique de couverture spéciale", + "specialcoveragepolicy": "Politique de couverture spéciale", + "state_tax_rate": "", + "status": "Statut de l'emploi", + "storage_payable": "Stockage", + "tax_lbr_rt": "", + "tax_levies_rt": "", + "tax_paint_mat_rt": "", + "tax_registration_number": "", + "tax_shop_mat_rt": "", + "tax_str_rt": "", + "tax_sub_rt": "", + "tax_tow_rt": "", + "towin": "", + "towing_payable": "Remorquage à payer", + "unitnumber": "Unité #", + "updated_at": "Mis à jour à", + "uploaded_by": "Telechargé par", + "vehicle": "Véhicule" + }, + "forms": { + "admindates": "", + "appraiserinfo": "", + "claiminfo": "", + "estdates": "", + "laborrates": "", + "lossinfo": "", + "other": "", + "repairdates": "", + "scheddates": "" + }, + "labels": { + "accountsreceivable": "", + "act_price_ppc": "", + "actual_completion_inferred": "", + "actual_delivery_inferred": "", + "actual_in_inferred": "", + "additionalpayeroverallocation": "", + "additionaltotal": "", + "adjustmentrate": "", + "adjustments": "", + "adminwarning": "", + "allocations": "", + "alreadyaddedtoscoreboard": "", + "alreadyclosed": "", + "appointmentconfirmation": "Envoyer une confirmation au client?", + "associationwarning": "", + "audit": "", + "available": "", + "availablejobs": "", + "ca_bc_pvrt": { + "days": "", + "rate": "" + }, + "ca_gst_all_if_null": "", + "calc_repair_days": "", + "calc_repair_days_tt": "", + "calc_scheuled_completion": "", + "cards": { + "customer": "Informations client", + "damage": "Zone de dommages", + "dates": "Rendez-vous", + "documents": "Documents récents", + "estimator": "Estimateur", + "filehandler": "Gestionnaire de fichiers", + "insurance": "Détails de l'assurance", + "more": "Plus", + "notes": "Remarques", + "parts": "les pièces", + "totals": "Totaux", + "vehicle": "Véhicule" + }, + "changeclass": "", + "checklistcompletedby": "", + "checklistdocuments": "", + "checklists": "", + "cieca_pfl": "", + "cieca_pfo": "", + "cieca_pft": "", + "closeconfirm": "", + "closejob": "", + "closingperiod": "", + "contracts": "", + "convertedtolabor": "", + "cost": "", + "cost_Additional": "", + "cost_labor": "", + "cost_parts": "", + "cost_sublet": "", + "costs": "", + "create": { + "jobinfo": "", + "newowner": "", + "newvehicle": "", + "novehicle": "", + "ownerinfo": "", + "vehicleinfo": "" + }, + "createiouwarning": "", + "creating_new_job": "Création d'un nouvel emploi ...", + "deductible": { + "stands": "", + "waived": "" + }, + "deleteconfirm": "", + "deletedelivery": "", + "deleteintake": "", + "deliverchecklist": "", + "difference": "", + "diskscan": "", + "dms": { + "apexported": "", + "damageto": "", + "defaultstory": "", + "disablebillwip": "", + "invoicedatefuture": "", + "kmoutnotgreaterthankmin": "", + "logs": "", + "notallocated": "", + "postingform": "", + "totalallocated": "" + }, + "documents": "Les documents", + "documents-images": "", + "documents-other": "", + "duplicateconfirm": "", + "emailaudit": "", + "employeeassignments": "", + "estimatelines": "", + "estimator": "", + "existing_jobs": "Emplois existants", + "federal_tax_amt": "", + "gpdollars": "", + "gppercent": "", + "hrs_claimed": "", + "hrs_total": "", + "importnote": "", + "inproduction": "", + "intakechecklist": "", + "iou": "", + "job": "", + "jobcosting": "", + "jobtotals": "", + "labor_hrs": "", + "labor_rates_subtotal": "", + "laborallocations": "", + "labortotals": "", + "lines": "Estimer les lignes", + "local_tax_amt": "", + "mapa": "", + "markforreexport": "", + "mash": "", + "masterbypass": "", + "materials": { + "mapa": "" + }, + "missingprofileinfo": "", + "multipayers": "", + "net_repairs": "", + "notes": "Remarques", + "othertotal": "", + "outstanding_ar": "", + "outstanding_credit_memos": "", + "outstanding_ppd": "", + "outstanding_reconciliation_discrep": "", + "outstanding_sublets": "", + "outstandinghours": "", + "override_header": "Remplacer l'en-tête d'estimation à l'importation?", + "ownerassociation": "", + "parts": "les pièces", + "parts_lines": "", + "parts_received": "", + "parts_tax_rates": "", + "partsfilter": "", + "partssubletstotal": "", + "partstotal": "", + "performance": "", + "pimraryamountpayable": "", + "plitooltips": { + "billtotal": "", + "calculatedcreditsnotreceived": "", + "creditmemos": "", + "creditsnotreceived": "", + "discrep1": "", + "discrep2": "", + "discrep3": "", + "laboradj": "", + "partstotal": "", + "totalreturns": "" + }, + "ppc": "", + "ppdnotexported": "", + "profileadjustments": "", + "profitbypassrequired": "", + "profits": "", + "prt_dsmk_total": "", + "rates": "Les taux", + "rates_subtotal": "", + "reconciliation": { + "billlinestotal": "", + "byassoc": "", + "byprice": "", + "clear": "", + "discrepancy": "", + "joblinestotal": "", + "multipleactprices": "", + "multiplebilllines": "", + "multiplebillsforactprice": "", + "removedpartsstrikethrough": "" + }, + "reconciliationheader": "", + "relatedros": "", + "remove_from_ar": "", + "returntotals": "", + "ro_guard": { + "enforce_ar": "", + "enforce_bills": "", + "enforce_cm": "", + "enforce_labor": "", + "enforce_ppd": "", + "enforce_profit": "", + "enforce_sublet": "", + "enforce_validation": "", + "enforced": "" + }, + "roguard": "", + "roguardwarnings": "", + "rosaletotal": "", + "sale_additional": "", + "sale_labor": "", + "sale_parts": "", + "sale_sublet": "", + "sales": "", + "savebeforeconversion": "", + "scheduledinchange": "", + "specialcoveragepolicy": "", + "state_tax_amt": "", + "subletsnotcompleted": "", + "subletstotal": "", + "subtotal": "", + "supplementnote": "", + "suspended": "", + "suspense": "", + "tasks": "", + "threshhold": "", + "total_cost": "", + "total_cust_payable": "", + "total_repairs": "", + "total_sales": "", + "total_sales_tax": "", + "totals": "", + "unvoidnote": "", + "update_scheduled_completion": "", + "vehicle_info": "Véhicule", + "vehicleassociation": "", + "viewallocations": "", + "voidjob": "", + "voidnote": "" + }, + "successes": { + "addedtoproduction": "", + "all_deleted": "{{count}} travaux supprimés avec succès.", + "closed": "", + "converted": "Travail converti avec succès.", + "created": "Le travail a été créé avec succès. Clique pour voir.", + "creatednoclick": "", + "delete": "", + "deleted": "Le travail a bien été supprimé.", + "duplicated": "", + "exported": "", + "invoiced": "", + "ioucreated": "", + "partsqueue": "", + "save": "Le travail a été enregistré avec succès.", + "savetitle": "Enregistrement enregistré avec succès.", + "supplemented": "Travail complété avec succès.", + "updated": "", + "voided": "" + } + }, + "landing": { + "bigfeature": { + "subtitle": "", + "title": "" + }, + "footer": { + "company": { + "about": "", + "contact": "", + "disclaimers": "", + "name": "", + "privacypolicy": "" + }, + "io": { + "help": "", + "name": "", + "status": "" + }, + "slogan": "" + }, + "hero": { + "button": "", + "title": "" + }, + "labels": { + "features": "", + "managemyshop": "", + "pricing": "" + }, + "pricing": { + "basic": { + "name": "", + "sub": "" + }, + "essentials": { + "name": "", + "sub": "" + }, + "pricingtitle": "", + "pro": { + "name": "", + "sub": "" + }, + "title": "", + "unlimited": { + "name": "", + "sub": "" + } + } + }, + "menus": { + "currentuser": { + "languageselector": "La langue", + "profile": "Profil" + }, + "header": { + "accounting": "", + "accounting-payables": "", + "accounting-payments": "", + "accounting-receivables": "", + "activejobs": "Emplois actifs", + "all_tasks": "", + "alljobs": "", + "allpayments": "", + "availablejobs": "Emplois disponibles", + "bills": "", + "courtesycars": "", + "courtesycars-all": "", + "courtesycars-contracts": "", + "courtesycars-newcontract": "", + "create_task": "", + "customers": "Les clients", + "dashboard": "", + "enterbills": "", + "entercardpayment": "", + "enterpayment": "", + "entertimeticket": "", + "export": "", + "export-logs": "", + "help": "", + "home": "Accueil", + "inventory": "", + "jobs": "Emplois", + "my_tasks": "", + "newjob": "", + "owners": "Propriétaires", + "parts-queue": "", + "phonebook": "", + "productionboard": "", + "productionlist": "", + "readyjobs": "", + "recent": "", + "reportcenter": "", + "rescueme": "", + "schedule": "Programme", + "scoreboard": "", + "search": { + "bills": "", + "jobs": "", + "owners": "", + "payments": "", + "phonebook": "", + "vehicles": "" + }, + "shiftclock": "", + "shop": "Mon magasin", + "shop_config": "Configuration", + "shop_csi": "", + "shop_templates": "", + "shop_vendors": "Vendeurs", + "tasks": "", + "temporarydocs": "", + "timetickets": "", + "ttapprovals": "", + "vehicles": "Véhicules" + }, + "jobsactions": { + "admin": "", + "cancelallappointments": "", + "closejob": "", + "deletejob": "", + "duplicate": "", + "duplicatenolines": "", + "newcccontract": "", + "void": "" + }, + "jobsdetail": { + "claimdetail": "Détails de la réclamation", + "dates": "Rendez-vous", + "financials": "", + "general": "", + "insurance": "", + "labor": "La main d'oeuvre", + "lifecycle": "", + "parts": "", + "partssublet": "Pièces / Sous-location", + "rates": "", + "repairdata": "Données de réparation", + "totals": "" + }, + "profilesidebar": { + "profile": "Mon profil", + "shops": "Mes boutiques" + }, + "tech": { + "assignedjobs": "", + "claimtask": "", + "dispatchedparts": "", + "home": "", + "jobclockin": "", + "jobclockout": "", + "joblookup": "", + "login": "", + "logout": "", + "productionboard": "", + "productionlist": "", + "shiftclockin": "" + } + }, + "messaging": { + "actions": { + "link": "", + "new": "" + }, + "errors": { + "invalidphone": "", + "noattachedjobs": "", + "updatinglabel": "" + }, + "labels": { + "addlabel": "", + "archive": "", + "maxtenimages": "", + "messaging": "Messagerie", + "noallowtxt": "", + "nojobs": "", + "nopush": "", + "phonenumber": "", + "presets": "", + "recentonly": "", + "selectmedia": "", + "sentby": "", + "typeamessage": "Envoyer un message...", + "unarchive": "" + }, + "render": { + "conversation_list": "" + } + }, + "notes": { + "actions": { + "actions": "actes", + "deletenote": "Supprimer la note", + "edit": "Note éditée", + "new": "Nouvelle note", + "savetojobnotes": "" + }, + "errors": { + "inserting": "" + }, + "fields": { + "createdby": "Créé par", + "critical": "Critique", + "private": "privé", + "text": "Contenu", + "type": "", + "types": { + "customer": "", + "general": "", + "office": "", + "paint": "", + "parts": "", + "shop": "", + "supplement": "" + }, + "updatedat": "Mis à jour à" + }, + "labels": { + "addtorelatedro": "", + "newnoteplaceholder": "Ajouter une note...", + "notetoadd": "", + "systemnotes": "", + "usernotes": "" + }, + "successes": { + "create": "Remarque créée avec succès.", + "deleted": "Remarque supprimée avec succès.", + "updated": "Remarque mise à jour avec succès." + } + }, + "owner": { + "labels": { + "noownerinfo": "" + } + }, + "owners": { + "actions": { + "update": "" + }, + "errors": { + "deleting": "", + "noaccess": "L'enregistrement n'existe pas ou vous n'y avez pas accès.", + "saving": "", + "selectexistingornew": "" + }, + "fields": { + "address": "Adresse", + "allow_text_message": "Autorisation de texte?", + "name": "Prénom", + "note": "", + "ownr_addr1": "Adresse", + "ownr_addr2": "Adresse 2 ", + "ownr_city": "Ville", + "ownr_co_nm": "", + "ownr_ctry": "Pays", + "ownr_ea": "Email", + "ownr_fn": "Prénom", + "ownr_ln": "Nom de famille", + "ownr_ph1": "Téléphone 1", + "ownr_ph2": "", + "ownr_st": "Etat / Province", + "ownr_title": "Titre", + "ownr_zip": "Zip / code postal", + "preferred_contact": "Méthode de contact préférée", + "tax_number": "" + }, + "forms": { + "address": "", + "contact": "", + "name": "" + }, + "labels": { + "create_new": "Créez un nouvel enregistrement de propriétaire.", + "deleteconfirm": "", + "existing_owners": "Propriétaires existants", + "fromclaim": "", + "fromowner": "", + "relatedjobs": "", + "updateowner": "" + }, + "successes": { + "delete": "", + "save": "Le propriétaire a bien enregistré." + } + }, + "parts": { + "actions": { + "order": "Commander des pièces", + "orderinhouse": "" + } + }, + "parts_dispatch": { + "actions": { + "accept": "" + }, + "errors": { + "accepting": "", + "creating": "" + }, + "fields": { + "number": "", + "percent_accepted": "" + }, + "labels": { + "notyetdispatched": "", + "parts_dispatch": "" + } + }, + "parts_dispatch_lines": { + "fields": { + "accepted_at": "" + } + }, + "parts_orders": { + "actions": { + "backordered": "", + "receive": "", + "receivebill": "" + }, + "errors": { + "associatedbills": "", + "backordering": "", + "creating": "Erreur rencontrée lors de la création de la commande de pièces.", + "oec": "", + "saving": "", + "updating": "" + }, + "fields": { + "act_price": "", + "backordered_eta": "", + "backordered_on": "", + "cm_received": "", + "comments": "", + "cost": "", + "db_price": "", + "deliver_by": "", + "job_line_id": "", + "line_desc": "", + "line_remarks": "", + "lineremarks": "Remarques sur la ligne", + "oem_partno": "", + "order_date": "", + "order_number": "", + "orderedby": "", + "part_type": "", + "quantity": "", + "return": "", + "status": "" + }, + "labels": { + "allpartsto": "", + "confirmdelete": "", + "custompercent": "", + "discount": "", + "email": "Envoyé par email", + "inthisorder": "Pièces dans cette commande", + "is_quote": "", + "mark_as_received": "", + "newpartsorder": "", + "notyetordered": "", + "oec": "", + "order_type": "", + "orderhistory": "Historique des commandes", + "parts_order": "", + "parts_orders": "", + "print": "Afficher le formulaire imprimé", + "receive": "", + "removefrompartsqueue": "", + "returnpartsorder": "", + "sublet_order": "" + }, + "successes": { + "created": "Commande de pièces créée avec succès.", + "line_updated": "", + "received": "", + "return_created": "" + } + }, + "payments": { + "actions": { + "generatepaymentlink": "" + }, + "errors": { + "exporting": "", + "exporting-partner": "", + "inserting": "" + }, + "fields": { + "amount": "", + "created_at": "", + "date": "", + "exportedat": "", + "memo": "", + "payer": "", + "paymentnum": "", + "stripeid": "", + "transactionid": "", + "type": "" + }, + "labels": { + "balance": "", + "ca_bc_etf_table": "", + "customer": "", + "edit": "", + "electronicpayment": "", + "external": "", + "findermodal": "", + "insurance": "", + "markexported": "", + "markforreexport": "", + "new": "", + "signup": "", + "smspaymentreminder": "", + "title": "", + "totalpayments": "" + }, + "successes": { + "exported": "", + "markexported": "", + "markreexported": "", + "payment": "", + "paymentupdate": "", + "stripe": "" + } + }, + "phonebook": { + "actions": { + "new": "" + }, + "errors": { + "adding": "", + "saving": "" + }, + "fields": { + "address1": "", + "address2": "", + "category": "", + "city": "", + "company": "", + "country": "", + "email": "", + "fax": "", + "firstname": "", + "lastname": "", + "phone1": "", + "phone2": "", + "state": "" + }, + "labels": { + "noneselected": "", + "onenamerequired": "", + "vendorcategory": "" + }, + "successes": { + "added": "", + "deleted": "", + "saved": "" + } + }, + "printcenter": { + "appointments": { + "appointment_confirmation": "" + }, + "bills": { + "inhouse_invoice": "" + }, + "courtesycarcontract": { + "courtesy_car_contract": "", + "courtesy_car_impound": "", + "courtesy_car_inventory": "", + "courtesy_car_terms": "" + }, + "errors": { + "nocontexttype": "" + }, + "jobs": { + "3rdpartyfields": { + "addr1": "", + "addr2": "", + "addr3": "", + "attn": "", + "city": "", + "custgst": "", + "ded_amt": "", + "depreciation": "", + "other": "", + "ponumber": "", + "refnumber": "", + "sendtype": "", + "state": "", + "zip": "" + }, + "3rdpartypayer": "", + "ab_proof_of_loss": "", + "appointment_confirmation": "", + "appointment_reminder": "", + "casl_authorization": "", + "committed_timetickets_ro": "", + "coversheet_landscape": "", + "coversheet_portrait": "", + "csi_invitation": "", + "csi_invitation_action": "", + "diagnostic_authorization": "", + "dms_posting_sheet": "", + "envelope_return_address": "", + "estimate": "", + "estimate_detail": "", + "estimate_followup": "", + "express_repair_checklist": "", + "filing_coversheet_landscape": "", + "filing_coversheet_portrait": "", + "final_invoice": "", + "fippa_authorization": "", + "folder_label_multiple": "", + "glass_express_checklist": "", + "guarantee": "", + "individual_job_note": "", + "invoice_customer_payable": "", + "invoice_total_payable": "", + "iou_form": "", + "job_costing_ro": "", + "job_lifecycle_ro": "", + "job_notes": "", + "job_tasks": "", + "key_tag": "", + "labels": { + "count": "", + "labels": "", + "position": "" + }, + "lag_time_ro": "", + "mechanical_authorization": "", + "mpi_animal_checklist": "", + "mpi_eglass_auth": "", + "mpi_final_acct_sheet": "", + "mpi_final_repair_acct_sheet": "", + "paint_grid": "", + "parts_dispatch": "", + "parts_invoice_label_single": "", + "parts_label_multiple": "", + "parts_label_single": "", + "parts_list": "", + "parts_order": "", + "parts_order_confirmation": "", + "parts_order_history": "", + "parts_return_slip": "", + "payment_receipt": "", + "payment_request": "", + "payments_by_job": "", + "purchases_by_ro_detail": "", + "purchases_by_ro_summary": "", + "qc_sheet": "", + "rental_reservation": "", + "ro_totals": "", + "ro_with_description": "", + "sgi_certificate_of_repairs": "", + "sgi_windshield_auth": "", + "stolen_recovery_checklist": "", + "sublet_order": "", + "supplement_request": "", + "thank_you_ro": "", + "thirdpartypayer": "", + "timetickets_ro": "", + "vehicle_check_in": "", + "vehicle_delivery_check": "", + "window_tag": "", + "window_tag_sublet": "", + "work_authorization": "", + "worksheet_by_line_number": "", + "worksheet_sorted_by_operation": "", + "worksheet_sorted_by_operation_no_hours": "", + "worksheet_sorted_by_operation_part_type": "", + "worksheet_sorted_by_operation_type": "", + "worksheet_sorted_by_team": "" + }, + "labels": { + "groups": { + "authorization": "", + "financial": "", + "post": "", + "pre": "", + "ro": "", + "worksheet": "" + }, + "misc": "", + "repairorder": "", + "reportcentermodal": "", + "speedprint": "", + "title": "" + }, + "payments": { + "ca_bc_etf_table": "", + "exported_payroll": "" + }, + "special": { + "attendance_detail_csv": "" + }, + "subjects": { + "jobs": { + "individual_job_note": "", + "parts_dispatch": "", + "parts_order": "", + "parts_return_slip": "", + "sublet_order": "" + } + }, + "vendors": { + "purchases_by_vendor_detailed": "", + "purchases_by_vendor_summary": "" + } + }, + "production": { + "actions": { + "addcolumns": "", + "bodypriority-clear": "", + "bodypriority-set": "", + "detailpriority-clear": "", + "detailpriority-set": "", + "paintpriority-clear": "", + "paintpriority-set": "", + "remove": "", + "removecolumn": "", + "saveconfig": "", + "suspend": "", + "unsuspend": "" + }, + "constants": { + "main_profile": "" + }, + "errors": { + "boardupdate": "", + "name_exists": "", + "name_required": "", + "removing": "", + "settings": "" + }, + "labels": { + "actual_in": "", + "addnewprofile": "", + "alert": "", + "alertoff": "", + "alerton": "", + "alerts": "", + "ats": "", + "bodyhours": "", + "bodypriority": "", + "bodyshop": { + "labels": { + "qbo_departmentid": "", + "qbo_usa": "" + } + }, + "card_size": "", + "cardcolor": "", + "cardsettings": "", + "clm_no": "", + "comment": "", + "compact": "", + "detailpriority": "", + "employeeassignments": "", + "employeesearch": "", + "estimator": "", + "horizontal": "", + "ins_co_nm": "", + "jobdetail": "", + "kiosk_mode": "", + "laborhrs": "", + "legend": "", + "model_info": "", + "note": "", + "off": "", + "on": "", + "orientation": "", + "ownr_nm": "", + "paintpriority": "", + "partsstatus": "", + "production_note": "", + "refinishhours": "", + "scheduled_completion": "", + "selectview": "", + "stickyheader": "", + "sublets": "", + "subtotal": "", + "tall": "", + "tasks": "", + "totalhours": "", + "touchtime": "", + "vertical": "", + "viewname": "", + "wide": "" + }, + "options": { + "horizontal": "", + "large": "", + "medium": "", + "small": "", + "vertical": "" + }, + "settings": { + "board_settings": "", + "filters": { + "md_estimators": "", + "md_ins_cos": "" + }, + "filters_title": "", + "information": "", + "layout": "", + "statistics": { + "jobs_in_production": "", + "tasks_in_production": "", + "tasks_in_view": "", + "tasks_on_board": "", + "total_amount_in_production": "", + "total_amount_in_view": "", + "total_amount_on_board": "", + "total_hours_in_production": "", + "total_hours_in_view": "", + "total_hours_on_board": "", + "total_jobs_in_view": "", + "total_jobs_on_board": "", + "total_lab_in_production": "", + "total_lab_in_view": "", + "total_lab_on_board": "", + "total_lar_in_production": "", + "total_lar_in_view": "", + "total_lar_on_board": "" + }, + "statistics_title": "" + }, + "statistics": { + "currency_symbol": "", + "hours": "", + "jobs": "", + "jobs_in_production": "", + "tasks": "", + "tasks_in_production": "", + "tasks_in_view": "", + "tasks_on_board": "", + "total_amount_in_production": "", + "total_amount_in_view": "", + "total_amount_on_board": "", + "total_hours_in_production": "", + "total_hours_in_view": "", + "total_hours_on_board": "", + "total_jobs_in_view": "", + "total_jobs_on_board": "", + "total_lab_in_production": "", + "total_lab_in_view": "", + "total_lab_on_board": "", + "total_lar_in_production": "", + "total_lar_in_view": "", + "total_lar_on_board": "" + }, + "successes": { + "removed": "" + } + }, + "profile": { + "errors": { + "state": "Erreur lors de la lecture de l'état de la page. Rafraichissez, s'il vous plait." + }, + "labels": { + "activeshop": "" + }, + "successes": { + "updated": "" + } + }, + "reportcenter": { + "actions": { + "generate": "" + }, + "labels": { + "advanced_filters": "", + "advanced_filters_false": "", + "advanced_filters_filter_field": "", + "advanced_filters_filter_operator": "", + "advanced_filters_filter_value": "", + "advanced_filters_filters": "", + "advanced_filters_hide": "", + "advanced_filters_show": "", + "advanced_filters_sorter_direction": "", + "advanced_filters_sorter_field": "", + "advanced_filters_sorters": "", + "advanced_filters_true": "", + "dates": "", + "employee": "", + "filterson": "", + "generateasemail": "", + "groups": { + "customers": "", + "jobs": "", + "payroll": "", + "purchases": "", + "sales": "" + }, + "key": "", + "objects": { + "appointments": "", + "bills": "", + "csi": "", + "exportlogs": "", + "jobs": "", + "parts_orders": "", + "payments": "", + "scoreboard": "", + "tasks": "", + "timetickets": "" + }, + "vendor": "" + }, + "templates": { + "adp_payroll_flat": "", + "adp_payroll_straight": "", + "anticipated_revenue": "", + "ar_aging": "", + "attendance_detail": "", + "attendance_employee": "", + "attendance_summary": "", + "committed_timetickets": "", + "committed_timetickets_employee": "", + "committed_timetickets_summary": "", + "credits_not_received_date": "", + "credits_not_received_date_vendorid": "", + "csi": "", + "customer_list": "", + "cycle_time_analysis": "", + "estimates_written_converted": "", + "estimator_detail": "", + "estimator_summary": "", + "export_payables": "", + "export_payments": "", + "export_receivables": "", + "exported_gsr_by_ro": "", + "exported_gsr_by_ro_labor": "", + "gsr_by_atp": "", + "gsr_by_ats": "", + "gsr_by_category": "", + "gsr_by_csr": "", + "gsr_by_delivery_date": "", + "gsr_by_estimator": "", + "gsr_by_exported_date": "", + "gsr_by_ins_co": "", + "gsr_by_make": "", + "gsr_by_referral": "", + "gsr_by_ro": "", + "gsr_labor_only": "", + "hours_sold_detail_closed": "", + "hours_sold_detail_closed_csr": "", + "hours_sold_detail_closed_estimator": "", + "hours_sold_detail_closed_ins_co": "", + "hours_sold_detail_closed_status": "", + "hours_sold_detail_open": "", + "hours_sold_detail_open_csr": "", + "hours_sold_detail_open_estimator": "", + "hours_sold_detail_open_ins_co": "", + "hours_sold_detail_open_status": "", + "hours_sold_summary_closed": "", + "hours_sold_summary_closed_csr": "", + "hours_sold_summary_closed_estimator": "", + "hours_sold_summary_closed_ins_co": "", + "hours_sold_summary_closed_status": "", + "hours_sold_summary_open": "", + "hours_sold_summary_open_csr": "", + "hours_sold_summary_open_estimator": "", + "hours_sold_summary_open_ins_co": "", + "hours_sold_summary_open_status": "", + "job_costing_ro_csr": "", + "job_costing_ro_date_detail": "", + "job_costing_ro_date_summary": "", + "job_costing_ro_estimator": "", + "job_costing_ro_ins_co": "", + "job_lifecycle_date_detail": "", + "job_lifecycle_date_summary": "", + "jobs_completed_not_invoiced": "", + "jobs_invoiced_not_exported": "", + "jobs_reconcile": "", + "jobs_scheduled_completion": "", + "lag_time": "", + "load_level": "", + "lost_sales": "", + "open_orders": "", + "open_orders_csr": "", + "open_orders_estimator": "", + "open_orders_excel": "", + "open_orders_ins_co": "", + "open_orders_referral": "", + "open_orders_specific_csr": "", + "open_orders_status": "", + "parts_backorder": "", + "parts_not_recieved": "", + "parts_not_recieved_vendor": "", + "parts_received_not_scheduled": "", + "payments_by_date": "", + "payments_by_date_payment": "", + "payments_by_date_type": "", + "production_by_category": "", + "production_by_category_one": "", + "production_by_csr": "", + "production_by_last_name": "", + "production_by_repair_status": "", + "production_by_repair_status_one": "", + "production_by_ro": "", + "production_by_target_date": "", + "production_by_technician": "", + "production_by_technician_one": "", + "production_not_production_status": "", + "production_over_time": "", + "psr_by_make": "", + "purchase_return_ratio_grouped_by_vendor_detail": "", + "purchase_return_ratio_grouped_by_vendor_summary": "", + "purchases_by_cost_center_detail": "", + "purchases_by_cost_center_summary": "", + "purchases_by_date_range_detail": "", + "purchases_by_date_range_summary": "", + "purchases_by_ro_detail_date": "", + "purchases_by_ro_summary_date": "", + "purchases_by_vendor_detailed_date_range": "", + "purchases_by_vendor_summary_date_range": "", + "purchases_grouped_by_vendor_detailed": "", + "purchases_grouped_by_vendor_summary": "", + "returns_grouped_by_vendor_detailed": "", + "returns_grouped_by_vendor_summary": "", + "schedule": "", + "scheduled_parts_list": "", + "scoreboard_detail": "", + "scoreboard_summary": "", + "supplement_ratio_ins_co": "", + "tasks_date": "", + "tasks_date_employee": "", + "thank_you_date": "", + "timetickets": "", + "timetickets_employee": "", + "timetickets_summary": "", + "unclaimed_hrs": "", + "void_ros": "", + "work_in_progress_committed_labour": "", + "work_in_progress_jobs": "", + "work_in_progress_labour": "", + "work_in_progress_payables": "" + } + }, + "schedule": { + "labels": { + "atssummary": "", + "employeevacation": "", + "estimators": "", + "ins_co_nm_filter": "", + "intake": "", + "manual": "", + "manualevent": "" + } + }, + "scoreboard": { + "actions": { + "edit": "" + }, + "errors": { + "adding": "", + "removing": "", + "updating": "" + }, + "fields": { + "bodyhrs": "", + "date": "", + "painthrs": "" + }, + "labels": { + "allemployeetimetickets": "", + "asoftodaytarget": "", + "body": "", + "bodyabbrev": "", + "bodycharttitle": "", + "calendarperiod": "", + "combinedcharttitle": "", + "dailyactual": "", + "dailytarget": "", + "efficiencyoverperiod": "", + "entries": "", + "jobs": "", + "jobscompletednotinvoiced": "", + "lastmonth": "", + "lastweek": "", + "monthlytarget": "", + "priorweek": "", + "productivestatistics": "", + "productivetimeticketsoverdate": "", + "refinish": "", + "refinishabbrev": "", + "refinishcharttitle": "", + "targets": "", + "thismonth": "", + "thisweek": "", + "timetickets": "", + "timeticketsemployee": "", + "todateactual": "", + "total": "", + "totalhrs": "", + "totaloverperiod": "", + "weeklyactual": "", + "weeklytarget": "", + "workingdays": "" + }, + "successes": { + "added": "", + "removed": "", + "updated": "" + } + }, + "tasks": { + "actions": { + "edit": "", + "new": "" + }, + "buttons": { + "allTasks": "", + "complete": "", + "create": "", + "delete": "", + "edit": "", + "myTasks": "", + "refresh": "" + }, + "date_presets": { + "completion": "", + "day": "", + "days": "", + "delivery": "", + "next_week": "", + "one_month": "", + "three_months": "", + "three_weeks": "", + "today": "", + "tomorrow": "", + "two_weeks": "" + }, + "failures": { + "completed": "", + "created": "", + "deleted": "", + "updated": "" + }, + "fields": { + "actions": "", + "assigned_to": "", + "bill": "", + "billid": "", + "completed": "", + "created_at": "", + "created_by": "", + "description": "", + "due_date": "", + "job": { + "ro_number": "" + }, + "jobid": "", + "jobline": "", + "joblineid": "", + "parts_order": "", + "partsorderid": "", + "priorities": { + "high": "", + "low": "", + "medium": "" + }, + "priority": "", + "related_items": "", + "remind_at": "", + "title": "" + }, + "placeholders": { + "assigned_to": "", + "billid": "", + "description": "", + "jobid": "", + "joblineid": "", + "partsorderid": "" + }, + "successes": { + "completed": "", + "created": "", + "deleted": "", + "updated": "" + }, + "titles": { + "all_tasks": "", + "completed": "", + "deleted": "", + "job_tasks": "", + "mine": "", + "my_tasks": "" + }, + "validation": { + "due_at_error_message": "", + "remind_at_error_message": "" + } + }, + "tech": { + "fields": { + "employeeid": "", + "pin": "" + }, + "labels": { + "loggedin": "", + "notloggedin": "" + } + }, + "templates": { + "errors": { + "updating": "" + }, + "successes": { + "updated": "" + } + }, + "timetickets": { + "actions": { + "claimtasks": "", + "clockin": "", + "clockout": "", + "commit": "", + "commitone": "", + "enter": "", + "payall": "", + "printemployee": "", + "uncommit": "" + }, + "errors": { + "clockingin": "", + "clockingout": "", + "creating": "", + "deleting": "", + "noemployeeforuser": "", + "noemployeeforuser_sub": "", + "payall": "", + "shiftalreadyclockedon": "" + }, + "fields": { + "actualhrs": "", + "ciecacode": "", + "clockhours": "", + "clockoff": "", + "clockon": "", + "committed": "", + "committed_at": "", + "cost_center": "", + "created_by": "", + "date": "", + "efficiency": "", + "employee": "", + "employee_team": "", + "flat_rate": "", + "memo": "", + "productivehrs": "", + "ro_number": "", + "task_name": "" + }, + "labels": { + "alreadyclockedon": "", + "ambreak": "", + "amshift": "", + "claimtaskpreview": "", + "clockhours": "", + "clockintojob": "", + "deleteconfirm": "", + "edit": "", + "efficiency": "", + "flat_rate": "", + "jobhours": "", + "lunch": "", + "new": "", + "payrollclaimedtasks": "", + "pmbreak": "", + "pmshift": "", + "shift": "", + "shiftalreadyclockedon": "", + "straight_time": "", + "task": "", + "timetickets": "", + "unassigned": "", + "zeroactualnegativeprod": "" + }, + "successes": { + "clockedin": "", + "clockedout": "", + "committed": "", + "created": "", + "deleted": "", + "payall": "" + }, + "validation": { + "clockoffmustbeafterclockon": "", + "clockoffwithoutclockon": "", + "hoursenteredmorethanavailable": "", + "unassignedlines": "" + } + }, + "titles": { + "accounting-payables": "", + "accounting-payments": "", + "accounting-receivables": "", + "all_tasks": "", + "app": "", + "bc": { + "accounting-payables": "", + "accounting-payments": "", + "accounting-receivables": "", + "all_tasks": "", + "availablejobs": "", + "bills-list": "", + "contracts": "", + "contracts-create": "", + "contracts-detail": "", + "courtesycars": "", + "courtesycars-detail": "", + "courtesycars-new": "", + "dashboard": "", + "dms": "", + "export-logs": "", + "inventory": "", + "jobs": "", + "jobs-active": "", + "jobs-admin": "", + "jobs-all": "", + "jobs-checklist": "", + "jobs-close": "", + "jobs-deliver": "", + "jobs-detail": "", + "jobs-intake": "", + "jobs-new": "", + "jobs-ready": "", + "my_tasks": "", + "owner-detail": "", + "owners": "", + "parts-queue": "", + "payments-all": "", + "phonebook": "", + "productionboard": "", + "productionlist": "", + "profile": "", + "schedule": "", + "scoreboard": "", + "shop": "", + "shop-csi": "", + "shop-templates": "", + "shop-vendors": "", + "tasks": "", + "temporarydocs": "", + "timetickets": "", + "ttapprovals": "", + "vehicle-details": "", + "vehicles": "" + }, + "bills-list": "", + "contracts": "", + "contracts-create": "", + "contracts-detail": "", + "courtesycars": "", + "courtesycars-create": "", + "courtesycars-detail": "", + "dashboard": "", + "dms": "", + "export-logs": "", + "imexonline": "", + "inventory": "", + "jobs": "Tous les emplois | {{app}}", + "jobs-admin": "", + "jobs-all": "", + "jobs-checklist": "", + "jobs-close": "", + "jobs-create": "", + "jobs-deliver": "", + "jobs-intake": "", + "jobsavailable": "Emplois disponibles | {{app}}", + "jobsdetail": "Travail {{ro_number}} | {{app}}", + "jobsdocuments": "Documents de travail {{ro_number}} | {{app}}", + "manageroot": "Accueil | {{app}}", + "my_tasks": "", + "owners": "Tous les propriétaires | {{app}}", + "owners-detail": "", + "parts-queue": "", + "payments-all": "", + "phonebook": "", + "productionboard": "", + "productionlist": "", + "profile": "Mon profil | {{app}}", + "promanager": "", + "readyjobs": "", + "resetpassword": "", + "resetpasswordvalidate": "", + "romeonline": "", + "schedule": "Horaire | {{app}}", + "scoreboard": "", + "shop": "Mon magasin | {{app}}", + "shop-csi": "", + "shop-templates": "", + "shop_vendors": "Vendeurs | {{app}}", + "tasks": "", + "techconsole": "{{app}}", + "techjobclock": "{{app}}", + "techjoblookup": "{{app}}", + "techshiftclock": "{{app}}", + "temporarydocs": "", + "timetickets": "", + "ttapprovals": "", + "vehicledetail": "Détails du véhicule {{vehicle} | {{app}}", + "vehicles": "Tous les véhicules | {{app}}" + }, + "trello": { + "labels": { + "add_card": "", + "add_lane": "", + "cancel": "", + "delete_lane": "", + "description": "", + "label": "", + "lane_actions": "", + "title": "" + } + }, + "tt_approvals": { + "actions": { + "approveselected": "" + }, + "labels": { + "approval_queue_in_use": "", + "calculate": "" + } + }, + "user": { + "actions": { + "changepassword": "", + "signout": "Déconnexion", + "updateprofile": "Mettre à jour le profil" + }, + "errors": { + "updating": "" + }, + "fields": { + "authlevel": "", + "displayname": "Afficher un nom", + "email": "", + "photourl": "URL de l'avatar" + }, + "labels": { + "actions": "", + "changepassword": "", + "profileinfo": "" + }, + "successess": { + "passwordchanged": "" + } + }, + "users": { + "errors": { + "signinerror": { + "auth/user-disabled": "", + "auth/user-not-found": "", + "auth/wrong-password": "" + } + } + }, + "vehicles": { + "errors": { + "deleting": "", + "noaccess": "Le véhicule n'existe pas ou vous n'y avez pas accès.", + "selectexistingornew": "", + "validation": "Veuillez vous assurer que tous les champs sont correctement entrés.", + "validationtitle": "Erreur de validation" + }, + "fields": { + "description": "Description du véhicule", + "notes": "", + "plate_no": "Plaque d'immatriculation", + "plate_st": "Juridiction de la plaque", + "trim_color": "Couleur de garniture", + "v_bstyle": "Style corporel", + "v_color": "Couleur", + "v_cond": "Etat", + "v_engine": "moteur", + "v_make_desc": "Faire", + "v_makecode": "Faire du code", + "v_mldgcode": "Code de moulage", + "v_model_desc": "Modèle", + "v_model_yr": "année", + "v_options": "Les options", + "v_paint_codes": "Codes de peinture", + "v_prod_dt": "Date de production", + "v_stage": "Étape", + "v_tone": "ton", + "v_trimcode": "Code de coupe", + "v_type": "Type", + "v_vin": "V.I.N." + }, + "forms": { + "detail": "", + "misc": "", + "registration": "" + }, + "labels": { + "deleteconfirm": "", + "fromvehicle": "", + "novehinfo": "", + "relatedjobs": "", + "updatevehicle": "" + }, + "successes": { + "delete": "", + "save": "Le véhicule a été enregistré avec succès." + } + }, + "vendors": { + "actions": { + "addtophonebook": "", + "new": "Nouveau vendeur", + "newpreferredmake": "" + }, + "errors": { + "deleting": "Erreur rencontrée lors de la suppression du fournisseur.", + "saving": "Erreur rencontrée lors de l'enregistrement du fournisseur." + }, + "fields": { + "active": "", + "am": "", + "city": "Ville", + "cost_center": "Centre de coûts", + "country": "Pays", + "discount": "Remise %", + "display_name": "Afficher un nom", + "dmsid": "", + "due_date": "Date limite de paiement", + "email": "Email du contact", + "favorite": "Préféré?", + "lkq": "", + "make": "", + "name": "Nom du vendeur", + "oem": "", + "phone": "", + "prompt_discount": "Remise rapide%", + "state": "Etat / Province", + "street1": "rue", + "street2": "Adresse 2 ", + "taxid": "Identifiant de taxe", + "terms": "Modalités de paiement", + "zip": "Zip / code postal" + }, + "labels": { + "noneselected": "Aucun fournisseur n'est sélectionné.", + "preferredmakes": "", + "search": "Tapez le nom d'un vendeur" + }, + "successes": { + "deleted": "Le fournisseur a bien été supprimé.", + "saved": "Le fournisseur a bien enregistré." + }, + "validation": { + "unique_vendor_name": "" + } + } + } } diff --git a/job-totals-testing-util.js b/job-totals-testing-util.js index fdd312df8..0bcc7e072 100644 --- a/job-totals-testing-util.js +++ b/job-totals-testing-util.js @@ -15,7 +15,7 @@ require("dotenv").config({ async function RunTheTest() { const bodyshopids = ["71f8494c-89f0-43e0-8eb2-820b52d723bc"]; - const bearerToken = `Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImU2YWMzNTcyNzY3ZGUyNjE0ZmM1MTA4NjMzMDg3YTQ5MjMzMDNkM2IiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiUGF0cmljayBGaWMgKERFVikiLCJodHRwczovL2hhc3VyYS5pby9qd3QvY2xhaW1zIjp7IngtaGFzdXJhLWRlZmF1bHQtcm9sZSI6InVzZXIiLCJ4LWhhc3VyYS1hbGxvd2VkLXJvbGVzIjpbInVzZXIiXSwieC1oYXN1cmEtdXNlci1pZCI6ImhOSjhBRHB0REhRQkRFcXNCOFFNWVRqaURuZjEifSwiaXNzIjoiaHR0cHM6Ly9zZWN1cmV0b2tlbi5nb29nbGUuY29tL2ltZXgtZGV2IiwiYXVkIjoiaW1leC1kZXYiLCJhdXRoX3RpbWUiOjE3MzAxMzIwMjksInVzZXJfaWQiOiJoTko4QURwdERIUUJERXFzQjhRTVlUamlEbmYxIiwic3ViIjoiaE5KOEFEcHRESFFCREVxc0I4UU1ZVGppRG5mMSIsImlhdCI6MTczMDQwNzg2MSwiZXhwIjoxNzMwNDExNDYxLCJlbWFpbCI6InBhdHJpY2tAaW1leC5kZXYiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsicGF0cmlja0BpbWV4LmRldiJdfSwic2lnbl9pbl9wcm92aWRlciI6InBhc3N3b3JkIn19.Mxla5zqBNpdzXuAPqCswsLohrV5xm0SNi2Agr0lAU_crNJxVXJdTaKL8B4Av-LMCGjylHqume9WUbZ43fnMxrq4hlWGqeDzGpD7ev76h4omSn0jiMJ0qZmFeR1ejV40KZ6eLrvxEbDLiAebbU2rLp7tZonpspwPa1ruoRk4ixvtvv66ZNcbiEA5NP3VM9VDFnLzj8Vb160t3dQF58bxMUt8RoCc9cP_XPXWxnj-DsyDaYBO-shJrg3Co8T_jPg2ix-x2HkAVpPvTUjCCpAya-DhOx5aFZYiaNlmvosqCqcfZgVfjVuEogZb_rDkSF4wkR8DtfUt9IgGjWxF_rhos_Q`; + const bearerToken = `Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImI4Y2FjOTViNGE1YWNkZTBiOTY1NzJkZWU4YzhjOTVlZWU0OGNjY2QiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiUGF0cmljayBGaWMgKERFVikiLCJodHRwczovL2hhc3VyYS5pby9qd3QvY2xhaW1zIjp7IngtaGFzdXJhLWRlZmF1bHQtcm9sZSI6InVzZXIiLCJ4LWhhc3VyYS1hbGxvd2VkLXJvbGVzIjpbInVzZXIiXSwieC1oYXN1cmEtdXNlci1pZCI6ImhOSjhBRHB0REhRQkRFcXNCOFFNWVRqaURuZjEifSwiaXNzIjoiaHR0cHM6Ly9zZWN1cmV0b2tlbi5nb29nbGUuY29tL2ltZXgtZGV2IiwiYXVkIjoiaW1leC1kZXYiLCJhdXRoX3RpbWUiOjE3MzAxMzIwMjksInVzZXJfaWQiOiJoTko4QURwdERIUUJERXFzQjhRTVlUamlEbmYxIiwic3ViIjoiaE5KOEFEcHRESFFCREVxc0I4UU1ZVGppRG5mMSIsImlhdCI6MTczMDg0MTc2NSwiZXhwIjoxNzMwODQ1MzY1LCJlbWFpbCI6InBhdHJpY2tAaW1leC5kZXYiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsicGF0cmlja0BpbWV4LmRldiJdfSwic2lnbl9pbl9wcm92aWRlciI6InBhc3N3b3JkIn19.npQWkyB5cB4wmkaBsQiY3JbvBM9vKPqf3e22nVHnSydGcQi0p9M2mca9FcDtdcWvQlShUM63FF-6KkzpovC92sHauNmzCSXRInaaCPEussUUNSJEe2gEV03tYX447LkkSmFQbJ5V6qLTIDelm25fF0MoEDVnLTgythK_9927f8cxKZH1kEow0ymDeMaWey1sRyu7n15OJMcu692mfuQnBAArGTHGJ4YmReI7tMmdrV438MLxuVpH5CLb6uzlUdZoJ__7yh0kz0lkZEeHQAL8yq-0fISbPeZ5uXuMzYGrHuuKsIPRoeShVSVnF7ov8yTT3_YrCkhYbxl0eSTfBB5OdQ`; const { jobs } = await client.request( gql` @@ -75,7 +75,12 @@ 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 + comment: newjob.comment, + imexsubtotal: Dinero(newjob.job_totals.totals.subtotal).toFormat("0.00"), + imextotalrepair: Dinero(newjob.job_totals.totals.total_repairs).toFormat("0.00"), + g_tax: newjob.cieca_ttl.data.g_tax, + n_ttl_amt: newjob.cieca_ttl.data.n_ttl_amt, + g_ttl_amt: newjob.cieca_ttl.data.g_ttl_amt }; const calcTotal = newjob.job_totals.totals.total_repairs.amount; @@ -90,15 +95,14 @@ async function RunTheTest() { result.ttl_adjustment = Dinero(newjob.job_totals.totals.ttl_adjustment).toFormat(); result.ttl_tax_adjustment = Dinero(newjob.job_totals.totals.ttl_tax_adjustment).toFormat(); - const calcTax = - Dinero(newjob.job_totals.totals.us_sales_tax_breakdown.ty1Tax) - .add(Dinero(newjob.job_totals.totals.us_sales_tax_breakdown.ty2Tax)) - .add(Dinero(newjob.job_totals.totals.us_sales_tax_breakdown.ty3Tax)) - .add(Dinero(newjob.job_totals.totals.us_sales_tax_breakdown.ty4Tax)) - .add(Dinero(newjob.job_totals.totals.us_sales_tax_breakdown.ty5Tax)) - .add(Dinero(newjob.job_totals.totals.ttl_tax_adjustment)) - .getAmount() / 100; - + const calcTaxDinero = Dinero(newjob.job_totals.totals.us_sales_tax_breakdown.ty1Tax) + .add(Dinero(newjob.job_totals.totals.us_sales_tax_breakdown.ty2Tax)) + .add(Dinero(newjob.job_totals.totals.us_sales_tax_breakdown.ty3Tax)) + .add(Dinero(newjob.job_totals.totals.us_sales_tax_breakdown.ty4Tax)) + .add(Dinero(newjob.job_totals.totals.us_sales_tax_breakdown.ty5Tax)) + .add(Dinero(newjob.job_totals.totals.ttl_tax_adjustment)); + result.calcTax = calcTaxDinero.toFormat("0.00"); + const calcTax = calcTaxDinero.getAmount() / 100; const emsTax = newjob.cieca_ttl.data.g_tax; result.taxDifference = calcTax - emsTax; @@ -113,7 +117,7 @@ async function RunTheTest() { results.push({ ro_number: job.ro_number, id: job.id, - result: "**503 FAILURE**" + result: error.message }); } }); diff --git a/server/accounting/qb-receivables-lines.js b/server/accounting/qb-receivables-lines.js index 043dc8e89..c257cd944 100644 --- a/server/accounting/qb-receivables-lines.js +++ b/server/accounting/qb-receivables-lines.js @@ -548,6 +548,61 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes } } + if (jobs_by_pk.job_totals.totals.ttl_adjustment) { + // Do not need to check for ImEX or Rome because ImEX uses a different totals calculation that will never set this field. + if (qbo) { + const taxAccountCode = findTaxCode( + { + local: false, + federal: InstanceManager({ imex: true, rome: false }), + state: jobs_by_pk.tax_lbr_rt === 0 ? false : true + }, + bodyshop.md_responsibility_centers.sales_tax_codes + ); + + const QboTaxId = InstanceManager({ + imex: taxCodes[taxAccountCode], + rome: CheckQBOUSATaxID({ + // jobline: jobline, + type: "adjustment", + job: jobs_by_pk + }) + }); + + InvoiceLineAdd.push({ + DetailType: "SalesItemLineDetail", + Amount: Dinero(jobs_by_pk.job_totals.totals.ttl_adjustment).toFormat(DineroQbFormat), + SalesItemLineDetail: { + ...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}), + ItemRef: { + value: items[responsibilityCenters.ttl_adjustment.accountitem] + }, + TaxCodeRef: { + value: QboTaxId + }, + Qty: 1 + } + }); + } else { + InvoiceLineAdd.push({ + ItemRef: { + FullName: responsibilityCenters.ttl_adjustment.accountitem + }, + Desc: "Adjustment", + Quantity: 1, + Amount: Dinero(jobs_by_pk.job_totals.totals.ttl_adjustment).toFormat(DineroQbFormat), + SalesTaxCodeRef: InstanceManager({ + imex: { + FullName: "E" + }, + rome: { + FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON" + } + }) + }); + } + } + //Add tax lines const job_totals = jobs_by_pk.job_totals; const federal_tax = Dinero(job_totals.totals.federal_tax); @@ -824,7 +879,60 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes } } } + if (jobs_by_pk.job_totals.totals.ttl_tax_adjustment) { + // Do not need to check for ImEX or Rome because ImEX uses a different totals calculation that will never set this field. + if (qbo) { + const taxAccountCode = findTaxCode( + { + local: false, + federal: InstanceManager({ imex: true, rome: false }), + state: jobs_by_pk.tax_lbr_rt === 0 ? false : true + }, + bodyshop.md_responsibility_centers.sales_tax_codes + ); + const QboTaxId = InstanceManager({ + imex: taxCodes[taxAccountCode], + rome: CheckQBOUSATaxID({ + // jobline: jobline, + type: "adjustment", + job: jobs_by_pk + }) + }); + + InvoiceLineAdd.push({ + DetailType: "SalesItemLineDetail", + Amount: Dinero(jobs_by_pk.job_totals.totals.ttl_tax_adjustment).toFormat(DineroQbFormat), + SalesItemLineDetail: { + ...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}), + ItemRef: { + value: items[responsibilityCenters.ttl_tax_adjustment.accountitem] + }, + TaxCodeRef: { + value: QboTaxId + }, + Qty: 1 + } + }); + } else { + InvoiceLineAdd.push({ + ItemRef: { + FullName: responsibilityCenters.ttl_tax_adjustment.accountitem + }, + Desc: "Tax Adjustment", + Quantity: 1, + Amount: Dinero(jobs_by_pk.job_totals.totals.ttl_tax_adjustment).toFormat(DineroQbFormat), + SalesTaxCodeRef: InstanceManager({ + imex: { + FullName: "E" + }, + rome: { + FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON" + } + }) + }); + } + } if (!qbo && InvoiceLineAdd.length === 0) { //Handle the scenario where there is a $0 sale invoice. InvoiceLineAdd.push({ diff --git a/server/job/job-totals-USA.js b/server/job/job-totals-USA.js index f673b5728..804cd1885 100644 --- a/server/job/job-totals-USA.js +++ b/server/job/job-totals-USA.js @@ -72,10 +72,10 @@ async function TotalsServerSide(req, res) { const emsTotal = job.cieca_ttl.data.n_ttl_amt === job.cieca_ttl.data.g_ttl_amt //It looks like sometimes, gross and net are the same, but they shouldn't be. ? job.cieca_ttl.data.g_ttl_amt - job.cieca_ttl.data.g_tax //If they are, adjust the gross total down by the tax amount. - : job.cieca_ttl.data.n_ttl_amt; + : job.cieca_ttl.data.n_ttl_amt - job.cieca_ttl.data.g_tax; const ttlDifference = emsTotal - ret.totals.subtotal.getAmount() / 100; - if (Math.abs(ttlDifference) > 0.01) { + if (Math.abs(ttlDifference) > 0.00) { //If difference is greater than a pennny, we need to adjust it. ret.totals.ttl_adjustment = Dinero({ amount: Math.round(ttlDifference * 100) }); ret.totals.subtotal = ret.totals.subtotal.add(ret.totals.ttl_adjustment); @@ -97,7 +97,7 @@ async function TotalsServerSide(req, res) { 100; const ttlTaxDifference = emsTaxTotal - totalUsTaxes; - if (Math.abs(ttlTaxDifference) > 0.01) { + if (Math.abs(ttlTaxDifference) > 0.00) { //If difference is greater than a pennny, we need to adjust it. ret.totals.ttl_tax_adjustment = Dinero({ amount: Math.round(ttlTaxDifference * 100) }); ret.totals.total_repairs = ret.totals.total_repairs.add(ret.totals.ttl_tax_adjustment); From cb80b79e1dea79f25223395e89462cdbce2ea5b2 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Tue, 12 Nov 2024 11:58:51 -0800 Subject: [PATCH 04/74] IO-3001 WIP CDK Adjustments. --- server/cdk/cdk-calculate-allocations.js | 38 ++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/server/cdk/cdk-calculate-allocations.js b/server/cdk/cdk-calculate-allocations.js index f78c48b91..03f7addae 100644 --- a/server/cdk/cdk-calculate-allocations.js +++ b/server/cdk/cdk-calculate-allocations.js @@ -352,6 +352,7 @@ function calculateAllocations(connectionData, job) { // console.log("NO MASH ACCOUNT FOUND!!"); } } + if (InstanceManager({ rome: true })) { //profile level adjustments for parts Object.keys(job.job_totals.parts.adjustments).forEach((key) => { @@ -427,6 +428,41 @@ function calculateAllocations(connectionData, job) { } else { return { ...taxAllocations[key], tax: key }; } - }) + }), + + ...(job.job_totals.totals.ttl_adjustment + ? [ + { + center: "SUB ADJ", + sale: Dinero(job.job_totals.totals.ttl_adjustment), + cost: Dinero(), + profitCenter: { + name: "SUB ADJ", + accountdesc: "SUB ADJ", + accountitem: "SUB ADJ", + accountname: "SUB ADJ", + dms_acctnumber: bodyshop.md_responsibility_centers.ttl_adjustment.dms_acctnumber + }, + costCenter: {} + } + ] + : []), + ...(job.job_totals.totals.ttl_tax_adjustment + ? [ + { + center: "TAX ADJ", + sale: Dinero(job.job_totals.totals.ttl_tax_adjustment), + cost: Dinero(), + profitCenter: { + name: "TAX ADJ", + accountdesc: "TAX ADJ", + accountitem: "TAX ADJ", + accountname: "TAX ADJ", + dms_acctnumber: bodyshop.md_responsibility_centers.ttl_tax_adjustment.dms_acctnumber + }, + costCenter: {} + } + ] + : []) ]; } From e363dca3f011de19533d80f3742dc48af979248b Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Fri, 15 Nov 2024 10:56:07 -0800 Subject: [PATCH 05/74] IO-3001 Additional tax changes. --- .../job-lines-part-price-change.component.jsx | 41 ++++++++++--------- server/job/job-totals-USA.js | 21 ++++------ 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/client/src/components/job-detail-lines/job-lines-part-price-change.component.jsx b/client/src/components/job-detail-lines/job-lines-part-price-change.component.jsx index a7548542c..13798eb0e 100644 --- a/client/src/components/job-detail-lines/job-lines-part-price-change.component.jsx +++ b/client/src/components/job-detail-lines/job-lines-part-price-change.component.jsx @@ -1,5 +1,5 @@ import { useMutation } from "@apollo/client"; -import { Button, Form, notification, Popover, Tooltip } from "antd"; +import { Button, Checkbox, Form, notification, Popover, Tooltip } from "antd"; import axios from "axios"; import { t } from "i18next"; import React, { useState } from "react"; @@ -60,27 +60,30 @@ export function JobLinesPartPriceChange({ job, line, refetch, technician }) { } }; - const popcontent = !technician && InstanceRenderManager({ - imex: null, - rome: ( -
- - - - -
- ), - promanager: null - }); + const popcontent = + !technician && + InstanceRenderManager({ + imex: null, + rome: ( +
+ + + + +
+ ), + promanager: null + }); return ( + {import.meta.env.DEV && } {line.db_ref === "900510" || line.db_ref === "900511" ? line.prt_dsmk_m : line.act_price} diff --git a/server/job/job-totals-USA.js b/server/job/job-totals-USA.js index 804cd1885..de1db5802 100644 --- a/server/job/job-totals-USA.js +++ b/server/job/job-totals-USA.js @@ -49,7 +49,7 @@ exports.totalsSsu = async function (req, res) { } catch (error) { logger.log("job-totals-ssu-USA-error", "ERROR", req?.user?.email, id, { jobid: id, - error + error: error.message }); res.status(503).send(); } @@ -75,7 +75,7 @@ async function TotalsServerSide(req, res) { : job.cieca_ttl.data.n_ttl_amt - job.cieca_ttl.data.g_tax; const ttlDifference = emsTotal - ret.totals.subtotal.getAmount() / 100; - if (Math.abs(ttlDifference) > 0.00) { + if (Math.abs(ttlDifference) > 0.0) { //If difference is greater than a pennny, we need to adjust it. ret.totals.ttl_adjustment = Dinero({ amount: Math.round(ttlDifference * 100) }); ret.totals.subtotal = ret.totals.subtotal.add(ret.totals.ttl_adjustment); @@ -97,7 +97,7 @@ async function TotalsServerSide(req, res) { 100; const ttlTaxDifference = emsTaxTotal - totalUsTaxes; - if (Math.abs(ttlTaxDifference) > 0.00) { + if (Math.abs(ttlTaxDifference) > 0.0) { //If difference is greater than a pennny, we need to adjust it. ret.totals.ttl_tax_adjustment = Dinero({ amount: Math.round(ttlTaxDifference * 100) }); ret.totals.total_repairs = ret.totals.total_repairs.add(ret.totals.ttl_tax_adjustment); @@ -998,7 +998,7 @@ function CalculateTaxesTotals(job, otherTotals) { } } } catch (error) { - logger.log("job-totals-USA Key with issue", "error", null, null, { + logger.log("job-totals-USA Key with issue", "error", null, job.id, { key }); } @@ -1028,7 +1028,7 @@ function CalculateTaxesTotals(job, otherTotals) { for (let threshCounter = 1; threshCounter <= 5; threshCounter++) { const thresholdAmount = parseFloat(job.cieca_pft[`ty${tyCounter}_thres${threshCounter}`]) || 0; const thresholdTaxRate = parseFloat(job.cieca_pft[`ty${tyCounter}_rate${threshCounter}`]) || 0; - + // console.log(taxTierKey, tyCounter, threshCounter, thresholdAmount, thresholdTaxRate); let taxableAmountInThisThreshold; if ( thresholdAmount === 9999.99 || @@ -1052,11 +1052,8 @@ function CalculateTaxesTotals(job, otherTotals) { taxableAmountInThisThreshold = Dinero({ amount: Math.round(thresholdAmount * 100) }); - remainingTaxableAmounts[taxTierKey] = remainingTaxableAmounts[taxTierKey].subtract( - Dinero({ - amount: Math.round(taxableAmountInThisThreshold * 100) - }) - ); + remainingTaxableAmounts[taxTierKey] = + remainingTaxableAmounts[taxTierKey].subtract(taxableAmountInThisThreshold); } } @@ -1065,8 +1062,8 @@ function CalculateTaxesTotals(job, otherTotals) { totalTaxByTier[taxTierKey] = totalTaxByTier[taxTierKey].add(taxAmountToAdd); } } catch (error) { - logger.log("job-totals-USA - PFP Calculation Error", "error", null, null, { - error + logger.log("job-totals-USA - PFP Calculation Error", "error", null, job.id, { + error: error.message }); } }); From 7cbabf8697cc0367645fefa582be4514934c947d Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Mon, 18 Nov 2024 12:02:08 -0800 Subject: [PATCH 06/74] IO-3027 Turn Off SFTP Logging for Production ENV Signed-off-by: Allan Carr --- package-lock.json | 11 ++++++----- package.json | 2 +- server/data/autohouse.js | 4 +++- server/data/chatter.js | 4 +++- server/data/claimscorp.js | 4 +++- server/data/kaizen.js | 4 +++- 6 files changed, 19 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2f0edfad5..92fb059f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,7 +53,7 @@ "soap": "^1.1.5", "socket.io": "^4.8.0", "socket.io-adapter": "^2.5.5", - "ssh2-sftp-client": "^10.0.3", + "ssh2-sftp-client": "^11.0.0", "twilio": "^4.23.0", "uuid": "^10.0.0", "winston": "^3.15.0", @@ -7760,16 +7760,17 @@ } }, "node_modules/ssh2-sftp-client": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/ssh2-sftp-client/-/ssh2-sftp-client-10.0.3.tgz", - "integrity": "sha512-Wlhasz/OCgrlqC8IlBZhF19Uw/X/dHI8ug4sFQybPE+0sDztvgvDf7Om6o7LbRLe68E7XkFZf3qMnqAvqn1vkQ==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/ssh2-sftp-client/-/ssh2-sftp-client-11.0.0.tgz", + "integrity": "sha512-lOjgNYtioYquhtgyHwPryFNhllkuENjvCKkUXo18w/Q4UpEffCnEUBfiOTlwFdKIhG1rhrOGnA6DeKPSF2CP6w==", + "license": "Apache-2.0", "dependencies": { "concat-stream": "^2.0.0", "promise-retry": "^2.0.1", "ssh2": "^1.15.0" }, "engines": { - "node": ">=16.20.2" + "node": ">=18.20.4" }, "funding": { "type": "individual", diff --git a/package.json b/package.json index 2a3c24ded..3090a4efa 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "soap": "^1.1.5", "socket.io": "^4.8.0", "socket.io-adapter": "^2.5.5", - "ssh2-sftp-client": "^10.0.3", + "ssh2-sftp-client": "^11.0.0", "twilio": "^4.23.0", "uuid": "^10.0.0", "winston": "^3.15.0", diff --git a/server/data/autohouse.js b/server/data/autohouse.js index 7d8447f68..1384771e6 100644 --- a/server/data/autohouse.js +++ b/server/data/autohouse.js @@ -25,7 +25,9 @@ const ftpSetup = { port: process.env.AUTOHOUSE_PORT, username: process.env.AUTOHOUSE_USER, password: process.env.AUTOHOUSE_PASSWORD, - debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data), + debug: process.env.NODE_ENV !== "production" + ? (message, ...data) => logger.log(message, "DEBUG", "api", null, data) + : () => {}, algorithms: { serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"] } diff --git a/server/data/chatter.js b/server/data/chatter.js index 8890fafe9..8fa217149 100644 --- a/server/data/chatter.js +++ b/server/data/chatter.js @@ -17,7 +17,9 @@ const ftpSetup = { port: process.env.CHATTER_PORT, username: process.env.CHATTER_USER, privateKey: null, - debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data), + debug: process.env.NODE_ENV !== "production" + ? (message, ...data) => logger.log(message, "DEBUG", "api", null, data) + : () => {}, algorithms: { serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"] } diff --git a/server/data/claimscorp.js b/server/data/claimscorp.js index cafa03df1..b8c77a018 100644 --- a/server/data/claimscorp.js +++ b/server/data/claimscorp.js @@ -24,7 +24,9 @@ const ftpSetup = { port: process.env.CLAIMSCORP_PORT, username: process.env.CLAIMSCORP_USER, password: process.env.CLAIMSCORP_PASSWORD, - debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data), + debug: process.env.NODE_ENV !== "production" + ? (message, ...data) => logger.log(message, "DEBUG", "api", null, data) + : () => {}, algorithms: { serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"] } diff --git a/server/data/kaizen.js b/server/data/kaizen.js index b79fe4745..bbb758a93 100644 --- a/server/data/kaizen.js +++ b/server/data/kaizen.js @@ -23,7 +23,9 @@ const ftpSetup = { port: process.env.KAIZEN_PORT, username: process.env.KAIZEN_USER, password: process.env.KAIZEN_PASSWORD, - debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data), + debug: process.env.NODE_ENV !== "production" + ? (message, ...data) => logger.log(message, "DEBUG", "api", null, data) + : () => {}, algorithms: { serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"] } From 2304e0bf02b85afdad5d0473f0f90fa0a8aa7aae Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Tue, 19 Nov 2024 10:59:59 -0800 Subject: [PATCH 07/74] IO-3001 Resolve towing totals issue. --- server/job/job-totals-USA.js | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/server/job/job-totals-USA.js b/server/job/job-totals-USA.js index de1db5802..2ebf5910f 100644 --- a/server/job/job-totals-USA.js +++ b/server/job/job-totals-USA.js @@ -881,17 +881,21 @@ function CalculateTaxesTotals(job, otherTotals) { } }); //Add towing and storage taxable amounts - const stlTowing = job.cieca_stl?.data.find((c) => c.ttl_typecd === "OTTW"); - const stlStorage = job.cieca_stl?.data.find((c) => c.ttl_typecd === "OTST"); + const stlTowing = job.cieca_stl?.data.find((c) => c.ttl_typecd === "OTTW" || c.ttl_type === "OTTW"); + const stlStorage = job.cieca_stl?.data.find((c) => c.ttl_typecd === "OTST" || c.ttl_type === "OTST"); if (stlTowing) - taxableAmounts.TOW = Dinero({ - amount: Math.round(stlTowing.t_amt * 100) - }); + taxableAmounts.TOW = taxableAmounts.TOW.add( + Dinero({ + amount: Math.round(stlTowing.t_amt * 100) + }) + ); if (stlStorage) - taxableAmounts.TOW = Dinero({ - amount: Math.round(stlStorage.t_amt * 100) - }); + taxableAmounts.TOW = taxableAmounts.TOW.add( + (taxableAmounts.TOW = Dinero({ + amount: Math.round(stlStorage.t_amt * 100) + })) + ); const pfp = job.parts_tax_rates; @@ -903,8 +907,8 @@ function CalculateTaxesTotals(job, otherTotals) { } }); - // console.log("*** Taxable Amounts***"); - // console.table(JSON.parse(JSON.stringify(taxableAmounts))); + 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. @@ -1018,8 +1022,8 @@ function CalculateTaxesTotals(job, otherTotals) { } const remainingTaxableAmounts = taxableAmountsByTier; - // console.log("*** Taxable Amounts by Tier***"); - // console.table(JSON.parse(JSON.stringify(taxableAmountsByTier))); + console.log("*** Taxable Amounts by Tier***"); + console.table(JSON.parse(JSON.stringify(taxableAmountsByTier))); Object.keys(taxableAmountsByTier).forEach((taxTierKey) => { try { From 299a675a9c26ad1027ed1cc80cdc3776fc6a47fb Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Tue, 19 Nov 2024 15:52:57 -0800 Subject: [PATCH 08/74] IO-3000 Adjusted first approach at messaging WS changes. --- .../chat-affix/chat-affix.container.jsx | 14 +- .../registerMessagingSocketHandlers.js | 136 + .../chat-conversation-list.component.jsx | 57 +- .../chat-conversation-list.styles.scss | 25 +- .../chat-conversation.container.jsx | 58 +- .../chat-message-list.component.jsx | 151 +- .../chat-message-list.styles.scss | 21 +- .../chat-popup/chat-popup.component.jsx | 1 + client/src/contexts/SocketIO/useSocket.js | 10 +- client/src/graphql/conversations.queries.js | 30 + .../pages/manage/manage.page.component.jsx | 4 +- client/src/utils/fcm-handler.js | 134 +- client/vite.config.js | 26 - package-lock.json | 3234 ++++++----------- package.json | 32 +- server/graphql-client/queries.js | 87 + server/routes/miscellaneousRoutes.js | 54 + server/sms/receive.js | 307 +- server/sms/send.js | 34 +- server/sms/status.js | 63 +- server/utils/ioHelpers.js | 6 +- server/web-sockets/redisSocketEvents.js | 38 +- 22 files changed, 1952 insertions(+), 2570 deletions(-) create mode 100644 client/src/components/chat-affix/registerMessagingSocketHandlers.js diff --git a/client/src/components/chat-affix/chat-affix.container.jsx b/client/src/components/chat-affix/chat-affix.container.jsx index 0541cd167..0c13d8327 100644 --- a/client/src/components/chat-affix/chat-affix.container.jsx +++ b/client/src/components/chat-affix/chat-affix.container.jsx @@ -2,20 +2,26 @@ import { useApolloClient } from "@apollo/client"; import { getToken, onMessage } from "@firebase/messaging"; import { Button, notification, Space } from "antd"; import axios from "axios"; -import React, { useEffect } from "react"; +import React, { useContext, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { messaging, requestForToken } from "../../firebase/firebase.utils"; import FcmHandler from "../../utils/fcm-handler"; import ChatPopupComponent from "../chat-popup/chat-popup.component"; import "./chat-affix.styles.scss"; +import SocketContext from "../../contexts/SocketIO/socketContext"; +import { registerMessagingHandlers, unregisterMessagingHandlers } from "./registerMessagingSocketHandlers"; export function ChatAffixContainer({ bodyshop, chatVisible }) { const { t } = useTranslation(); const client = useApolloClient(); + const { socket } = useContext(SocketContext); useEffect(() => { if (!bodyshop || !bodyshop.messagingservicesid) return; + //Register WS handlers + registerMessagingHandlers({ socket, client }); + async function SubscribeToTopic() { try { const r = await axios.post("/notifications/subscribe", { @@ -61,7 +67,11 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) { SubscribeToTopic(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [bodyshop]); + + return () => { + unregisterMessagingHandlers({ socket }); + }; + }, [bodyshop, socket, t, client]); useEffect(() => { function handleMessage(payload) { diff --git a/client/src/components/chat-affix/registerMessagingSocketHandlers.js b/client/src/components/chat-affix/registerMessagingSocketHandlers.js new file mode 100644 index 000000000..08d2e86bb --- /dev/null +++ b/client/src/components/chat-affix/registerMessagingSocketHandlers.js @@ -0,0 +1,136 @@ +import { CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries"; + +export function registerMessagingHandlers({ socket, client }) { + if (!(socket && client)) return; + function handleNewMessageSummary(message) { + console.log("🚀 ~ SUMMARY CONSOLE LOG:", message); + + if (!message.isoutbound) { + //It's an inbound message. + if (!message.existingConversation) { + //Do a read query. + const queryResults = client.cache.readQuery({ + query: CONVERSATION_LIST_QUERY, + variables: {} + }); + // Do a write query. Assume 0 unread messages to utilize code below. + client.cache.writeQuery({ + query: CONVERSATION_LIST_QUERY, + variables: {}, + data: { + conversations: [ + { ...message.newConversation, messages_aggregate: { aggregate: { count: 0 } } }, + ...queryResults + ] + } + }); + } + client.cache.modify({ + id: client.cache.identify({ + __typename: "conversations", + id: message.conversationId + }), + fields: { + updated_at: () => new Date(), + messages_aggregate(cached) { + return { aggregate: { count: cached.aggregate.count + 1 } }; + } + } + }); + client.cache.modify({ + fields: { + conversations(existingConversations = [], { readField }) { + return [ + { __ref: `conversations:${message.conversationId}` }, // TODO: This throws the cache merging error in apollo. + ...existingConversations.filter((c) => c.__ref !== `conversations:${message.conversationId}`) + ]; + } + } + }); + + client.cache.modify({ + fields: { + messages_aggregate(cached) { + return { aggregate: { count: cached.aggregate.count + 1 } }; + } + } + }); + } else { + //It's an outbound message + //Update the last updated for conversations in the list. If it's new, add it in. + // If it isn't just update the last updated at. + client.cache.modify({ + id: client.cache.identify({ + __typename: "conversations", + id: message.conversationId + }), + fields: { + updated_at: () => message.newMessage.updated_at + } + }); + } + } + + function handleNewMessageDetailed(message) { + console.log("🚀 ~ DETAIL CONSOLE LOG:", message); + //They're looking at the conversation right now. Need to merge into the list of messages i.e. append to the end. + //Add the message to the overall cache. + + //Handle outbound messages + if (message.newMessage.isoutbound) { + const queryResults = client.cache.readQuery({ + query: GET_CONVERSATION_DETAILS, + variables: { conversationId: message.newMessage.conversationid } + }); + client.cache.writeQuery({ + query: GET_CONVERSATION_DETAILS, + variables: { conversationId: message.newMessage.conversationid }, + data: { + ...queryResults, + conversations_by_pk: { + ...queryResults.conversations_by_pk, + messages: [...queryResults.conversations_by_pk.messages, message.newMessage] + } + } + }); + } + // We got this as a receive. + else { + } + } + + function handleMessageChanged(message) { + //Find it in the cache, and just update it based on what was sent. + client.cache.modify({ + id: client.cache.identify({ + __typename: "messages", + id: message.id + }), + fields: { + //TODO: see if there is a way to have this update all fields e.g. only spread in updates rather than prescribing + updated_at: () => new Date(), + status(cached) { + return message.status; + } + } + }); + } + + function handleConversationChanged(conversation) { + //If it was archived, marked unread, etc. + } + + socket.on("new-message-summary", handleNewMessageSummary); + socket.on("new-message-detailed", handleNewMessageDetailed); + socket.on("message-changed", handleMessageChanged); + socket.on("conversation-changed", handleConversationChanged); //TODO: Unread, mark as read, archived, unarchive, etc. +} + +export function unregisterMessagingHandlers({ socket }) { + if (!socket) return; + socket.off("new-message-summary"); + socket.off("new-message-detailed"); + socket.off("message-changed"); + socket.off("message-changed"); + socket.off("conversation-changed"); +} diff --git a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx index a49c3b4b4..38221085d 100644 --- a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx +++ b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx @@ -1,7 +1,7 @@ import { Badge, Card, List, Space, Tag } from "antd"; import React from "react"; import { connect } from "react-redux"; -import { AutoSizer, CellMeasurer, CellMeasurerCache, List as VirtualizedList } from "react-virtualized"; +import { Virtuoso } from "react-virtuoso"; import { createStructuredSelector } from "reselect"; import { setSelectedConversation } from "../../redux/messaging/messaging.actions"; import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors"; @@ -25,12 +25,7 @@ function ChatConversationListComponent({ setSelectedConversation, loadMoreConversations }) { - const cache = new CellMeasurerCache({ - fixedWidth: true, - defaultHeight: 60 - }); - - const rowRenderer = ({ index, key, style, parent }) => { + const renderConversation = (index) => { const item = conversationList[index]; const cardContentRight = {item.updated_at}; const cardContentLeft = @@ -52,7 +47,8 @@ function ChatConversationListComponent({ )} ); - const cardExtra = ; + + const cardExtra = ; const getCardStyle = () => item.id === selectedConversation @@ -60,40 +56,27 @@ function ChatConversationListComponent({ : { backgroundColor: index % 2 === 0 ? "#f0f2f5" : "#ffffff" }; return ( - - setSelectedConversation(item.id)} - style={style} - className={`chat-list-item - ${item.id === selectedConversation ? "chat-list-selected-conversation" : null}`} - > - -
{cardContentLeft}
-
{cardContentRight}
-
-
-
+ setSelectedConversation(item.id)} + className={`chat-list-item ${item.id === selectedConversation ? "chat-list-selected-conversation" : ""}`} + > + +
{cardContentLeft}
+
{cardContentRight}
+
+
); }; return (
- - {({ height, width }) => ( - { - if (scrollTop + clientHeight === scrollHeight) { - loadMoreConversations(); - } - }} - /> - )} - + renderConversation(index)} + style={{ height: "100%", width: "100%" }} + endReached={loadMoreConversations} // Calls loadMoreConversations when scrolled to the bottom + />
); } diff --git a/client/src/components/chat-conversation-list/chat-conversation-list.styles.scss b/client/src/components/chat-conversation-list/chat-conversation-list.styles.scss index 2922799a2..e6169777c 100644 --- a/client/src/components/chat-conversation-list/chat-conversation-list.styles.scss +++ b/client/src/components/chat-conversation-list/chat-conversation-list.styles.scss @@ -1,7 +1,7 @@ .chat-list-container { - overflow: hidden; - height: 100%; + height: 100%; /* Ensure it takes up the full available height */ border: 1px solid gainsboro; + overflow: auto; /* Allow scrolling for the Virtuoso component */ } .chat-list-item { @@ -14,3 +14,24 @@ color: #ff7a00; } } + +/* Virtuoso item container adjustments */ +.chat-list-container > div { + height: 100%; /* Ensure Virtuoso takes full height */ + display: flex; + flex-direction: column; +} + +/* Add spacing and better alignment for items */ +.chat-list-item { + padding: 0.5rem 0; /* Add spacing between list items */ + + .ant-card { + border-radius: 8px; /* Slight rounding for card edges */ + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); /* Subtle shadow for better definition */ + } + + &:hover .ant-card { + border-color: #ff7a00; /* Highlight border on hover */ + } +} diff --git a/client/src/components/chat-conversation/chat-conversation.container.jsx b/client/src/components/chat-conversation/chat-conversation.container.jsx index 2b91d2320..2ea760e3c 100644 --- a/client/src/components/chat-conversation/chat-conversation.container.jsx +++ b/client/src/components/chat-conversation/chat-conversation.container.jsx @@ -1,13 +1,14 @@ -import { useMutation, useQuery, useSubscription } from "@apollo/client"; -import React, { useState } from "react"; +import { useMutation, useQuery } from "@apollo/client"; +import axios from "axios"; +import React, { useEffect, useState, useContext } from "react"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; -import { CONVERSATION_SUBSCRIPTION_BY_PK, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries"; +import SocketContext from "../../contexts/SocketIO/socketContext"; +import { GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries"; import { MARK_MESSAGES_AS_READ_BY_CONVERSATION } from "../../graphql/messages.queries"; import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors"; -import ChatConversationComponent from "./chat-conversation.component"; -import axios from "axios"; import { selectBodyshop } from "../../redux/user/user.selectors"; +import ChatConversationComponent from "./chat-conversation.component"; const mapStateToProps = createStructuredSelector({ selectedConversation: selectSelectedConversation, @@ -27,41 +28,34 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) { nextFetchPolicy: "network-only" }); - const { loading, error, data } = useSubscription(CONVERSATION_SUBSCRIPTION_BY_PK, { - variables: { conversationId: selectedConversation } - }); + const { socket } = useContext(SocketContext); + + useEffect(() => { + socket.emit("join-bodyshop-conversation", { bodyshopId: bodyshop.id, conversationId: selectedConversation }); + + return () => { + socket.emit("leave-bodyshop-conversation", { bodyshopId: bodyshop.id, conversationId: selectedConversation }); + }; + }, [selectedConversation, bodyshop, socket]); + + // const { loading, error, data } = useSubscription(CONVERSATION_SUBSCRIPTION_BY_PK, { + // variables: { conversationId: selectedConversation } + // }); const [markingAsReadInProgress, setMarkingAsReadInProgress] = useState(false); - const [markConversationRead] = useMutation(MARK_MESSAGES_AS_READ_BY_CONVERSATION, { - variables: { conversationId: selectedConversation }, - refetchQueries: ["UNREAD_CONVERSATION_COUNT"], - update(cache) { - cache.modify({ - id: cache.identify({ - __typename: "conversations", - id: selectedConversation - }), - fields: { - messages_aggregate(cached) { - return { aggregate: { count: 0 } }; - } - } - }); - } - }); - const unreadCount = - data && - data.messages && - data.messages.reduce((acc, val) => { + convoData && + convoData.conversations_by_pk && + convoData.conversations_by_pk.messages && + convoData.conversations_by_pk.messages.reduce((acc, val) => { return !val.read && !val.isoutbound ? acc + 1 : acc; }, 0); const handleMarkConversationAsRead = async () => { if (unreadCount > 0 && !!selectedConversation && !markingAsReadInProgress) { setMarkingAsReadInProgress(true); - await markConversationRead({}); + // await markConversationRead({}); await axios.post("/sms/markConversationRead", { conversationid: selectedConversation, imexshopid: bodyshop.imexshopid @@ -72,9 +66,9 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) { return ( ); diff --git a/client/src/components/chat-messages-list/chat-message-list.component.jsx b/client/src/components/chat-messages-list/chat-message-list.component.jsx index 55aa81dc0..076941d8c 100644 --- a/client/src/components/chat-messages-list/chat-message-list.component.jsx +++ b/client/src/components/chat-messages-list/chat-message-list.component.jsx @@ -2,105 +2,90 @@ import Icon from "@ant-design/icons"; import { Tooltip } from "antd"; import i18n from "i18next"; import dayjs from "../../utils/day"; -import React, { useEffect, useRef } from "react"; +import React, { useRef, useEffect } from "react"; import { MdDone, MdDoneAll } from "react-icons/md"; -import { AutoSizer, CellMeasurer, CellMeasurerCache, List } from "react-virtualized"; +import { Virtuoso } from "react-virtuoso"; import { DateTimeFormatter } from "../../utils/DateFormatter"; import "./chat-message-list.styles.scss"; export default function ChatMessageListComponent({ messages }) { - const virtualizedListRef = useRef(null); + const virtuosoRef = useRef(null); - const _cache = new CellMeasurerCache({ - fixedWidth: true, - // minHeight: 50, - defaultHeight: 100 - }); + // Scroll to the bottom after a short delay when the component mounts + useEffect(() => { + const timer = setTimeout(() => { + if (virtuosoRef.current) { + virtuosoRef.current.scrollToIndex({ + index: messages.length - 1, + behavior: "auto" // Instantly scroll to the bottom + }); + } + }, 100); // Delay of 100ms to allow rendering + return () => clearTimeout(timer); // Cleanup the timer on unmount + }, [messages.length]); // Run only once on component mount - const scrollToBottom = (renderedrows) => { - //console.log("Scrolling to", messages.length); - // !!virtualizedListRef.current && - // virtualizedListRef.current.scrollToRow(messages.length); - // Outstanding isue on virtualization: https://github.com/bvaughn/react-virtualized/issues/1179 - //Scrolling does not work on this version of React. - }; - - useEffect(scrollToBottom, [messages]); - - const _rowRenderer = ({ index, key, parent, style }) => { + // Scroll to the bottom after the new messages are rendered + useEffect(() => { + if (virtuosoRef.current) { + // Allow the DOM and Virtuoso to fully render the new data + setTimeout(() => { + virtuosoRef.current.scrollToIndex({ + index: messages.length - 1, + align: "end", // Ensure the last message is fully visible + behavior: "smooth" // Smooth scrolling + }); + }, 50); // Slight delay to ensure layout recalculates + } + }, [messages]); // Triggered when new messages are added + //TODO: Does this one need to come into the render of the method? + const renderMessage = (index) => { + const message = messages[index]; return ( - - {({ measure, registerChild }) => ( -
-
- {MessageRender(messages[index])} - {StatusRender(messages[index].status)} +
+
+ +
+ {message.image_path && + message.image_path.map((i, idx) => ( +
+ + Received + +
+ ))} +
{message.text}
- {messages[index].isoutbound && ( -
- {i18n.t("messaging.labels.sentby", { - by: messages[index].userid, - time: dayjs(messages[index].created_at).format("MM/DD/YYYY @ hh:mm a") - })} -
- )} +
+ {message.status && ( +
+ +
+ )} +
+ {message.isoutbound && ( +
+ {i18n.t("messaging.labels.sentby", { + by: message.userid, + time: dayjs(message.created_at).format("MM/DD/YYYY @ hh:mm a") + })}
)} - +
); }; return (
- - {({ height, width }) => ( - - )} - + renderMessage(index)} + followOutput="smooth" // Ensure smooth scrolling when new data is appended + style={{ height: "100%", width: "100%" }} + />
); } - -const MessageRender = (message) => { - return ( - -
- {message.image_path && - message.image_path.map((i, idx) => ( -
- - Received - -
- ))} -
{message.text}
-
-
- ); -}; - -const StatusRender = (status) => { - switch (status) { - case "sent": - return ; - case "delivered": - return ; - default: - return null; - } -}; diff --git a/client/src/components/chat-messages-list/chat-message-list.styles.scss b/client/src/components/chat-messages-list/chat-message-list.styles.scss index d576fa7b6..7958f02c3 100644 --- a/client/src/components/chat-messages-list/chat-message-list.styles.scss +++ b/client/src/components/chat-messages-list/chat-message-list.styles.scss @@ -1,37 +1,30 @@ .message-icon { - //position: absolute; - // bottom: 0rem; color: whitesmoke; border: #000000; position: absolute; margin: 0 0.1rem; bottom: 0.1rem; right: 0.3rem; - z-index: 5; } .chat { flex: 1; - //width: 300px; - //border: solid 1px #eee; display: flex; flex-direction: column; margin: 0.8rem 0rem; + overflow: hidden; // Ensure the content scrolls correctly } .messages { - //margin-top: 30px; display: flex; flex-direction: column; + padding: 0.5rem; // Add padding to avoid edge clipping } .message { border-radius: 20px; padding: 0.25rem 0.8rem; - //margin-top: 5px; - // margin-bottom: 5px; - //display: inline-block; .message-img { max-width: 10rem; @@ -56,7 +49,7 @@ position: relative; } -.yours .message.last:before { +.yours .message:last-child:before { content: ""; position: absolute; z-index: 0; @@ -68,7 +61,7 @@ border-bottom-right-radius: 15px; } -.yours .message.last:after { +.yours .message:last-child:after { content: ""; position: absolute; z-index: 1; @@ -88,12 +81,11 @@ color: white; margin-left: 25%; background: linear-gradient(to bottom, #00d0ea 0%, #0085d1 100%); - background-attachment: fixed; position: relative; padding-bottom: 0.6rem; } -.mine .message.last:before { +.mine .message:last-child:before { content: ""; position: absolute; z-index: 0; @@ -102,11 +94,10 @@ height: 20px; width: 20px; background: linear-gradient(to bottom, #00d0ea 0%, #0085d1 100%); - background-attachment: fixed; border-bottom-left-radius: 15px; } -.mine .message.last:after { +.mine .message:last-child:after { content: ""; position: absolute; z-index: 1; diff --git a/client/src/components/chat-popup/chat-popup.component.jsx b/client/src/components/chat-popup/chat-popup.component.jsx index d557ad91d..b9a500166 100644 --- a/client/src/components/chat-popup/chat-popup.component.jsx +++ b/client/src/components/chat-popup/chat-popup.component.jsx @@ -41,6 +41,7 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh const fcmToken = sessionStorage.getItem("fcmtoken"); + //TODO: Change to be a fallback incase sockets shit the bed useEffect(() => { if (fcmToken) { setpollInterval(0); diff --git a/client/src/contexts/SocketIO/useSocket.js b/client/src/contexts/SocketIO/useSocket.js index 1c5a058fc..69c9402bd 100644 --- a/client/src/contexts/SocketIO/useSocket.js +++ b/client/src/contexts/SocketIO/useSocket.js @@ -3,10 +3,12 @@ import SocketIO from "socket.io-client"; import { auth } from "../../firebase/firebase.utils"; import { store } from "../../redux/store"; import { addAlerts, setWssStatus } from "../../redux/application/application.actions"; +import { useDispatch } from "react-redux"; const useSocket = (bodyshop) => { const socketRef = useRef(null); const [clientId, setClientId] = useState(null); + const dispatch = useDispatch(); useEffect(() => { const unsubscribe = auth.onIdTokenChanged(async (user) => { @@ -38,6 +40,8 @@ const useSocket = (bodyshop) => { case "alert-update": store.dispatch(addAlerts(message.payload)); break; + default: + break; } if (!import.meta.env.DEV) return; @@ -45,14 +49,12 @@ const useSocket = (bodyshop) => { }; const handleConnect = () => { - console.log("Socket connected:", socketInstance.id); socketInstance.emit("join-bodyshop-room", bodyshop.id); setClientId(socketInstance.id); store.dispatch(setWssStatus("connected")); }; const handleReconnect = (attempt) => { - console.log(`Socket reconnected after ${attempt} attempts`); store.dispatch(setWssStatus("connected")); }; @@ -62,10 +64,10 @@ const useSocket = (bodyshop) => { }; const handleDisconnect = () => { - console.log("Socket disconnected"); store.dispatch(setWssStatus("disconnected")); }; + //TODO: Check these handlers. socketInstance.on("connect", handleConnect); socketInstance.on("reconnect", handleReconnect); socketInstance.on("connect_error", handleConnectionError); @@ -89,7 +91,7 @@ const useSocket = (bodyshop) => { socketRef.current = null; } }; - }, [bodyshop]); + }, [bodyshop, dispatch]); return { socket: socketRef.current, clientId }; }; diff --git a/client/src/graphql/conversations.queries.js b/client/src/graphql/conversations.queries.js index f482cc325..e05a474ab 100644 --- a/client/src/graphql/conversations.queries.js +++ b/client/src/graphql/conversations.queries.js @@ -71,6 +71,17 @@ export const GET_CONVERSATION_DETAILS = gql` ro_number } } + messages(order_by: { created_at: asc_nulls_first }) { + id + status + text + isoutbound + image + image_path + userid + created_at + read + } } } `; @@ -114,3 +125,22 @@ export const UPDATE_CONVERSATION_LABEL = gql` } } `; + +export const GET_CONVERSATION_MESSAGES = gql` + query GET_CONVERSATION_MESSAGES($conversationId: uuid!) { + conversation: conversations_by_pk(id: $conversationId) { + id + phone_num + updated_at + label + } + messages: messages(where: { conversationid: { _eq: $conversationId } }, order_by: { created_at: asc }) { + id + text + created_at + read + isoutbound + userid + } + } +`; diff --git a/client/src/pages/manage/manage.page.component.jsx b/client/src/pages/manage/manage.page.component.jsx index 6dfd8af6e..c1a8e561c 100644 --- a/client/src/pages/manage/manage.page.component.jsx +++ b/client/src/pages/manage/manage.page.component.jsx @@ -145,7 +145,7 @@ export function Manage({ conflict, bodyshop, alerts, setAlerts }) { }; fetchAlerts(); - }, []); + }, [setAlerts]); // Use useEffect to watch for new alerts useEffect(() => { @@ -647,7 +647,7 @@ export function Manage({ conflict, bodyshop, alerts, setAlerts }) { return ( <> - {import.meta.env.PROD && } + {true && } diff --git a/client/src/utils/fcm-handler.js b/client/src/utils/fcm-handler.js index c6284c764..e3f7c8d43 100644 --- a/client/src/utils/fcm-handler.js +++ b/client/src/utils/fcm-handler.js @@ -1,70 +1,70 @@ export default async function FcmHandler({ client, payload }) { console.log("FCM", payload); - switch (payload.type) { - case "messaging-inbound": - client.cache.modify({ - id: client.cache.identify({ - __typename: "conversations", - id: payload.conversationid - }), - fields: { - messages_aggregate(cached) { - return { aggregate: { count: cached.aggregate.count + 1 } }; - } - } - }); - client.cache.modify({ - fields: { - messages_aggregate(cached) { - return { aggregate: { count: cached.aggregate.count + 1 } }; - } - } - }); - break; - case "messaging-outbound": - client.cache.modify({ - id: client.cache.identify({ - __typename: "conversations", - id: payload.conversationid - }), - fields: { - updated_at(oldupdated0) { - return new Date(); - } - // messages_aggregate(cached) { - // return { aggregate: { count: cached.aggregate.count + 1 } }; - // }, - } - }); - break; - case "messaging-mark-conversation-read": - let previousUnreadCount = 0; - client.cache.modify({ - id: client.cache.identify({ - __typename: "conversations", - id: payload.conversationid - }), - fields: { - messages_aggregate(cached) { - previousUnreadCount = cached.aggregate.count; - return { aggregate: { count: 0 } }; - } - } - }); - client.cache.modify({ - fields: { - messages_aggregate(cached) { - return { - aggregate: { - count: cached.aggregate.count - previousUnreadCount - } - }; - } - } - }); - break; - default: - console.log("No payload type set."); - break; - } + // switch (payload.type) { + // case "messaging-inbound": + // client.cache.modify({ + // id: client.cache.identify({ + // __typename: "conversations", + // id: payload.conversationid + // }), + // fields: { + // messages_aggregate(cached) { + // return { aggregate: { count: cached.aggregate.count + 1 } }; + // } + // } + // }); + // client.cache.modify({ + // fields: { + // messages_aggregate(cached) { + // return { aggregate: { count: cached.aggregate.count + 1 } }; + // } + // } + // }); + // break; + // case "messaging-outbound": + // client.cache.modify({ + // id: client.cache.identify({ + // __typename: "conversations", + // id: payload.conversationid + // }), + // fields: { + // updated_at(oldupdated0) { + // return new Date(); + // } + // // messages_aggregate(cached) { + // // return { aggregate: { count: cached.aggregate.count + 1 } }; + // // }, + // } + // }); + // break; + // case "messaging-mark-conversation-read": + // let previousUnreadCount = 0; + // client.cache.modify({ + // id: client.cache.identify({ + // __typename: "conversations", + // id: payload.conversationid + // }), + // fields: { + // messages_aggregate(cached) { + // previousUnreadCount = cached.aggregate.count; + // return { aggregate: { count: 0 } }; + // } + // } + // }); + // client.cache.modify({ + // fields: { + // messages_aggregate(cached) { + // return { + // aggregate: { + // count: cached.aggregate.count - previousUnreadCount + // } + // }; + // } + // } + // }); + // break; + // default: + // console.log("No payload type set."); + // break; + // } } diff --git a/client/vite.config.js b/client/vite.config.js index 03f56c538..7bb200e23 100644 --- a/client/vite.config.js +++ b/client/vite.config.js @@ -1,8 +1,5 @@ import react from "@vitejs/plugin-react"; import { promises as fsPromises } from "fs"; -import { createRequire } from "module"; -import * as path from "path"; -import * as url from "url"; import { createLogger, defineConfig } from "vite"; import { ViteEjsPlugin } from "vite-plugin-ejs"; import eslint from "vite-plugin-eslint"; @@ -18,28 +15,6 @@ process.env.VITE_APP_GIT_SHA_DATE = new Date().toLocaleString("en-US", { const getFormattedTimestamp = () => new Date().toLocaleTimeString("en-US", { hour12: true }).replace("AM", "a.m.").replace("PM", "p.m."); -/** This is a hack around react-virtualized, should be removed when switching to react-virtuoso */ -const WRONG_CODE = `import { bpfrpt_proptype_WindowScroller } from "../WindowScroller.js";`; - -function reactVirtualizedFix() { - return { - name: "flat:react-virtualized", - configResolved: async () => { - const require = createRequire(import.meta.url); - const reactVirtualizedPath = require.resolve("react-virtualized"); - const { pathname: reactVirtualizedFilePath } = new url.URL(reactVirtualizedPath, import.meta.url); - const file = reactVirtualizedFilePath.replace( - path.join("dist", "commonjs", "index.js"), - path.join("dist", "es", "WindowScroller", "utils", "onScroll.js") - ); - const code = await fsPromises.readFile(file, "utf-8"); - const modified = code.replace(WRONG_CODE, ""); - await fsPromises.writeFile(file, modified); - } - }; -} -/** End of hack */ - export const logger = createLogger("info", { allowClearScreen: false }); @@ -108,7 +83,6 @@ export default defineConfig({ gcm_sender_id: "103953800507" } }), - reactVirtualizedFix(), react(), eslint() ], diff --git a/package-lock.json b/package-lock.json index d95191db5..f1e6a5f31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,13 @@ "version": "0.2.0", "license": "UNLICENSED", "dependencies": { - "@aws-sdk/client-cloudwatch-logs": "^3.679.0", - "@aws-sdk/client-elasticache": "^3.675.0", - "@aws-sdk/client-s3": "^3.689.0", - "@aws-sdk/client-secrets-manager": "^3.675.0", - "@aws-sdk/client-ses": "^3.675.0", - "@aws-sdk/credential-provider-node": "^3.675.0", - "@opensearch-project/opensearch": "^2.12.0", + "@aws-sdk/client-cloudwatch-logs": "^3.693.0", + "@aws-sdk/client-elasticache": "^3.693.0", + "@aws-sdk/client-s3": "^3.693.0", + "@aws-sdk/client-secrets-manager": "^3.693.0", + "@aws-sdk/client-ses": "^3.693.0", + "@aws-sdk/credential-provider-node": "^3.693.0", + "@opensearch-project/opensearch": "^2.13.0", "@socket.io/admin-ui": "^0.5.1", "@socket.io/redis-adapter": "^8.3.0", "aws4": "^1.13.2", @@ -24,20 +24,20 @@ "bluebird": "^3.7.2", "body-parser": "^1.20.3", "canvas": "^2.11.2", - "chart.js": "^4.4.5", + "chart.js": "^4.4.6", "cloudinary": "^2.5.1", - "compression": "^1.7.4", + "compression": "^1.7.5", "cookie-parser": "^1.4.7", "cors": "2.8.5", "csrf": "^3.1.0", "dinero.js": "^1.9.1", "dotenv": "^16.4.5", "express": "^4.21.1", - "firebase-admin": "^12.6.0", + "firebase-admin": "^13.0.0", "graphql": "^16.9.0", "graphql-request": "^6.1.0", "inline-css": "^4.0.2", - "intuit-oauth": "^4.1.2", + "intuit-oauth": "^4.1.3", "ioredis": "^5.4.1", "json-2-csv": "^5.5.6", "lodash": "^4.17.21", @@ -46,18 +46,18 @@ "multer": "^1.4.5-lts.1", "node-mailjet": "^6.0.6", "node-persist": "^4.0.3", - "nodemailer": "^6.9.15", - "phone": "^3.1.51", + "nodemailer": "^6.9.16", + "phone": "^3.1.53", "recursive-diff": "^1.0.9", "redis": "^4.7.0", "rimraf": "^6.0.1", - "soap": "^1.1.5", - "socket.io": "^4.8.0", + "soap": "^1.1.6", + "socket.io": "^4.8.1", "socket.io-adapter": "^2.5.5", "ssh2-sftp-client": "^10.0.3", "twilio": "^4.23.0", "uuid": "^10.0.0", - "winston": "^3.15.0", + "winston": "^3.17.0", "winston-cloudwatch": "^6.3.0", "xml2js": "^0.6.2", "xmlbuilder2": "^3.1.1" @@ -266,53 +266,53 @@ } }, "node_modules/@aws-sdk/client-cloudwatch-logs": { - "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.679.0.tgz", - "integrity": "sha512-A1qTVNX8KdpqvXgULd4Suo88uuNWPa8DiuBL8Qkw/WefYT7TSWsOpwuVK0oFkMpCfB0rQN9fXZh2DBiaz8YmZg==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.693.0.tgz", + "integrity": "sha512-ZJIzTqRSQmvBjGSYeE38mUO0Y70ioBAbokrIqQZdY7N+hQNU6xPWgZBURWdkC+yLfjCpbN9mSR4MPc/w6Tu+LQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.679.0", - "@aws-sdk/client-sts": "3.679.0", - "@aws-sdk/core": "3.679.0", - "@aws-sdk/credential-provider-node": "3.679.0", - "@aws-sdk/middleware-host-header": "3.679.0", - "@aws-sdk/middleware-logger": "3.679.0", - "@aws-sdk/middleware-recursion-detection": "3.679.0", - "@aws-sdk/middleware-user-agent": "3.679.0", - "@aws-sdk/region-config-resolver": "3.679.0", - "@aws-sdk/types": "3.679.0", - "@aws-sdk/util-endpoints": "3.679.0", - "@aws-sdk/util-user-agent-browser": "3.679.0", - "@aws-sdk/util-user-agent-node": "3.679.0", - "@smithy/config-resolver": "^3.0.9", - "@smithy/core": "^2.4.8", - "@smithy/eventstream-serde-browser": "^3.0.10", - "@smithy/eventstream-serde-config-resolver": "^3.0.7", - "@smithy/eventstream-serde-node": "^3.0.9", - "@smithy/fetch-http-handler": "^3.2.9", - "@smithy/hash-node": "^3.0.7", - "@smithy/invalid-dependency": "^3.0.7", - "@smithy/middleware-content-length": "^3.0.9", - "@smithy/middleware-endpoint": "^3.1.4", - "@smithy/middleware-retry": "^3.0.23", - "@smithy/middleware-serde": "^3.0.7", - "@smithy/middleware-stack": "^3.0.7", - "@smithy/node-config-provider": "^3.1.8", - "@smithy/node-http-handler": "^3.2.4", - "@smithy/protocol-http": "^4.1.4", - "@smithy/smithy-client": "^3.4.0", - "@smithy/types": "^3.5.0", - "@smithy/url-parser": "^3.0.7", + "@aws-sdk/client-sso-oidc": "3.693.0", + "@aws-sdk/client-sts": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/eventstream-serde-browser": "^3.0.12", + "@smithy/eventstream-serde-config-resolver": "^3.0.9", + "@smithy/eventstream-serde-node": "^3.0.11", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.23", - "@smithy/util-defaults-mode-node": "^3.0.23", - "@smithy/util-endpoints": "^2.1.3", - "@smithy/util-middleware": "^3.0.7", - "@smithy/util-retry": "^3.0.7", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", "@smithy/util-utf8": "^3.0.0", "@types/uuid": "^9.0.1", "tslib": "^2.6.2", @@ -322,484 +322,6 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/client-sso": { - "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.679.0.tgz", - "integrity": "sha512-/0cAvYnpOZTo/Y961F1kx2fhDDLUYZ0SQQ5/75gh3xVImLj7Zw+vp74ieqFbqWLYGMaq8z1Arr9A8zG95mbLdg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.679.0", - "@aws-sdk/middleware-host-header": "3.679.0", - "@aws-sdk/middleware-logger": "3.679.0", - "@aws-sdk/middleware-recursion-detection": "3.679.0", - "@aws-sdk/middleware-user-agent": "3.679.0", - "@aws-sdk/region-config-resolver": "3.679.0", - "@aws-sdk/types": "3.679.0", - "@aws-sdk/util-endpoints": "3.679.0", - "@aws-sdk/util-user-agent-browser": "3.679.0", - "@aws-sdk/util-user-agent-node": "3.679.0", - "@smithy/config-resolver": "^3.0.9", - "@smithy/core": "^2.4.8", - "@smithy/fetch-http-handler": "^3.2.9", - "@smithy/hash-node": "^3.0.7", - "@smithy/invalid-dependency": "^3.0.7", - "@smithy/middleware-content-length": "^3.0.9", - "@smithy/middleware-endpoint": "^3.1.4", - "@smithy/middleware-retry": "^3.0.23", - "@smithy/middleware-serde": "^3.0.7", - "@smithy/middleware-stack": "^3.0.7", - "@smithy/node-config-provider": "^3.1.8", - "@smithy/node-http-handler": "^3.2.4", - "@smithy/protocol-http": "^4.1.4", - "@smithy/smithy-client": "^3.4.0", - "@smithy/types": "^3.5.0", - "@smithy/url-parser": "^3.0.7", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.23", - "@smithy/util-defaults-mode-node": "^3.0.23", - "@smithy/util-endpoints": "^2.1.3", - "@smithy/util-middleware": "^3.0.7", - "@smithy/util-retry": "^3.0.7", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.679.0.tgz", - "integrity": "sha512-/dBYWcCwbA/id4sFCIVZvf0UsvzHCC68SryxeNQk/PDkY9N4n5yRcMUkZDaEyQCjowc3kY4JOXp2AdUP037nhA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.679.0", - "@aws-sdk/credential-provider-node": "3.679.0", - "@aws-sdk/middleware-host-header": "3.679.0", - "@aws-sdk/middleware-logger": "3.679.0", - "@aws-sdk/middleware-recursion-detection": "3.679.0", - "@aws-sdk/middleware-user-agent": "3.679.0", - "@aws-sdk/region-config-resolver": "3.679.0", - "@aws-sdk/types": "3.679.0", - "@aws-sdk/util-endpoints": "3.679.0", - "@aws-sdk/util-user-agent-browser": "3.679.0", - "@aws-sdk/util-user-agent-node": "3.679.0", - "@smithy/config-resolver": "^3.0.9", - "@smithy/core": "^2.4.8", - "@smithy/fetch-http-handler": "^3.2.9", - "@smithy/hash-node": "^3.0.7", - "@smithy/invalid-dependency": "^3.0.7", - "@smithy/middleware-content-length": "^3.0.9", - "@smithy/middleware-endpoint": "^3.1.4", - "@smithy/middleware-retry": "^3.0.23", - "@smithy/middleware-serde": "^3.0.7", - "@smithy/middleware-stack": "^3.0.7", - "@smithy/node-config-provider": "^3.1.8", - "@smithy/node-http-handler": "^3.2.4", - "@smithy/protocol-http": "^4.1.4", - "@smithy/smithy-client": "^3.4.0", - "@smithy/types": "^3.5.0", - "@smithy/url-parser": "^3.0.7", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.23", - "@smithy/util-defaults-mode-node": "^3.0.23", - "@smithy/util-endpoints": "^2.1.3", - "@smithy/util-middleware": "^3.0.7", - "@smithy/util-retry": "^3.0.7", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.679.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/client-sts": { - "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.679.0.tgz", - "integrity": "sha512-3CvrT8w1RjFu1g8vKA5Azfr5V83r2/b68Ock43WE003Bq/5Y38mwmYX7vk0fPHzC3qejt4YMAWk/C3fSKOy25g==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.679.0", - "@aws-sdk/core": "3.679.0", - "@aws-sdk/credential-provider-node": "3.679.0", - "@aws-sdk/middleware-host-header": "3.679.0", - "@aws-sdk/middleware-logger": "3.679.0", - "@aws-sdk/middleware-recursion-detection": "3.679.0", - "@aws-sdk/middleware-user-agent": "3.679.0", - "@aws-sdk/region-config-resolver": "3.679.0", - "@aws-sdk/types": "3.679.0", - "@aws-sdk/util-endpoints": "3.679.0", - "@aws-sdk/util-user-agent-browser": "3.679.0", - "@aws-sdk/util-user-agent-node": "3.679.0", - "@smithy/config-resolver": "^3.0.9", - "@smithy/core": "^2.4.8", - "@smithy/fetch-http-handler": "^3.2.9", - "@smithy/hash-node": "^3.0.7", - "@smithy/invalid-dependency": "^3.0.7", - "@smithy/middleware-content-length": "^3.0.9", - "@smithy/middleware-endpoint": "^3.1.4", - "@smithy/middleware-retry": "^3.0.23", - "@smithy/middleware-serde": "^3.0.7", - "@smithy/middleware-stack": "^3.0.7", - "@smithy/node-config-provider": "^3.1.8", - "@smithy/node-http-handler": "^3.2.4", - "@smithy/protocol-http": "^4.1.4", - "@smithy/smithy-client": "^3.4.0", - "@smithy/types": "^3.5.0", - "@smithy/url-parser": "^3.0.7", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.23", - "@smithy/util-defaults-mode-node": "^3.0.23", - "@smithy/util-endpoints": "^2.1.3", - "@smithy/util-middleware": "^3.0.7", - "@smithy/util-retry": "^3.0.7", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/core": { - "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.679.0.tgz", - "integrity": "sha512-CS6PWGX8l4v/xyvX8RtXnBisdCa5+URzKd0L6GvHChype9qKUVxO/Gg6N/y43Hvg7MNWJt9FBPNWIxUB+byJwg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.679.0", - "@smithy/core": "^2.4.8", - "@smithy/node-config-provider": "^3.1.8", - "@smithy/property-provider": "^3.1.7", - "@smithy/protocol-http": "^4.1.4", - "@smithy/signature-v4": "^4.2.0", - "@smithy/smithy-client": "^3.4.0", - "@smithy/types": "^3.5.0", - "@smithy/util-middleware": "^3.0.7", - "fast-xml-parser": "4.4.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.679.0.tgz", - "integrity": "sha512-EdlTYbzMm3G7VUNAMxr9S1nC1qUNqhKlAxFU8E7cKsAe8Bp29CD5HAs3POc56AVo9GC4yRIS+/mtlZSmrckzUA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.679.0", - "@aws-sdk/types": "3.679.0", - "@smithy/property-provider": "^3.1.7", - "@smithy/types": "^3.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.679.0.tgz", - "integrity": "sha512-ZoKLubW5DqqV1/2a3TSn+9sSKg0T8SsYMt1JeirnuLJF0mCoYFUaWMyvxxKuxPoqvUsaycxKru4GkpJ10ltNBw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.679.0", - "@aws-sdk/types": "3.679.0", - "@smithy/fetch-http-handler": "^3.2.9", - "@smithy/node-http-handler": "^3.2.4", - "@smithy/property-provider": "^3.1.7", - "@smithy/protocol-http": "^4.1.4", - "@smithy/smithy-client": "^3.4.0", - "@smithy/types": "^3.5.0", - "@smithy/util-stream": "^3.1.9", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.679.0.tgz", - "integrity": "sha512-Rg7t8RwUzKcumpipG4neZqaeJ6DF+Bco1+FHn5BZB68jpvwvjBjcQUuWkxj18B6ctYHr1fkunnzeKEn/+vy7+w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.679.0", - "@aws-sdk/credential-provider-env": "3.679.0", - "@aws-sdk/credential-provider-http": "3.679.0", - "@aws-sdk/credential-provider-process": "3.679.0", - "@aws-sdk/credential-provider-sso": "3.679.0", - "@aws-sdk/credential-provider-web-identity": "3.679.0", - "@aws-sdk/types": "3.679.0", - "@smithy/credential-provider-imds": "^3.2.4", - "@smithy/property-provider": "^3.1.7", - "@smithy/shared-ini-file-loader": "^3.1.8", - "@smithy/types": "^3.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.679.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.679.0.tgz", - "integrity": "sha512-E3lBtaqCte8tWs6Rkssc8sLzvGoJ10TLGvpkijOlz43wPd6xCRh1YLwg6zolf9fVFtEyUs/GsgymiASOyxhFtw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.679.0", - "@aws-sdk/credential-provider-http": "3.679.0", - "@aws-sdk/credential-provider-ini": "3.679.0", - "@aws-sdk/credential-provider-process": "3.679.0", - "@aws-sdk/credential-provider-sso": "3.679.0", - "@aws-sdk/credential-provider-web-identity": "3.679.0", - "@aws-sdk/types": "3.679.0", - "@smithy/credential-provider-imds": "^3.2.4", - "@smithy/property-provider": "^3.1.7", - "@smithy/shared-ini-file-loader": "^3.1.8", - "@smithy/types": "^3.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.679.0.tgz", - "integrity": "sha512-u/p4TV8kQ0zJWDdZD4+vdQFTMhkDEJFws040Gm113VHa/Xo1SYOjbpvqeuFoz6VmM0bLvoOWjxB9MxnSQbwKpQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.679.0", - "@aws-sdk/types": "3.679.0", - "@smithy/property-provider": "^3.1.7", - "@smithy/shared-ini-file-loader": "^3.1.8", - "@smithy/types": "^3.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.679.0.tgz", - "integrity": "sha512-SAtWonhi9asxn0ukEbcE81jkyanKgqpsrtskvYPpO9Z9KOednM4Cqt6h1bfcS9zaHjN2zu815Gv8O7WiV+F/DQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.679.0", - "@aws-sdk/core": "3.679.0", - "@aws-sdk/token-providers": "3.679.0", - "@aws-sdk/types": "3.679.0", - "@smithy/property-provider": "^3.1.7", - "@smithy/shared-ini-file-loader": "^3.1.8", - "@smithy/types": "^3.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.679.0.tgz", - "integrity": "sha512-a74tLccVznXCaBefWPSysUcLXYJiSkeUmQGtalNgJ1vGkE36W5l/8czFiiowdWdKWz7+x6xf0w+Kjkjlj42Ung==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.679.0", - "@aws-sdk/types": "3.679.0", - "@smithy/property-provider": "^3.1.7", - "@smithy/types": "^3.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.679.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.679.0.tgz", - "integrity": "sha512-y176HuQ8JRY3hGX8rQzHDSbCl9P5Ny9l16z4xmaiLo+Qfte7ee4Yr3yaAKd7GFoJ3/Mhud2XZ37fR015MfYl2w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.679.0", - "@smithy/protocol-http": "^4.1.4", - "@smithy/types": "^3.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/middleware-logger": { - "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.679.0.tgz", - "integrity": "sha512-0vet8InEj7nvIvGKk+ch7bEF5SyZ7Us9U7YTEgXPrBNStKeRUsgwRm0ijPWWd0a3oz2okaEwXsFl7G/vI0XiEA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.679.0", - "@smithy/types": "^3.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.679.0.tgz", - "integrity": "sha512-sQoAZFsQiW/LL3DfKMYwBoGjYDEnMbA9WslWN8xneCmBAwKo6IcSksvYs23PP8XMIoBGe2I2J9BSr654XWygTQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.679.0", - "@smithy/protocol-http": "^4.1.4", - "@smithy/types": "^3.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.679.0.tgz", - "integrity": "sha512-4hdeXhPDURPqQLPd9jCpUEo9fQITXl3NM3W1MwcJpE0gdUM36uXkQOYsTPeeU/IRCLVjK8Htlh2oCaM9iJrLCA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.679.0", - "@aws-sdk/types": "3.679.0", - "@aws-sdk/util-endpoints": "3.679.0", - "@smithy/core": "^2.4.8", - "@smithy/protocol-http": "^4.1.4", - "@smithy/types": "^3.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.679.0.tgz", - "integrity": "sha512-Ybx54P8Tg6KKq5ck7uwdjiKif7n/8g1x+V0V9uTjBjRWqaIgiqzXwKWoPj6NCNkE7tJNtqI4JrNxp/3S3HvmRw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.679.0", - "@smithy/node-config-provider": "^3.1.8", - "@smithy/types": "^3.5.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.7", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/token-providers": { - "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.679.0.tgz", - "integrity": "sha512-1/+Zso/x2jqgutKixYFQEGli0FELTgah6bm7aB+m2FAWH4Hz7+iMUsazg6nSWm714sG9G3h5u42Dmpvi9X6/hA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.679.0", - "@smithy/property-provider": "^3.1.7", - "@smithy/shared-ini-file-loader": "^3.1.8", - "@smithy/types": "^3.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.679.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/types": { - "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.679.0.tgz", - "integrity": "sha512-NwVq8YvInxQdJ47+zz4fH3BRRLC6lL+WLkvr242PVBbUOLRyK/lkwHlfiKUoeVIMyK5NF+up6TRg71t/8Bny6Q==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^3.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/util-endpoints": { - "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.679.0.tgz", - "integrity": "sha512-YL6s4Y/1zC45OvddvgE139fjeWSKKPgLlnfrvhVL7alNyY9n7beR4uhoDpNrt5mI6sn9qiBF17790o+xLAXjjg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.679.0", - "@smithy/types": "^3.5.0", - "@smithy/util-endpoints": "^2.1.3", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.679.0.tgz", - "integrity": "sha512-CusSm2bTBG1kFypcsqU8COhnYc6zltobsqs3nRrvYqYaOqtMnuE46K4XTWpnzKgwDejgZGOE+WYyprtAxrPvmQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.679.0", - "@smithy/types": "^3.5.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.679.0.tgz", - "integrity": "sha512-Bw4uXZ+NU5ed6TNfo4tBbhBSW+2eQxXYjYBGl5gLUNUpg2pDFToQAP6rXBFiwcG52V2ny5oLGiD82SoYuYkAVg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.679.0", - "@aws-sdk/types": "3.679.0", - "@smithy/node-config-provider": "^3.1.8", - "@smithy/types": "^3.5.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -814,52 +336,52 @@ } }, "node_modules/@aws-sdk/client-elasticache": { - "version": "3.675.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-elasticache/-/client-elasticache-3.675.0.tgz", - "integrity": "sha512-OMdlxBrsrDdBw2/RHj80NSAbb4wQYR0TJMvSbuC/XYnF1W9OpNpNpzQ9ZdZwiLymkjQMgfrVk27FiEivEBwj1A==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-elasticache/-/client-elasticache-3.693.0.tgz", + "integrity": "sha512-ofdENKhzBcDjIRS6i7CDwvQetqTRn9v8BFFo1uqrmJVs5qLW6wuKDL7oI4ESXV2I1t9jJsobboKBj9naMxCciQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.675.0", - "@aws-sdk/client-sts": "3.675.0", - "@aws-sdk/core": "3.667.0", - "@aws-sdk/credential-provider-node": "3.675.0", - "@aws-sdk/middleware-host-header": "3.667.0", - "@aws-sdk/middleware-logger": "3.667.0", - "@aws-sdk/middleware-recursion-detection": "3.667.0", - "@aws-sdk/middleware-user-agent": "3.669.0", - "@aws-sdk/region-config-resolver": "3.667.0", - "@aws-sdk/types": "3.667.0", - "@aws-sdk/util-endpoints": "3.667.0", - "@aws-sdk/util-user-agent-browser": "3.675.0", - "@aws-sdk/util-user-agent-node": "3.669.0", - "@smithy/config-resolver": "^3.0.9", - "@smithy/core": "^2.4.8", - "@smithy/fetch-http-handler": "^3.2.9", - "@smithy/hash-node": "^3.0.7", - "@smithy/invalid-dependency": "^3.0.7", - "@smithy/middleware-content-length": "^3.0.9", - "@smithy/middleware-endpoint": "^3.1.4", - "@smithy/middleware-retry": "^3.0.23", - "@smithy/middleware-serde": "^3.0.7", - "@smithy/middleware-stack": "^3.0.7", - "@smithy/node-config-provider": "^3.1.8", - "@smithy/node-http-handler": "^3.2.4", - "@smithy/protocol-http": "^4.1.4", - "@smithy/smithy-client": "^3.4.0", - "@smithy/types": "^3.5.0", - "@smithy/url-parser": "^3.0.7", + "@aws-sdk/client-sso-oidc": "3.693.0", + "@aws-sdk/client-sts": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.23", - "@smithy/util-defaults-mode-node": "^3.0.23", - "@smithy/util-endpoints": "^2.1.3", - "@smithy/util-middleware": "^3.0.7", - "@smithy/util-retry": "^3.0.7", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", "@smithy/util-utf8": "^3.0.0", - "@smithy/util-waiter": "^3.1.6", + "@smithy/util-waiter": "^3.1.8", "tslib": "^2.6.2" }, "engines": { @@ -867,610 +389,119 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.689.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.689.0.tgz", - "integrity": "sha512-qYD1GJEPeLM6H3x8BuAAMXZltvVce5vGiwtZc9uMkBBo3HyFnmPitIPTPfaD1q8LOn/7KFdkY4MJ4e8D3YpV9g==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.693.0.tgz", + "integrity": "sha512-vgGI2e0Q6pzyhqfrSysi+sk/i+Nl+lMon67oqj/57RcCw9daL1/inpS+ADuwHpiPWkrg+U0bOXnmHjkLeTslJg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.687.0", - "@aws-sdk/client-sts": "3.687.0", - "@aws-sdk/core": "3.686.0", - "@aws-sdk/credential-provider-node": "3.687.0", - "@aws-sdk/middleware-bucket-endpoint": "3.686.0", - "@aws-sdk/middleware-expect-continue": "3.686.0", - "@aws-sdk/middleware-flexible-checksums": "3.689.0", - "@aws-sdk/middleware-host-header": "3.686.0", - "@aws-sdk/middleware-location-constraint": "3.686.0", - "@aws-sdk/middleware-logger": "3.686.0", - "@aws-sdk/middleware-recursion-detection": "3.686.0", - "@aws-sdk/middleware-sdk-s3": "3.687.0", - "@aws-sdk/middleware-ssec": "3.686.0", - "@aws-sdk/middleware-user-agent": "3.687.0", - "@aws-sdk/region-config-resolver": "3.686.0", - "@aws-sdk/signature-v4-multi-region": "3.687.0", - "@aws-sdk/types": "3.686.0", - "@aws-sdk/util-endpoints": "3.686.0", - "@aws-sdk/util-user-agent-browser": "3.686.0", - "@aws-sdk/util-user-agent-node": "3.687.0", - "@aws-sdk/xml-builder": "3.686.0", - "@smithy/config-resolver": "^3.0.10", - "@smithy/core": "^2.5.1", - "@smithy/eventstream-serde-browser": "^3.0.11", - "@smithy/eventstream-serde-config-resolver": "^3.0.8", - "@smithy/eventstream-serde-node": "^3.0.10", - "@smithy/fetch-http-handler": "^4.0.0", - "@smithy/hash-blob-browser": "^3.1.7", - "@smithy/hash-node": "^3.0.8", - "@smithy/hash-stream-node": "^3.1.7", - "@smithy/invalid-dependency": "^3.0.8", - "@smithy/md5-js": "^3.0.8", - "@smithy/middleware-content-length": "^3.0.10", - "@smithy/middleware-endpoint": "^3.2.1", - "@smithy/middleware-retry": "^3.0.25", - "@smithy/middleware-serde": "^3.0.8", - "@smithy/middleware-stack": "^3.0.8", - "@smithy/node-config-provider": "^3.1.9", - "@smithy/node-http-handler": "^3.2.5", - "@smithy/protocol-http": "^4.1.5", - "@smithy/smithy-client": "^3.4.2", - "@smithy/types": "^3.6.0", - "@smithy/url-parser": "^3.0.8", + "@aws-sdk/client-sso-oidc": "3.693.0", + "@aws-sdk/client-sts": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-bucket-endpoint": "3.693.0", + "@aws-sdk/middleware-expect-continue": "3.693.0", + "@aws-sdk/middleware-flexible-checksums": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-location-constraint": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-sdk-s3": "3.693.0", + "@aws-sdk/middleware-ssec": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/signature-v4-multi-region": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@aws-sdk/xml-builder": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/eventstream-serde-browser": "^3.0.12", + "@smithy/eventstream-serde-config-resolver": "^3.0.9", + "@smithy/eventstream-serde-node": "^3.0.11", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-blob-browser": "^3.1.8", + "@smithy/hash-node": "^3.0.9", + "@smithy/hash-stream-node": "^3.1.8", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/md5-js": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.25", - "@smithy/util-defaults-mode-node": "^3.0.25", - "@smithy/util-endpoints": "^2.1.4", - "@smithy/util-middleware": "^3.0.8", - "@smithy/util-retry": "^3.0.8", - "@smithy/util-stream": "^3.2.1", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-stream": "^3.3.0", "@smithy/util-utf8": "^3.0.0", - "@smithy/util-waiter": "^3.1.7", + "@smithy/util-waiter": "^3.1.8", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso": { - "version": "3.687.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.687.0.tgz", - "integrity": "sha512-dfj0y9fQyX4kFill/ZG0BqBTLQILKlL7+O5M4F9xlsh2WNuV2St6WtcOg14Y1j5UODPJiJs//pO+mD1lihT5Kw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.686.0", - "@aws-sdk/middleware-host-header": "3.686.0", - "@aws-sdk/middleware-logger": "3.686.0", - "@aws-sdk/middleware-recursion-detection": "3.686.0", - "@aws-sdk/middleware-user-agent": "3.687.0", - "@aws-sdk/region-config-resolver": "3.686.0", - "@aws-sdk/types": "3.686.0", - "@aws-sdk/util-endpoints": "3.686.0", - "@aws-sdk/util-user-agent-browser": "3.686.0", - "@aws-sdk/util-user-agent-node": "3.687.0", - "@smithy/config-resolver": "^3.0.10", - "@smithy/core": "^2.5.1", - "@smithy/fetch-http-handler": "^4.0.0", - "@smithy/hash-node": "^3.0.8", - "@smithy/invalid-dependency": "^3.0.8", - "@smithy/middleware-content-length": "^3.0.10", - "@smithy/middleware-endpoint": "^3.2.1", - "@smithy/middleware-retry": "^3.0.25", - "@smithy/middleware-serde": "^3.0.8", - "@smithy/middleware-stack": "^3.0.8", - "@smithy/node-config-provider": "^3.1.9", - "@smithy/node-http-handler": "^3.2.5", - "@smithy/protocol-http": "^4.1.5", - "@smithy/smithy-client": "^3.4.2", - "@smithy/types": "^3.6.0", - "@smithy/url-parser": "^3.0.8", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.25", - "@smithy/util-defaults-mode-node": "^3.0.25", - "@smithy/util-endpoints": "^2.1.4", - "@smithy/util-middleware": "^3.0.8", - "@smithy/util-retry": "^3.0.8", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.687.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.687.0.tgz", - "integrity": "sha512-Rdd8kLeTeh+L5ZuG4WQnWgYgdv7NorytKdZsGjiag1D8Wv3PcJvPqqWdgnI0Og717BSXVoaTYaN34FyqFYSx6Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.686.0", - "@aws-sdk/credential-provider-node": "3.687.0", - "@aws-sdk/middleware-host-header": "3.686.0", - "@aws-sdk/middleware-logger": "3.686.0", - "@aws-sdk/middleware-recursion-detection": "3.686.0", - "@aws-sdk/middleware-user-agent": "3.687.0", - "@aws-sdk/region-config-resolver": "3.686.0", - "@aws-sdk/types": "3.686.0", - "@aws-sdk/util-endpoints": "3.686.0", - "@aws-sdk/util-user-agent-browser": "3.686.0", - "@aws-sdk/util-user-agent-node": "3.687.0", - "@smithy/config-resolver": "^3.0.10", - "@smithy/core": "^2.5.1", - "@smithy/fetch-http-handler": "^4.0.0", - "@smithy/hash-node": "^3.0.8", - "@smithy/invalid-dependency": "^3.0.8", - "@smithy/middleware-content-length": "^3.0.10", - "@smithy/middleware-endpoint": "^3.2.1", - "@smithy/middleware-retry": "^3.0.25", - "@smithy/middleware-serde": "^3.0.8", - "@smithy/middleware-stack": "^3.0.8", - "@smithy/node-config-provider": "^3.1.9", - "@smithy/node-http-handler": "^3.2.5", - "@smithy/protocol-http": "^4.1.5", - "@smithy/smithy-client": "^3.4.2", - "@smithy/types": "^3.6.0", - "@smithy/url-parser": "^3.0.8", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.25", - "@smithy/util-defaults-mode-node": "^3.0.25", - "@smithy/util-endpoints": "^2.1.4", - "@smithy/util-middleware": "^3.0.8", - "@smithy/util-retry": "^3.0.8", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.687.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sts": { - "version": "3.687.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.687.0.tgz", - "integrity": "sha512-SQjDH8O4XCTtouuCVYggB0cCCrIaTzUZIkgJUpOsIEJBLlTbNOb/BZqUShAQw2o9vxr2rCeOGjAQOYPysW/Pmg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.687.0", - "@aws-sdk/core": "3.686.0", - "@aws-sdk/credential-provider-node": "3.687.0", - "@aws-sdk/middleware-host-header": "3.686.0", - "@aws-sdk/middleware-logger": "3.686.0", - "@aws-sdk/middleware-recursion-detection": "3.686.0", - "@aws-sdk/middleware-user-agent": "3.687.0", - "@aws-sdk/region-config-resolver": "3.686.0", - "@aws-sdk/types": "3.686.0", - "@aws-sdk/util-endpoints": "3.686.0", - "@aws-sdk/util-user-agent-browser": "3.686.0", - "@aws-sdk/util-user-agent-node": "3.687.0", - "@smithy/config-resolver": "^3.0.10", - "@smithy/core": "^2.5.1", - "@smithy/fetch-http-handler": "^4.0.0", - "@smithy/hash-node": "^3.0.8", - "@smithy/invalid-dependency": "^3.0.8", - "@smithy/middleware-content-length": "^3.0.10", - "@smithy/middleware-endpoint": "^3.2.1", - "@smithy/middleware-retry": "^3.0.25", - "@smithy/middleware-serde": "^3.0.8", - "@smithy/middleware-stack": "^3.0.8", - "@smithy/node-config-provider": "^3.1.9", - "@smithy/node-http-handler": "^3.2.5", - "@smithy/protocol-http": "^4.1.5", - "@smithy/smithy-client": "^3.4.2", - "@smithy/types": "^3.6.0", - "@smithy/url-parser": "^3.0.8", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.25", - "@smithy/util-defaults-mode-node": "^3.0.25", - "@smithy/util-endpoints": "^2.1.4", - "@smithy/util-middleware": "^3.0.8", - "@smithy/util-retry": "^3.0.8", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/core": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.686.0.tgz", - "integrity": "sha512-Xt3DV4DnAT3v2WURwzTxWQK34Ew+iiLzoUoguvLaZrVMFOqMMrwVjP+sizqIaHp1j7rGmFcN5I8saXnsDLuQLA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.686.0", - "@smithy/core": "^2.5.1", - "@smithy/node-config-provider": "^3.1.9", - "@smithy/property-provider": "^3.1.7", - "@smithy/protocol-http": "^4.1.5", - "@smithy/signature-v4": "^4.2.0", - "@smithy/smithy-client": "^3.4.2", - "@smithy/types": "^3.6.0", - "@smithy/util-middleware": "^3.0.8", - "fast-xml-parser": "4.4.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.686.0.tgz", - "integrity": "sha512-osD7lPO8OREkgxPiTWmA1i6XEmOth1uW9HWWj/+A2YGCj1G/t2sHu931w4Qj9NWHYZtbTTXQYVRg+TErALV7nQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.686.0", - "@aws-sdk/types": "3.686.0", - "@smithy/property-provider": "^3.1.7", - "@smithy/types": "^3.6.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.686.0.tgz", - "integrity": "sha512-xyGAD/f3vR/wssUiZrNFWQWXZvI4zRm2wpHhoHA1cC2fbRMNFYtFn365yw6dU7l00ZLcdFB1H119AYIUZS7xbw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.686.0", - "@aws-sdk/types": "3.686.0", - "@smithy/fetch-http-handler": "^4.0.0", - "@smithy/node-http-handler": "^3.2.5", - "@smithy/property-provider": "^3.1.7", - "@smithy/protocol-http": "^4.1.5", - "@smithy/smithy-client": "^3.4.2", - "@smithy/types": "^3.6.0", - "@smithy/util-stream": "^3.2.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.687.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.687.0.tgz", - "integrity": "sha512-6d5ZJeZch+ZosJccksN0PuXv7OSnYEmanGCnbhUqmUSz9uaVX6knZZfHCZJRgNcfSqg9QC0zsFA/51W5HCUqSQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.686.0", - "@aws-sdk/credential-provider-env": "3.686.0", - "@aws-sdk/credential-provider-http": "3.686.0", - "@aws-sdk/credential-provider-process": "3.686.0", - "@aws-sdk/credential-provider-sso": "3.687.0", - "@aws-sdk/credential-provider-web-identity": "3.686.0", - "@aws-sdk/types": "3.686.0", - "@smithy/credential-provider-imds": "^3.2.4", - "@smithy/property-provider": "^3.1.7", - "@smithy/shared-ini-file-loader": "^3.1.8", - "@smithy/types": "^3.6.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.687.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.687.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.687.0.tgz", - "integrity": "sha512-Pqld8Nx11NYaBUrVk3bYiGGpLCxkz8iTONlpQWoVWFhSOzlO7zloNOaYbD2XgFjjqhjlKzE91drs/f41uGeCTA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.686.0", - "@aws-sdk/credential-provider-http": "3.686.0", - "@aws-sdk/credential-provider-ini": "3.687.0", - "@aws-sdk/credential-provider-process": "3.686.0", - "@aws-sdk/credential-provider-sso": "3.687.0", - "@aws-sdk/credential-provider-web-identity": "3.686.0", - "@aws-sdk/types": "3.686.0", - "@smithy/credential-provider-imds": "^3.2.4", - "@smithy/property-provider": "^3.1.7", - "@smithy/shared-ini-file-loader": "^3.1.8", - "@smithy/types": "^3.6.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.686.0.tgz", - "integrity": "sha512-sXqaAgyzMOc+dm4CnzAR5Q6S9OWVHyZjLfW6IQkmGjqeQXmZl24c4E82+w64C+CTkJrFLzH1VNOYp1Hy5gE6Qw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.686.0", - "@aws-sdk/types": "3.686.0", - "@smithy/property-provider": "^3.1.7", - "@smithy/shared-ini-file-loader": "^3.1.8", - "@smithy/types": "^3.6.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.687.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.687.0.tgz", - "integrity": "sha512-N1YCoE7DovIRF2ReyRrA4PZzF0WNi4ObPwdQQkVxhvSm7PwjbWxrfq7rpYB+6YB1Uq3QPzgVwUFONE36rdpxUQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.687.0", - "@aws-sdk/core": "3.686.0", - "@aws-sdk/token-providers": "3.686.0", - "@aws-sdk/types": "3.686.0", - "@smithy/property-provider": "^3.1.7", - "@smithy/shared-ini-file-loader": "^3.1.8", - "@smithy/types": "^3.6.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.686.0.tgz", - "integrity": "sha512-40UqCpPxyHCXDP7CGd9JIOZDgDZf+u1OyLaGBpjQJlz1HYuEsIWnnbTe29Yg3Ah/Zc3g4NBWcUdlGVotlnpnDg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.686.0", - "@aws-sdk/types": "3.686.0", - "@smithy/property-provider": "^3.1.7", - "@smithy/types": "^3.6.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.686.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.686.0.tgz", - "integrity": "sha512-+Yc6rO02z+yhFbHmRZGvEw1vmzf/ifS9a4aBjJGeVVU+ZxaUvnk+IUZWrj4YQopUQ+bSujmMUzJLXSkbDq7yuw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.686.0", - "@smithy/protocol-http": "^4.1.5", - "@smithy/types": "^3.6.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-logger": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.686.0.tgz", - "integrity": "sha512-cX43ODfA2+SPdX7VRxu6gXk4t4bdVJ9pkktbfnkE5t27OlwNfvSGGhnHrQL8xTOFeyQ+3T+oowf26gf1OI+vIg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.686.0", - "@smithy/types": "^3.6.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.686.0.tgz", - "integrity": "sha512-jF9hQ162xLgp9zZ/3w5RUNhmwVnXDBlABEUX8jCgzaFpaa742qR/KKtjjZQ6jMbQnP+8fOCSXFAVNMU+s6v81w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.686.0", - "@smithy/protocol-http": "^4.1.5", - "@smithy/types": "^3.6.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.687.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.687.0.tgz", - "integrity": "sha512-nUgsKiEinyA50CaDXojAkOasAU3Apdg7Qox6IjNUC4ZjgOu7QWsCDB5N28AYMUt06cNYeYQdfMX1aEzG85a1Mg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.686.0", - "@aws-sdk/types": "3.686.0", - "@aws-sdk/util-endpoints": "3.686.0", - "@smithy/core": "^2.5.1", - "@smithy/protocol-http": "^4.1.5", - "@smithy/types": "^3.6.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.686.0.tgz", - "integrity": "sha512-6zXD3bSD8tcsMAVVwO1gO7rI1uy2fCD3czgawuPGPopeLiPpo6/3FoUWCQzk2nvEhj7p9Z4BbjwZGSlRkVrXTw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.686.0", - "@smithy/node-config-provider": "^3.1.9", - "@smithy/types": "^3.6.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/token-providers": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.686.0.tgz", - "integrity": "sha512-9oL4kTCSePFmyKPskibeiOXV6qavPZ63/kXM9Wh9V6dTSvBtLeNnMxqGvENGKJcTdIgtoqyqA6ET9u0PJ5IRIg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.686.0", - "@smithy/property-provider": "^3.1.7", - "@smithy/shared-ini-file-loader": "^3.1.8", - "@smithy/types": "^3.6.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.686.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/types": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.686.0.tgz", - "integrity": "sha512-xFnrb3wxOoJcW2Xrh63ZgFo5buIu9DF7bOHnwoUxHdNpUXicUh0AHw85TjXxyxIAd0d1psY/DU7QHoNI3OswgQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^3.6.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-endpoints": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.686.0.tgz", - "integrity": "sha512-7msZE2oYl+6QYeeRBjlDgxQUhq/XRky3cXE0FqLFs2muLS7XSuQEXkpOXB3R782ygAP6JX0kmBxPTLurRTikZg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.686.0", - "@smithy/types": "^3.6.0", - "@smithy/util-endpoints": "^2.1.4", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.686.0.tgz", - "integrity": "sha512-YiQXeGYZegF1b7B2GOR61orhgv79qmI0z7+Agm3NXLO6hGfVV3kFUJbXnjtH1BgWo5hbZYW7HQ2omGb3dnb6Lg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.686.0", - "@smithy/types": "^3.6.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.687.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.687.0.tgz", - "integrity": "sha512-idkP6ojSTZ4ek1pJ8wIN7r9U3KR5dn0IkJn3KQBXQ58LWjkRqLtft2vxzdsktWwhPKjjmIKl1S0kbvqLawf8XQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.687.0", - "@aws-sdk/types": "3.686.0", - "@smithy/node-config-provider": "^3.1.9", - "@smithy/types": "^3.6.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@smithy/fetch-http-handler": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.0.0.tgz", - "integrity": "sha512-MLb1f5tbBO2X6K4lMEKJvxeLooyg7guq48C2zKr4qM7F2Gpkz4dc+hdSgu77pCJ76jVqFBjZczHYAs6dp15N+g==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^4.1.5", - "@smithy/querystring-builder": "^3.0.8", - "@smithy/types": "^3.6.0", - "@smithy/util-base64": "^3.0.0", - "tslib": "^2.6.2" - } - }, "node_modules/@aws-sdk/client-secrets-manager": { - "version": "3.675.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.675.0.tgz", - "integrity": "sha512-qC9e56BzlAbKOtvAfbRuGCNDkGjFLi856SeYQ1U9kpegd6+yrFNScKUCJHEZ/clX1zfGPaJCpbEwCtiEaayADw==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.693.0.tgz", + "integrity": "sha512-PiXkl64LYhwZQ2zPQhxwpnLwGS7Lw8asFCj29SxEaYRnYra3ajE5d+Yvv68qC+diUNkeZh6k6zn7nEOZ4rWEwA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.675.0", - "@aws-sdk/client-sts": "3.675.0", - "@aws-sdk/core": "3.667.0", - "@aws-sdk/credential-provider-node": "3.675.0", - "@aws-sdk/middleware-host-header": "3.667.0", - "@aws-sdk/middleware-logger": "3.667.0", - "@aws-sdk/middleware-recursion-detection": "3.667.0", - "@aws-sdk/middleware-user-agent": "3.669.0", - "@aws-sdk/region-config-resolver": "3.667.0", - "@aws-sdk/types": "3.667.0", - "@aws-sdk/util-endpoints": "3.667.0", - "@aws-sdk/util-user-agent-browser": "3.675.0", - "@aws-sdk/util-user-agent-node": "3.669.0", - "@smithy/config-resolver": "^3.0.9", - "@smithy/core": "^2.4.8", - "@smithy/fetch-http-handler": "^3.2.9", - "@smithy/hash-node": "^3.0.7", - "@smithy/invalid-dependency": "^3.0.7", - "@smithy/middleware-content-length": "^3.0.9", - "@smithy/middleware-endpoint": "^3.1.4", - "@smithy/middleware-retry": "^3.0.23", - "@smithy/middleware-serde": "^3.0.7", - "@smithy/middleware-stack": "^3.0.7", - "@smithy/node-config-provider": "^3.1.8", - "@smithy/node-http-handler": "^3.2.4", - "@smithy/protocol-http": "^4.1.4", - "@smithy/smithy-client": "^3.4.0", - "@smithy/types": "^3.5.0", - "@smithy/url-parser": "^3.0.7", + "@aws-sdk/client-sso-oidc": "3.693.0", + "@aws-sdk/client-sts": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.23", - "@smithy/util-defaults-mode-node": "^3.0.23", - "@smithy/util-endpoints": "^2.1.3", - "@smithy/util-middleware": "^3.0.7", - "@smithy/util-retry": "^3.0.7", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", "@smithy/util-utf8": "^3.0.0", "@types/uuid": "^9.0.1", "tslib": "^2.6.2", @@ -1493,52 +524,52 @@ } }, "node_modules/@aws-sdk/client-ses": { - "version": "3.675.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.675.0.tgz", - "integrity": "sha512-4/OyFFpHMIahDc063vk4viETLtNPjopcUpwmWMtV8rhOns8KjJ2b1tvpvV7lNYT53mUm+g3fhYok9McHFDeeMA==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.693.0.tgz", + "integrity": "sha512-5/bq6eqM/TEoMxzw2R1iD9cuOp5IABsc4wnabN6QwuOqXM0NVC0VopwWh+QKkUU+uzoQV4imLK8AlJ3rLSThfg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.675.0", - "@aws-sdk/client-sts": "3.675.0", - "@aws-sdk/core": "3.667.0", - "@aws-sdk/credential-provider-node": "3.675.0", - "@aws-sdk/middleware-host-header": "3.667.0", - "@aws-sdk/middleware-logger": "3.667.0", - "@aws-sdk/middleware-recursion-detection": "3.667.0", - "@aws-sdk/middleware-user-agent": "3.669.0", - "@aws-sdk/region-config-resolver": "3.667.0", - "@aws-sdk/types": "3.667.0", - "@aws-sdk/util-endpoints": "3.667.0", - "@aws-sdk/util-user-agent-browser": "3.675.0", - "@aws-sdk/util-user-agent-node": "3.669.0", - "@smithy/config-resolver": "^3.0.9", - "@smithy/core": "^2.4.8", - "@smithy/fetch-http-handler": "^3.2.9", - "@smithy/hash-node": "^3.0.7", - "@smithy/invalid-dependency": "^3.0.7", - "@smithy/middleware-content-length": "^3.0.9", - "@smithy/middleware-endpoint": "^3.1.4", - "@smithy/middleware-retry": "^3.0.23", - "@smithy/middleware-serde": "^3.0.7", - "@smithy/middleware-stack": "^3.0.7", - "@smithy/node-config-provider": "^3.1.8", - "@smithy/node-http-handler": "^3.2.4", - "@smithy/protocol-http": "^4.1.4", - "@smithy/smithy-client": "^3.4.0", - "@smithy/types": "^3.5.0", - "@smithy/url-parser": "^3.0.7", + "@aws-sdk/client-sso-oidc": "3.693.0", + "@aws-sdk/client-sts": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.23", - "@smithy/util-defaults-mode-node": "^3.0.23", - "@smithy/util-endpoints": "^2.1.3", - "@smithy/util-middleware": "^3.0.7", - "@smithy/util-retry": "^3.0.7", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", "@smithy/util-utf8": "^3.0.0", - "@smithy/util-waiter": "^3.1.6", + "@smithy/util-waiter": "^3.1.8", "tslib": "^2.6.2" }, "engines": { @@ -1546,47 +577,47 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.675.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.675.0.tgz", - "integrity": "sha512-2goBCEr4acZJ1YJ69eWPTsIfZUbO7enog+lBA5kZShDiwovqzwYSHSlf6OGz4ETs2xT1n7n+QfKY0p+TluTfEw==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.693.0.tgz", + "integrity": "sha512-QEynrBC26x6TG9ZMzApR/kZ3lmt4lEIs2D+cHuDxt6fDGzahBUsQFBwJqhizzsM97JJI5YvmJhmihoYjdSSaXA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.667.0", - "@aws-sdk/middleware-host-header": "3.667.0", - "@aws-sdk/middleware-logger": "3.667.0", - "@aws-sdk/middleware-recursion-detection": "3.667.0", - "@aws-sdk/middleware-user-agent": "3.669.0", - "@aws-sdk/region-config-resolver": "3.667.0", - "@aws-sdk/types": "3.667.0", - "@aws-sdk/util-endpoints": "3.667.0", - "@aws-sdk/util-user-agent-browser": "3.675.0", - "@aws-sdk/util-user-agent-node": "3.669.0", - "@smithy/config-resolver": "^3.0.9", - "@smithy/core": "^2.4.8", - "@smithy/fetch-http-handler": "^3.2.9", - "@smithy/hash-node": "^3.0.7", - "@smithy/invalid-dependency": "^3.0.7", - "@smithy/middleware-content-length": "^3.0.9", - "@smithy/middleware-endpoint": "^3.1.4", - "@smithy/middleware-retry": "^3.0.23", - "@smithy/middleware-serde": "^3.0.7", - "@smithy/middleware-stack": "^3.0.7", - "@smithy/node-config-provider": "^3.1.8", - "@smithy/node-http-handler": "^3.2.4", - "@smithy/protocol-http": "^4.1.4", - "@smithy/smithy-client": "^3.4.0", - "@smithy/types": "^3.5.0", - "@smithy/url-parser": "^3.0.7", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.23", - "@smithy/util-defaults-mode-node": "^3.0.23", - "@smithy/util-endpoints": "^2.1.3", - "@smithy/util-middleware": "^3.0.7", - "@smithy/util-retry": "^3.0.7", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, @@ -1595,48 +626,48 @@ } }, "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.675.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.675.0.tgz", - "integrity": "sha512-4kEcaa2P/BFz+xy5tagbtzM08gbjHXyYqW+n6SJuUFK7N6bZNnA4cu1hVgHcqOqk8Dbwv7fiseGT0x3Hhqjwqg==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.693.0.tgz", + "integrity": "sha512-UEDbYlYtK/e86OOMyFR4zEPyenIxDzO2DRdz3fwVW7RzZ94wfmSwBh/8skzPTuY1G7sI064cjHW0b0QG01Sdtg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.667.0", - "@aws-sdk/credential-provider-node": "3.675.0", - "@aws-sdk/middleware-host-header": "3.667.0", - "@aws-sdk/middleware-logger": "3.667.0", - "@aws-sdk/middleware-recursion-detection": "3.667.0", - "@aws-sdk/middleware-user-agent": "3.669.0", - "@aws-sdk/region-config-resolver": "3.667.0", - "@aws-sdk/types": "3.667.0", - "@aws-sdk/util-endpoints": "3.667.0", - "@aws-sdk/util-user-agent-browser": "3.675.0", - "@aws-sdk/util-user-agent-node": "3.669.0", - "@smithy/config-resolver": "^3.0.9", - "@smithy/core": "^2.4.8", - "@smithy/fetch-http-handler": "^3.2.9", - "@smithy/hash-node": "^3.0.7", - "@smithy/invalid-dependency": "^3.0.7", - "@smithy/middleware-content-length": "^3.0.9", - "@smithy/middleware-endpoint": "^3.1.4", - "@smithy/middleware-retry": "^3.0.23", - "@smithy/middleware-serde": "^3.0.7", - "@smithy/middleware-stack": "^3.0.7", - "@smithy/node-config-provider": "^3.1.8", - "@smithy/node-http-handler": "^3.2.4", - "@smithy/protocol-http": "^4.1.4", - "@smithy/smithy-client": "^3.4.0", - "@smithy/types": "^3.5.0", - "@smithy/url-parser": "^3.0.7", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.23", - "@smithy/util-defaults-mode-node": "^3.0.23", - "@smithy/util-endpoints": "^2.1.3", - "@smithy/util-middleware": "^3.0.7", - "@smithy/util-retry": "^3.0.7", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, @@ -1644,53 +675,53 @@ "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-sts": "^3.675.0" + "@aws-sdk/client-sts": "^3.693.0" } }, "node_modules/@aws-sdk/client-sts": { - "version": "3.675.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.675.0.tgz", - "integrity": "sha512-zgjyR4GyuONeDGJBKNt9lFJ8HfDX7rpxZZVR7LSXr9lUkjf6vUGgD2k/K4UAoOTWCKKCor6TA562ezGlA8su6Q==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.693.0.tgz", + "integrity": "sha512-4S2y7VEtvdnjJX4JPl4kDQlslxXEZFnC50/UXVUYSt/AMc5A/GgspFNA5FVz4E3Gwpfobbf23hR2NBF8AGvYoQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.675.0", - "@aws-sdk/core": "3.667.0", - "@aws-sdk/credential-provider-node": "3.675.0", - "@aws-sdk/middleware-host-header": "3.667.0", - "@aws-sdk/middleware-logger": "3.667.0", - "@aws-sdk/middleware-recursion-detection": "3.667.0", - "@aws-sdk/middleware-user-agent": "3.669.0", - "@aws-sdk/region-config-resolver": "3.667.0", - "@aws-sdk/types": "3.667.0", - "@aws-sdk/util-endpoints": "3.667.0", - "@aws-sdk/util-user-agent-browser": "3.675.0", - "@aws-sdk/util-user-agent-node": "3.669.0", - "@smithy/config-resolver": "^3.0.9", - "@smithy/core": "^2.4.8", - "@smithy/fetch-http-handler": "^3.2.9", - "@smithy/hash-node": "^3.0.7", - "@smithy/invalid-dependency": "^3.0.7", - "@smithy/middleware-content-length": "^3.0.9", - "@smithy/middleware-endpoint": "^3.1.4", - "@smithy/middleware-retry": "^3.0.23", - "@smithy/middleware-serde": "^3.0.7", - "@smithy/middleware-stack": "^3.0.7", - "@smithy/node-config-provider": "^3.1.8", - "@smithy/node-http-handler": "^3.2.4", - "@smithy/protocol-http": "^4.1.4", - "@smithy/smithy-client": "^3.4.0", - "@smithy/types": "^3.5.0", - "@smithy/url-parser": "^3.0.7", + "@aws-sdk/client-sso-oidc": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.23", - "@smithy/util-defaults-mode-node": "^3.0.23", - "@smithy/util-endpoints": "^2.1.3", - "@smithy/util-middleware": "^3.0.7", - "@smithy/util-retry": "^3.0.7", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, @@ -1699,20 +730,20 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.667.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.667.0.tgz", - "integrity": "sha512-pMcDVI7Tmdsc8R3sDv0Omj/4iRParGY+uJtAfF669WnZfDfaBQaix2Mq7+Mu08vdjqO9K3gicFvjk9S1VLmOKA==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.693.0.tgz", + "integrity": "sha512-v6Z/kWmLFqRLDPEwl9hJGhtTgIFHjZugSfF1Yqffdxf4n1AWgtHS7qSegakuMyN5pP4K2tvUD8qHJ+gGe2Bw2A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.667.0", - "@smithy/core": "^2.4.8", - "@smithy/node-config-provider": "^3.1.8", - "@smithy/property-provider": "^3.1.7", - "@smithy/protocol-http": "^4.1.4", - "@smithy/signature-v4": "^4.2.0", - "@smithy/smithy-client": "^3.4.0", - "@smithy/types": "^3.5.0", - "@smithy/util-middleware": "^3.0.7", + "@aws-sdk/types": "3.692.0", + "@smithy/core": "^2.5.2", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/signature-v4": "^4.2.2", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-middleware": "^3.0.9", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, @@ -1721,15 +752,15 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.667.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.667.0.tgz", - "integrity": "sha512-zZbrkkaPc54WXm+QAnpuv0LPNfsts0HPPd+oCECGs7IQRaFsGj187cwvPg9RMWDFZqpm64MdBDoA8OQHsqzYCw==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.693.0.tgz", + "integrity": "sha512-hMUZaRSF7+iBKZfBHNLihFs9zvpM1CB8MBOTnTp5NGCVkRYF3SB2LH+Kcippe0ats4qCyB1eEoyQX99rERp2iQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.667.0", - "@aws-sdk/types": "3.667.0", - "@smithy/property-provider": "^3.1.7", - "@smithy/types": "^3.5.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { @@ -1737,20 +768,20 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.667.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.667.0.tgz", - "integrity": "sha512-sjtybFfERZWiqTY7fswBxKQLvUkiCucOWyqh3IaPo/4nE1PXRnaZCVG0+kRBPrYIxWqiVwytvZzMJy8sVZcG0A==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.693.0.tgz", + "integrity": "sha512-sL8MvwNJU7ZpD7/d2VVb3by1GknIJUxzTIgYtVkDVA/ojo+KRQSSHxcj0EWWXF5DTSh2Tm+LrEug3y1ZyKHsDA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.667.0", - "@aws-sdk/types": "3.667.0", - "@smithy/fetch-http-handler": "^3.2.9", - "@smithy/node-http-handler": "^3.2.4", - "@smithy/property-provider": "^3.1.7", - "@smithy/protocol-http": "^4.1.4", - "@smithy/smithy-client": "^3.4.0", - "@smithy/types": "^3.5.0", - "@smithy/util-stream": "^3.1.9", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-stream": "^3.3.0", "tslib": "^2.6.2" }, "engines": { @@ -1758,48 +789,48 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.675.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.675.0.tgz", - "integrity": "sha512-kCBlC6grpbpCvgowk9T4JHZxJ88VfN0r77bDZClcadFRAKQ8UHyO02zhgFCfUdnU1lNv1mr3ngEcGN7XzJlYWA==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.693.0.tgz", + "integrity": "sha512-kvaa4mXhCCOuW7UQnBhYqYfgWmwy7WSBSDClutwSLPZvgrhYj2l16SD2lN4IfYdxARYMJJ1lFYp3/jJG/9Yk4Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.667.0", - "@aws-sdk/credential-provider-env": "3.667.0", - "@aws-sdk/credential-provider-http": "3.667.0", - "@aws-sdk/credential-provider-process": "3.667.0", - "@aws-sdk/credential-provider-sso": "3.675.0", - "@aws-sdk/credential-provider-web-identity": "3.667.0", - "@aws-sdk/types": "3.667.0", - "@smithy/credential-provider-imds": "^3.2.4", - "@smithy/property-provider": "^3.1.7", - "@smithy/shared-ini-file-loader": "^3.1.8", - "@smithy/types": "^3.5.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-env": "3.693.0", + "@aws-sdk/credential-provider-http": "3.693.0", + "@aws-sdk/credential-provider-process": "3.693.0", + "@aws-sdk/credential-provider-sso": "3.693.0", + "@aws-sdk/credential-provider-web-identity": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-sts": "^3.675.0" + "@aws-sdk/client-sts": "^3.693.0" } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.675.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.675.0.tgz", - "integrity": "sha512-VO1WVZCDmAYu4sY/6qIBzdm5vJTxLhWKJWvL5kVFfSe8WiNNoHlTqYYUK9vAm/JYpIgFLTefPbIc5W4MK7o6Pg==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.693.0.tgz", + "integrity": "sha512-42WMsBjTNnjYxYuM3qD/Nq+8b7UdMopUq5OduMDxoM3mFTV6PXMMnfI4Z1TNnR4tYRvPXAnuNltF6xmjKbSJRA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.667.0", - "@aws-sdk/credential-provider-http": "3.667.0", - "@aws-sdk/credential-provider-ini": "3.675.0", - "@aws-sdk/credential-provider-process": "3.667.0", - "@aws-sdk/credential-provider-sso": "3.675.0", - "@aws-sdk/credential-provider-web-identity": "3.667.0", - "@aws-sdk/types": "3.667.0", - "@smithy/credential-provider-imds": "^3.2.4", - "@smithy/property-provider": "^3.1.7", - "@smithy/shared-ini-file-loader": "^3.1.8", - "@smithy/types": "^3.5.0", + "@aws-sdk/credential-provider-env": "3.693.0", + "@aws-sdk/credential-provider-http": "3.693.0", + "@aws-sdk/credential-provider-ini": "3.693.0", + "@aws-sdk/credential-provider-process": "3.693.0", + "@aws-sdk/credential-provider-sso": "3.693.0", + "@aws-sdk/credential-provider-web-identity": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { @@ -1807,16 +838,16 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.667.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.667.0.tgz", - "integrity": "sha512-HZHnvop32fKgsNHkdhVaul7UzQ25sEc0j9yqA4bjhtbk0ECl42kj3f1pJ+ZU/YD9ut8lMJs/vVqiOdNThVdeBw==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.693.0.tgz", + "integrity": "sha512-cvxQkrTWHHjeHrPlj7EWXPnFSq8x7vMx+Zn1oTsMpCY445N9KuzjfJTkmNGwU2GT6rSZI9/0MM02aQvl5bBBTQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.667.0", - "@aws-sdk/types": "3.667.0", - "@smithy/property-provider": "^3.1.7", - "@smithy/shared-ini-file-loader": "^3.1.8", - "@smithy/types": "^3.5.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { @@ -1824,18 +855,18 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.675.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.675.0.tgz", - "integrity": "sha512-p/EE2c0ebSgRhg1Fe1OH2+xNl7j1P4DTc7kZy1mX1NJ72fkqnGgBuf1vk5J9RmiRpbauPNMlm+xohjkGS7iodA==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.693.0.tgz", + "integrity": "sha512-479UlJxY+BFjj3pJFYUNC0DCMrykuG7wBAXfsvZqQxKUa83DnH5Q1ID/N2hZLkxjGd4ZW0AC3lTOMxFelGzzpQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.675.0", - "@aws-sdk/core": "3.667.0", - "@aws-sdk/token-providers": "3.667.0", - "@aws-sdk/types": "3.667.0", - "@smithy/property-provider": "^3.1.7", - "@smithy/shared-ini-file-loader": "^3.1.8", - "@smithy/types": "^3.5.0", + "@aws-sdk/client-sso": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/token-providers": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { @@ -1843,35 +874,35 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.667.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.667.0.tgz", - "integrity": "sha512-t8CFlZMD/1p/8Cli3rvRiTJpjr/8BO64gw166AHgFZYSN2h95L2l1tcW0jpsc3PprA32nLg1iQVKYt4WGM4ugw==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.693.0.tgz", + "integrity": "sha512-8LB210Pr6VeCiSb2hIra+sAH4KUBLyGaN50axHtIgufVK8jbKIctTZcVY5TO9Se+1107TsruzeXS7VeqVdJfFA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.667.0", - "@aws-sdk/types": "3.667.0", - "@smithy/property-provider": "^3.1.7", - "@smithy/types": "^3.5.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-sts": "^3.667.0" + "@aws-sdk/client-sts": "^3.693.0" } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.686.0.tgz", - "integrity": "sha512-6qCoWI73/HDzQE745MHQUYz46cAQxHCgy1You8MZQX9vHAQwqBnkcsb2hGp7S6fnQY5bNsiZkMWVQ/LVd2MNjg==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.693.0.tgz", + "integrity": "sha512-cPIa+lxMYiFRHtxKfNIVSFGO6LSgZCk42pu3d7KGwD6hu6vXRD5B2/DD3rPcEH1zgl2j0Kx1oGAV7SRXKHSFag==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.686.0", - "@aws-sdk/util-arn-parser": "3.679.0", - "@smithy/node-config-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.5", - "@smithy/types": "^3.6.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-arn-parser": "3.693.0", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "@smithy/util-config-provider": "^3.0.0", "tslib": "^2.6.2" }, @@ -1879,41 +910,15 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@aws-sdk/types": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.686.0.tgz", - "integrity": "sha512-xFnrb3wxOoJcW2Xrh63ZgFo5buIu9DF7bOHnwoUxHdNpUXicUh0AHw85TjXxyxIAd0d1psY/DU7QHoNI3OswgQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^3.6.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.686.0.tgz", - "integrity": "sha512-5yYqIbyhLhH29vn4sHiTj7sU6GttvLMk3XwCmBXjo2k2j3zHqFUwh9RyFGF9VY6Z392Drf/E/cl+qOGypwULpg==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.693.0.tgz", + "integrity": "sha512-MuK/gsJWpHz6Tv0CqTCS+QNOxLa2RfPh1biVCu/uO3l7kA0TjQ/C+tfgKvLXeH103tuDrOVINK+bt2ENmI3SWg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.686.0", - "@smithy/protocol-http": "^4.1.5", - "@smithy/types": "^3.6.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/middleware-expect-continue/node_modules/@aws-sdk/types": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.686.0.tgz", - "integrity": "sha512-xFnrb3wxOoJcW2Xrh63ZgFo5buIu9DF7bOHnwoUxHdNpUXicUh0AHw85TjXxyxIAd0d1psY/DU7QHoNI3OswgQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^3.6.0", + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { @@ -1921,22 +926,22 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.689.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.689.0.tgz", - "integrity": "sha512-6VxMOf3mgmAgg6SMagwKj5pAe+putcx2F2odOAWviLcobFpdM/xK9vNry7p6kY+RDNmSlBvcji9wnU59fjV74Q==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.693.0.tgz", + "integrity": "sha512-xkS6zjuE11ob93H9t65kHzphXcUMnN2SmIm2wycUPg+hi8Q6DJA6U2p//6oXkrr9oHy1QvwtllRd7SAd63sFKQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "3.686.0", - "@aws-sdk/types": "3.686.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", "@smithy/is-array-buffer": "^3.0.0", - "@smithy/node-config-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.5", - "@smithy/types": "^3.6.0", - "@smithy/util-middleware": "^3.0.8", - "@smithy/util-stream": "^3.2.1", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-stream": "^3.3.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, @@ -1944,50 +949,15 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@aws-sdk/core": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.686.0.tgz", - "integrity": "sha512-Xt3DV4DnAT3v2WURwzTxWQK34Ew+iiLzoUoguvLaZrVMFOqMMrwVjP+sizqIaHp1j7rGmFcN5I8saXnsDLuQLA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.686.0", - "@smithy/core": "^2.5.1", - "@smithy/node-config-provider": "^3.1.9", - "@smithy/property-provider": "^3.1.7", - "@smithy/protocol-http": "^4.1.5", - "@smithy/signature-v4": "^4.2.0", - "@smithy/smithy-client": "^3.4.2", - "@smithy/types": "^3.6.0", - "@smithy/util-middleware": "^3.0.8", - "fast-xml-parser": "4.4.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@aws-sdk/types": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.686.0.tgz", - "integrity": "sha512-xFnrb3wxOoJcW2Xrh63ZgFo5buIu9DF7bOHnwoUxHdNpUXicUh0AHw85TjXxyxIAd0d1psY/DU7QHoNI3OswgQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^3.6.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.667.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.667.0.tgz", - "integrity": "sha512-Z7fIAMQnPegs7JjAQvlOeWXwpMRfegh5eCoIP6VLJIeR6DLfYKbP35JBtt98R6DXslrN2RsbTogjbxPEDQfw1w==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.693.0.tgz", + "integrity": "sha512-BCki6sAZ5jYwIN/t3ElCiwerHad69ipHwPsDCxJQyeiOnJ8HG+lEpnVIfrnI8A0fLQNSF3Gtx6ahfBpKiv1Oug==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.667.0", - "@smithy/protocol-http": "^4.1.4", - "@smithy/types": "^3.5.0", + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { @@ -1995,26 +965,13 @@ } }, "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.686.0.tgz", - "integrity": "sha512-pCLeZzt5zUGY3NbW4J/5x3kaHyJEji4yqtoQcUlJmkoEInhSxJ0OE8sTxAfyL3nIOF4yr6L2xdaLCqYgQT8Aog==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.693.0.tgz", + "integrity": "sha512-eDAExTZ9uNIP7vs2JCVCOuWJauGueisBSn+Ovt7UvvuEUp6KOIJqn8oFxWmyUQu2GvbG4OcaTLgbqD95YHTB0Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.686.0", - "@smithy/types": "^3.6.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/middleware-location-constraint/node_modules/@aws-sdk/types": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.686.0.tgz", - "integrity": "sha512-xFnrb3wxOoJcW2Xrh63ZgFo5buIu9DF7bOHnwoUxHdNpUXicUh0AHw85TjXxyxIAd0d1psY/DU7QHoNI3OswgQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^3.6.0", + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { @@ -2022,13 +979,13 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.667.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.667.0.tgz", - "integrity": "sha512-PtTRNpNm/5c746jRgZCNg4X9xEJIwggkGJrF0GP9AB1ANg4pc/sF2Fvn1NtqPe9wtQ2stunJprnm5WkCHN7QiA==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.693.0.tgz", + "integrity": "sha512-dXnXDPr+wIiJ1TLADACI1g9pkSB21KkMIko2u4CJ2JCBoxi5IqeTnVoa6YcC8GdFNVRl+PorZ3Zqfmf1EOTC6w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.667.0", - "@smithy/types": "^3.5.0", + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { @@ -2036,14 +993,14 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.667.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.667.0.tgz", - "integrity": "sha512-U5glWD3ehFohzpUpopLtmqAlDurGWo2wRGPNgi4SwhWU7UDt6LS7E/UvJjqC0CUrjlzOw+my2A+Ncf+fisMhxQ==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.693.0.tgz", + "integrity": "sha512-0LDmM+VxXp0u3rG0xQRWD/q6Ubi7G8I44tBPahevD5CaiDZTkmNTrVUf0VEJgVe0iCKBppACMBDkLB0/ETqkFw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.667.0", - "@smithy/protocol-http": "^4.1.4", - "@smithy/types": "^3.5.0", + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { @@ -2051,23 +1008,23 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.687.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.687.0.tgz", - "integrity": "sha512-YGHYqiyRiNNucmvLrfx3QxIkjSDWR/+cc72bn0lPvqFUQBRHZgmYQLxVYrVZSmRzzkH2FQ1HsZcXhOafLbq4vQ==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.693.0.tgz", + "integrity": "sha512-5A++RBjJ3guyq5pbYs+Oq5hMlA8CK2OWaHx09cxVfhHWl/RoaY8DXrft4gnhoUEBrrubyMw7r9j7RIMLvS58kg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.686.0", - "@aws-sdk/types": "3.686.0", - "@aws-sdk/util-arn-parser": "3.679.0", - "@smithy/core": "^2.5.1", - "@smithy/node-config-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.5", - "@smithy/signature-v4": "^4.2.0", - "@smithy/smithy-client": "^3.4.2", - "@smithy/types": "^3.6.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-arn-parser": "3.693.0", + "@smithy/core": "^2.5.2", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/protocol-http": "^4.1.6", + "@smithy/signature-v4": "^4.2.2", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.8", - "@smithy/util-stream": "^3.2.1", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-stream": "^3.3.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, @@ -2075,62 +1032,14 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@aws-sdk/core": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.686.0.tgz", - "integrity": "sha512-Xt3DV4DnAT3v2WURwzTxWQK34Ew+iiLzoUoguvLaZrVMFOqMMrwVjP+sizqIaHp1j7rGmFcN5I8saXnsDLuQLA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.686.0", - "@smithy/core": "^2.5.1", - "@smithy/node-config-provider": "^3.1.9", - "@smithy/property-provider": "^3.1.7", - "@smithy/protocol-http": "^4.1.5", - "@smithy/signature-v4": "^4.2.0", - "@smithy/smithy-client": "^3.4.2", - "@smithy/types": "^3.6.0", - "@smithy/util-middleware": "^3.0.8", - "fast-xml-parser": "4.4.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@aws-sdk/types": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.686.0.tgz", - "integrity": "sha512-xFnrb3wxOoJcW2Xrh63ZgFo5buIu9DF7bOHnwoUxHdNpUXicUh0AHw85TjXxyxIAd0d1psY/DU7QHoNI3OswgQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^3.6.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.686.0.tgz", - "integrity": "sha512-zJXml/CpVHFUdlGQqja87vNQ3rPB5SlDbfdwxlj1KBbjnRRwpBtxxmOlWRShg8lnVV6aIMGv95QmpIFy4ayqnQ==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.693.0.tgz", + "integrity": "sha512-Ro5vzI7SRgEeuoMk3fKqFjGv6mG4c7VsSCDwnkiasmafQFBTPvUIpgmu2FXMHqW/OthvoiOzpSrlJ9Bwlx2f8A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.686.0", - "@smithy/types": "^3.6.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/middleware-ssec/node_modules/@aws-sdk/types": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.686.0.tgz", - "integrity": "sha512-xFnrb3wxOoJcW2Xrh63ZgFo5buIu9DF7bOHnwoUxHdNpUXicUh0AHw85TjXxyxIAd0d1psY/DU7QHoNI3OswgQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^3.6.0", + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { @@ -2138,17 +1047,17 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.669.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.669.0.tgz", - "integrity": "sha512-K8ScPi45zjJrj5Y2gRqVsvKKQCQbvQBfYGcBw9ZOx9TTavH80bOCBjWg/GFnvs4f37tqVc1wMN2oGvcTF6HveQ==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.693.0.tgz", + "integrity": "sha512-/KUq/KEpFFbQmNmpp7SpAtFAdViquDfD2W0QcG07zYBfz9MwE2ig48ALynXm5sMpRmnG7sJXjdvPtTsSVPfkiw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.667.0", - "@aws-sdk/types": "3.667.0", - "@aws-sdk/util-endpoints": "3.667.0", - "@smithy/core": "^2.4.8", - "@smithy/protocol-http": "^4.1.4", - "@smithy/types": "^3.5.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@smithy/core": "^2.5.2", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { @@ -2156,16 +1065,16 @@ } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.667.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.667.0.tgz", - "integrity": "sha512-iNr+JhhA902JMKHG9IwT9YdaEx6KGl6vjAL5BRNeOjfj4cZYMog6Lz/IlfOAltMtT0w88DAHDEFrBd2uO0l2eg==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.693.0.tgz", + "integrity": "sha512-YLUkMsUY0GLW/nfwlZ69cy1u07EZRmsv8Z9m0qW317/EZaVx59hcvmcvb+W4bFqj5E8YImTjoGfE4cZ0F9mkyw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.667.0", - "@smithy/node-config-provider": "^3.1.8", - "@smithy/types": "^3.5.0", + "@aws-sdk/types": "3.692.0", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/types": "^3.7.0", "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.7", + "@smithy/util-middleware": "^3.0.9", "tslib": "^2.6.2" }, "engines": { @@ -2173,29 +1082,16 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.687.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.687.0.tgz", - "integrity": "sha512-vdOQHCRHJPX9mT8BM6xOseazHD6NodvHl9cyF5UjNtLn+gERRJEItIA9hf0hlt62odGD8Fqp+rFRuqdmbNkcNw==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.693.0.tgz", + "integrity": "sha512-s7zbbsoVIriTR4ZGaateKuTqz6ddpazAyHvjk7I9kd+NvGNPiuAI18UdbuiiRI6K5HuYKf1ah6mKWFGPG15/kQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.687.0", - "@aws-sdk/types": "3.686.0", - "@smithy/protocol-http": "^4.1.5", - "@smithy/signature-v4": "^4.2.0", - "@smithy/types": "^3.6.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@aws-sdk/types": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.686.0.tgz", - "integrity": "sha512-xFnrb3wxOoJcW2Xrh63ZgFo5buIu9DF7bOHnwoUxHdNpUXicUh0AHw85TjXxyxIAd0d1psY/DU7QHoNI3OswgQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^3.6.0", + "@aws-sdk/middleware-sdk-s3": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/signature-v4": "^4.2.2", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { @@ -2203,31 +1099,31 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.667.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.667.0.tgz", - "integrity": "sha512-ZecJlG8p6D4UTYlBHwOWX6nknVtw/OBJ3yPXTSajBjhUlj9lE2xvejI8gl4rqkyLXk7z3bki+KR4tATbMaM9yg==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.693.0.tgz", + "integrity": "sha512-nDBTJMk1l/YmFULGfRbToOA2wjf+FkQT4dMgYCv+V9uSYsMzQj8A7Tha2dz9yv4vnQgYaEiErQ8d7HVyXcVEoA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.667.0", - "@smithy/property-provider": "^3.1.7", - "@smithy/shared-ini-file-loader": "^3.1.8", - "@smithy/types": "^3.5.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.667.0" + "@aws-sdk/client-sso-oidc": "^3.693.0" } }, "node_modules/@aws-sdk/types": { - "version": "3.667.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.667.0.tgz", - "integrity": "sha512-gYq0xCsqFfQaSL/yT1Gl1vIUjtsg7d7RhnUfsXaHt8xTxOKRTdH9GjbesBjXOzgOvB0W0vfssfreSNGFlOOMJg==", + "version": "3.692.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.692.0.tgz", + "integrity": "sha512-RpNvzD7zMEhiKgmlxGzyXaEcg2khvM7wd5sSHVapOcrde1awQSOMGI4zKBQ+wy5TnDfrm170ROz/ERLYtrjPZA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.5.0", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { @@ -2235,9 +1131,9 @@ } }, "node_modules/@aws-sdk/util-arn-parser": { - "version": "3.679.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.679.0.tgz", - "integrity": "sha512-CwzEbU8R8rq9bqUFryO50RFBlkfufV9UfMArHPWlo+lmsC+NlSluHQALoj6Jkq3zf5ppn1CN0c1DDLrEqdQUXg==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.693.0.tgz", + "integrity": "sha512-WC8x6ca+NRrtpAH64rWu+ryDZI3HuLwlEr8EU6/dbC/pt+r/zC0PBoC15VEygUaBA+isppCikQpGyEDu0Yj7gQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -2247,14 +1143,14 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.667.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.667.0.tgz", - "integrity": "sha512-X22SYDAuQJWnkF1/q17pkX3nGw5XMD9YEUbmt87vUnRq7iyJ3JOpl6UKOBeUBaL838wA5yzdbinmCITJ/VZ1QA==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.693.0.tgz", + "integrity": "sha512-eo4F6DRQ/kxS3gxJpLRv+aDNy76DxQJL5B3DPzpr9Vkq0ygVoi4GT5oIZLVaAVIJmi6k5qq9dLsYZfWLUxJJSg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.667.0", - "@smithy/types": "^3.5.0", - "@smithy/util-endpoints": "^2.1.3", + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", + "@smithy/util-endpoints": "^2.1.5", "tslib": "^2.6.2" }, "engines": { @@ -2273,27 +1169,27 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.675.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.675.0.tgz", - "integrity": "sha512-HW4vGfRiX54RLcsYjLuAhcBBJ6lRVEZd7njfGpAwBB9s7BH8t48vrpYbyA5XbbqbTvXfYBnugQCUw9HWjEa1ww==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.693.0.tgz", + "integrity": "sha512-6EUfuKOujtddy18OLJUaXfKBgs+UcbZ6N/3QV4iOkubCUdeM1maIqs++B9bhCbWeaeF5ORizJw5FTwnyNjE/mw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.667.0", - "@smithy/types": "^3.5.0", + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.669.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.669.0.tgz", - "integrity": "sha512-9jxCYrgggy2xd44ZASqI7AMiRVaSiFp+06Kg8BQSU0ijKpBJlwcsqIS8pDT/n6LxuOw2eV5ipvM2C0r1iKzrGA==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.693.0.tgz", + "integrity": "sha512-td0OVX8m5ZKiXtecIDuzY3Y3UZIzvxEr57Hp21NOwieqKCG2UeyQWWeGPv0FQaU7dpTkvFmVNI+tx9iB8V/Nhg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.669.0", - "@aws-sdk/types": "3.667.0", - "@smithy/node-config-provider": "^3.1.8", - "@smithy/types": "^3.5.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { @@ -2309,12 +1205,12 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.686.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.686.0.tgz", - "integrity": "sha512-k0z5b5dkYSuOHY0AOZ4iyjcGBeVL9lWsQNF4+c+1oK3OW4fRWl/bNa1soMRMpangsHPzgyn/QkzuDbl7qR4qrw==", + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.693.0.tgz", + "integrity": "sha512-C/rPwJcqnV8VDr2/VtcQnymSpcfEEgH1Jm6V0VmfXNZFv4Qzf1eCS8nsec0gipYgZB+cBBjfXw5dAk6pJ8ubpw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { @@ -2742,87 +1638,113 @@ "license": "MIT" }, "node_modules/@firebase/app-check-interop-types": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.0.tgz", - "integrity": "sha512-xAxHPZPIgFXnI+vb4sbBjZcde7ZluzPPaSK7Lx3/nmuVk4TjZvnL8ONnkd4ERQKL8WePQySU+pRcWkh8rDf5Sg==" + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz", + "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==", + "license": "Apache-2.0" }, "node_modules/@firebase/app-types": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz", - "integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==" + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", + "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", + "license": "Apache-2.0" }, "node_modules/@firebase/auth-interop-types": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz", - "integrity": "sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg==" + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz", + "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==", + "license": "Apache-2.0" }, "node_modules/@firebase/component": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.4.tgz", - "integrity": "sha512-rLMyrXuO9jcAUCaQXCMjCMUsWrba5fzHlNK24xz5j2W6A/SRmK8mZJ/hn7V0fViLbxC0lPMtrK1eYzk6Fg03jA==", + "version": "0.6.11", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.11.tgz", + "integrity": "sha512-eQbeCgPukLgsKD0Kw5wQgsMDX5LeoI1MIrziNDjmc6XDq5ZQnuUymANQgAb2wp1tSF9zDSXyxJmIUXaKgN58Ug==", + "license": "Apache-2.0", "dependencies": { - "@firebase/util": "1.9.3", + "@firebase/util": "1.10.2", "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@firebase/database": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.2.tgz", - "integrity": "sha512-8X6NBJgUQzDz0xQVaCISoOLINKat594N2eBbMR3Mu/MH/ei4WM+aAMlsNzngF22eljXu1SILP5G3evkyvsG3Ng==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.10.tgz", + "integrity": "sha512-sWp2g92u7xT4BojGbTXZ80iaSIaL6GAL0pwvM0CO/hb0nHSnABAqsH7AhnWGsGvXuEvbPr7blZylPaR9J+GSuQ==", + "license": "Apache-2.0", "dependencies": { - "@firebase/app-check-interop-types": "0.3.0", - "@firebase/auth-interop-types": "0.2.1", - "@firebase/component": "0.6.4", - "@firebase/logger": "0.4.0", - "@firebase/util": "1.9.3", + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.6.11", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.10.2", "faye-websocket": "0.11.4", "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@firebase/database-compat": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-1.0.2.tgz", - "integrity": "sha512-09ryJnXDvuycsxn8aXBzLhBTuCos3HEnCOBWY6hosxfYlNCGnLvG8YMlbSAt5eNhf7/00B095AEfDsdrrLjxqA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.0.1.tgz", + "integrity": "sha512-IsFivOjdE1GrjTeKoBU/ZMenESKDXidFDzZzHBPQ/4P20ptGdrl3oLlWrV/QJqJ9lND4IidE3z4Xr5JyfUW1vg==", + "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.4", - "@firebase/database": "1.0.2", - "@firebase/database-types": "1.0.0", - "@firebase/logger": "0.4.0", - "@firebase/util": "1.9.3", + "@firebase/component": "0.6.11", + "@firebase/database": "1.0.10", + "@firebase/database-types": "1.0.7", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.10.2", "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@firebase/database-types": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.0.tgz", - "integrity": "sha512-SjnXStoE0Q56HcFgNQ+9SsmJc0c8TqGARdI/T44KXy+Ets3r6x/ivhQozT66bMnCEjJRywYoxNurRTMlZF8VNg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.7.tgz", + "integrity": "sha512-I7zcLfJXrM0WM+ksFmFdAMdlq/DFmpeMNa+/GNsLyFo5u/lX5zzkPzGe3srVWqaBQBY5KprylDGxOsP6ETfL0A==", + "license": "Apache-2.0", "dependencies": { - "@firebase/app-types": "0.9.0", - "@firebase/util": "1.9.3" + "@firebase/app-types": "0.9.3", + "@firebase/util": "1.10.2" } }, "node_modules/@firebase/logger": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.0.tgz", - "integrity": "sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.4.tgz", + "integrity": "sha512-mH0PEh1zoXGnaR8gD1DeGeNZtWFKbnz9hDO91dIml3iou1gpOnLqXQ2dJfB71dj6dpmUjcQ6phY3ZZJbjErr9g==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@firebase/util": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz", - "integrity": "sha512-DY02CRhOZwpzO36fHpuVysz6JZrscPiBXD0fXp6qSrL9oNOx5KWICKdR95C0lSITzxp0TZosVyHqzatE8JbcjA==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.10.2.tgz", + "integrity": "sha512-qnSHIoE9FK+HYnNhTI8q14evyqbc/vHRivfB4TgCIUOl4tosmKSQlp7ltymOlMP4xVIJTg5wrkfcZ60X4nUf7Q==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@google-cloud/firestore": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.7.0.tgz", - "integrity": "sha512-41/vBFXOeSYjFI/2mJuJrDwg2umGk+FDrI/SCGzBRUe+UZWDN4GoahIbGZ19YQsY0ANNl6DRiAy4wD6JezK02g==", + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.10.0.tgz", + "integrity": "sha512-VFNhdHvfnmqcHHs6YhmSNHHxQqaaD64GwiL0c+e1qz85S8SWZPC2XFRf8p9yHRTF40Kow424s1KBU9f0fdQa+Q==", + "license": "Apache-2.0", "optional": true, "dependencies": { + "@opentelemetry/api": "^1.3.0", "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", "google-gax": "^4.3.3", @@ -2833,9 +1755,10 @@ } }, "node_modules/@google-cloud/paginator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.0.tgz", - "integrity": "sha512-87aeg6QQcEPxGCOthnpUjvw4xAZ57G7pL8FS0C4e/81fr3FjkpUpibf1s2v5XGyGhUVGF4Jfg7yEcxqn2iUw1w==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", + "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", + "license": "Apache-2.0", "optional": true, "dependencies": { "arrify": "^2.0.0", @@ -2849,6 +1772,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", + "license": "Apache-2.0", "optional": true, "engines": { "node": ">=14.0.0" @@ -2858,15 +1782,17 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", + "license": "Apache-2.0", "optional": true, "engines": { "node": ">=14" } }, "node_modules/@google-cloud/storage": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.7.0.tgz", - "integrity": "sha512-EMCEY+6JiIkx7Dt8NXVGGjy1vRdSGdHkoqZoqjJw7cEBkT7ZkX0c7puedfn1MamnzW5SX4xoa2jVq5u7OWBmkQ==", + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.14.0.tgz", + "integrity": "sha512-H41bPL2cMfSi4EEnFzKvg7XSb7T67ocSXrmF7MPjfgFB0L6CKGzfIYJheAZi1iqXjz6XaCT1OBf6HCG5vDBTOQ==", + "license": "Apache-2.0", "optional": true, "dependencies": { "@google-cloud/paginator": "^5.0.0", @@ -2874,14 +1800,12 @@ "@google-cloud/promisify": "^4.0.0", "abort-controller": "^3.0.0", "async-retry": "^1.3.3", - "compressible": "^2.0.12", - "duplexify": "^4.0.0", - "ent": "^2.2.0", - "fast-xml-parser": "^4.3.0", + "duplexify": "^4.1.3", + "fast-xml-parser": "^4.4.1", "gaxios": "^6.0.2", - "google-auth-library": "^9.0.0", + "google-auth-library": "^9.6.3", + "html-entities": "^2.5.2", "mime": "^3.0.0", - "mime-types": "^2.0.8", "p-limit": "^3.0.1", "retry-request": "^7.0.0", "teeny-request": "^9.0.0", @@ -2895,6 +1819,7 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", "optional": true, "bin": { "uuid": "dist/bin/uuid" @@ -2909,9 +1834,10 @@ } }, "node_modules/@grpc/grpc-js": { - "version": "1.10.8", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.8.tgz", - "integrity": "sha512-vYVqYzHicDqyKB+NQhAc54I1QWCBLCrYG6unqOIcBTHx+7x8C9lcoLj3KVJXs2VB4lUbpWY+Kk9NipcbXYWmvg==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.12.2.tgz", + "integrity": "sha512-bgxdZmgTrJZX50OjyVwz3+mNEnCTNkh3cIqGPWVNeW9jX6bn1ZkU80uPd+67/ZpIJIjRQ9qaHCjhavyoWYxumg==", + "license": "Apache-2.0", "optional": true, "dependencies": { "@grpc/proto-loader": "^0.7.13", @@ -2925,6 +1851,7 @@ "version": "0.7.13", "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "license": "Apache-2.0", "optional": true, "dependencies": { "lodash.camelcase": "^4.3.0", @@ -3091,6 +2018,7 @@ "version": "4.4.2", "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", "optional": true, "funding": { "type": "opencollective", @@ -3221,15 +2149,15 @@ } }, "node_modules/@opensearch-project/opensearch": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/@opensearch-project/opensearch/-/opensearch-2.12.0.tgz", - "integrity": "sha512-FNGWbWjvpWIZHVvAbv0FkSgvc1PnWnYEHnOTeIY08vMDp9QpXumGNDjNc1tZthJ3OEeoooqH0miGFORjWnRYsQ==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@opensearch-project/opensearch/-/opensearch-2.13.0.tgz", + "integrity": "sha512-Bu3jJ7pKzumbMMeefu7/npAWAvFu5W9SlbBow1ulhluqUpqc7QoXe0KidDrMy7Dy3BQrkI6llR3cWL4lQTZOFw==", "license": "Apache-2.0", "dependencies": { "aws4": "^1.11.0", "debug": "^4.3.1", "hpagent": "^1.2.0", - "json11": "^1.1.2", + "json11": "^2.0.0", "ms": "^2.1.3", "secure-json-parse": "^2.4.0" }, @@ -3238,6 +2166,16 @@ "yarn": "^1.22.10" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -3251,30 +2189,35 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause", "optional": true }, "node_modules/@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause", "optional": true }, "node_modules/@protobufjs/codegen": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause", "optional": true }, "node_modules/@protobufjs/eventemitter": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause", "optional": true }, "node_modules/@protobufjs/fetch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", "optional": true, "dependencies": { "@protobufjs/aspromise": "^1.1.1", @@ -3285,30 +2228,35 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause", "optional": true }, "node_modules/@protobufjs/inquire": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause", "optional": true }, "node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause", "optional": true }, "node_modules/@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause", "optional": true }, "node_modules/@protobufjs/utf8": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause", "optional": true }, "node_modules/@redis/bloom": { @@ -3371,12 +2319,12 @@ } }, "node_modules/@smithy/abort-controller": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.6.tgz", - "integrity": "sha512-0XuhuHQlEqbNQZp7QxxrFTdVWdwxch4vjxYgfInF91hZFkPxf9QDrdQka0KfxFMPqLNzSw0b95uGTrLliQUavQ==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.8.tgz", + "integrity": "sha512-+3DOBcUn5/rVjlxGvUPKc416SExarAQ+Qe0bqk30YSUjbepwpS7QN0cyKUSifvLJhdMZ0WPzPP5ymut0oonrpQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -3403,15 +2351,15 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.10.tgz", - "integrity": "sha512-Uh0Sz9gdUuz538nvkPiyv1DZRX9+D15EKDtnQP5rYVAzM/dnYk3P8cg73jcxyOitPgT3mE3OVj7ky7sibzHWkw==", + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.12.tgz", + "integrity": "sha512-YAJP9UJFZRZ8N+UruTeq78zkdjUHmzsY62J4qKWZ4SXB4QXJ/+680EfXXgkYA2xj77ooMqtUY9m406zGNqwivQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.9", - "@smithy/types": "^3.6.0", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/types": "^3.7.1", "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.8", + "@smithy/util-middleware": "^3.0.10", "tslib": "^2.6.2" }, "engines": { @@ -3419,17 +2367,17 @@ } }, "node_modules/@smithy/core": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.5.1.tgz", - "integrity": "sha512-DujtuDA7BGEKExJ05W5OdxCoyekcKT3Rhg1ZGeiUWaz2BJIWXjZmsG/DIP4W48GHno7AQwRsaCb8NcBgH3QZpg==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.5.3.tgz", + "integrity": "sha512-96uW8maifUSmehaeW7uydWn7wBc98NEeNI3zN8vqakGpyCQgzyJaA64Z4FCOUmAdCJkhppd/7SZ798Fo4Xx37g==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^3.0.8", - "@smithy/protocol-http": "^4.1.5", - "@smithy/types": "^3.6.0", + "@smithy/middleware-serde": "^3.0.10", + "@smithy/protocol-http": "^4.1.7", + "@smithy/types": "^3.7.1", "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-middleware": "^3.0.8", - "@smithy/util-stream": "^3.2.1", + "@smithy/util-middleware": "^3.0.10", + "@smithy/util-stream": "^3.3.1", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, @@ -3438,15 +2386,15 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.5.tgz", - "integrity": "sha512-4FTQGAsuwqTzVMmiRVTn0RR9GrbRfkP0wfu/tXWVHd2LgNpTY0uglQpIScXK4NaEyXbB3JmZt8gfVqO50lP8wg==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.7.tgz", + "integrity": "sha512-cEfbau+rrWF8ylkmmVAObOmjbTIzKyUC5TkBL58SbLywD0RCBC4JAUKbmtSm2w5KUJNRPGgpGFMvE2FKnuNlWQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.9", - "@smithy/property-provider": "^3.1.8", - "@smithy/types": "^3.6.0", - "@smithy/url-parser": "^3.0.8", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/property-provider": "^3.1.10", + "@smithy/types": "^3.7.1", + "@smithy/url-parser": "^3.0.10", "tslib": "^2.6.2" }, "engines": { @@ -3454,25 +2402,25 @@ } }, "node_modules/@smithy/eventstream-codec": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.7.tgz", - "integrity": "sha512-kVSXScIiRN7q+s1x7BrQtZ1Aa9hvvP9FeCqCdBxv37GimIHgBCOnZ5Ip80HLt0DhnAKpiobFdGqTFgbaJNrazA==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.9.tgz", + "integrity": "sha512-F574nX0hhlNOjBnP+noLtsPFqXnWh2L0+nZKCwcu7P7J8k+k+rdIDs+RMnrMwrzhUE4mwMgyN0cYnEn0G8yrnQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "@smithy/util-hex-encoding": "^3.0.0", "tslib": "^2.6.2" } }, "node_modules/@smithy/eventstream-serde-browser": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.11.tgz", - "integrity": "sha512-Pd1Wnq3CQ/v2SxRifDUihvpXzirJYbbtXfEnnLV/z0OGCTx/btVX74P86IgrZkjOydOASBGXdPpupYQI+iO/6A==", + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.13.tgz", + "integrity": "sha512-Nee9m+97o9Qj6/XeLz2g2vANS2SZgAxV4rDBMKGHvFJHU/xz88x2RwCkwsvEwYjSX4BV1NG1JXmxEaDUzZTAtw==", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^3.0.10", - "@smithy/types": "^3.6.0", + "@smithy/eventstream-serde-universal": "^3.0.12", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -3480,12 +2428,12 @@ } }, "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.8.tgz", - "integrity": "sha512-zkFIG2i1BLbfoGQnf1qEeMqX0h5qAznzaZmMVNnvPZz9J5AWBPkOMckZWPedGUPcVITacwIdQXoPcdIQq5FRcg==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.10.tgz", + "integrity": "sha512-K1M0x7P7qbBUKB0UWIL5KOcyi6zqV5mPJoL0/o01HPJr0CSq3A9FYuJC6e11EX6hR8QTIR++DBiGrYveOu6trw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -3493,13 +2441,13 @@ } }, "node_modules/@smithy/eventstream-serde-node": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.10.tgz", - "integrity": "sha512-hjpU1tIsJ9qpcoZq9zGHBJPBOeBGYt+n8vfhDwnITPhEre6APrvqq/y3XMDEGUT2cWQ4ramNqBPRbx3qn55rhw==", + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.12.tgz", + "integrity": "sha512-kiZymxXvZ4tnuYsPSMUHe+MMfc4FTeFWJIc0Q5wygJoUQM4rVHNghvd48y7ppuulNMbuYt95ah71pYc2+o4JOA==", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^3.0.10", - "@smithy/types": "^3.6.0", + "@smithy/eventstream-serde-universal": "^3.0.12", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -3507,13 +2455,13 @@ } }, "node_modules/@smithy/eventstream-serde-universal": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.10.tgz", - "integrity": "sha512-ewG1GHbbqsFZ4asaq40KmxCmXO+AFSM1b+DcO2C03dyJj/ZH71CiTg853FSE/3SHK9q3jiYQIFjlGSwfxQ9kww==", + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.12.tgz", + "integrity": "sha512-1i8ifhLJrOZ+pEifTlF0EfZzMLUGQggYQ6WmZ4d5g77zEKf7oZ0kvh1yKWHPjofvOwqrkwRDVuxuYC8wVd662A==", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-codec": "^3.1.7", - "@smithy/types": "^3.6.0", + "@smithy/eventstream-codec": "^3.1.9", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -3521,37 +2469,37 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.9.tgz", - "integrity": "sha512-hYNVQOqhFQ6vOpenifFME546f0GfJn2OiQ3M0FDmuUu8V/Uiwy2wej7ZXxFBNqdx0R5DZAqWM1l6VRhGz8oE6A==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.1.1.tgz", + "integrity": "sha512-bH7QW0+JdX0bPBadXt8GwMof/jz0H28I84hU1Uet9ISpzUqXqRQ3fEZJ+ANPOhzSEczYvANNl3uDQDYArSFDtA==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^4.1.4", - "@smithy/querystring-builder": "^3.0.7", - "@smithy/types": "^3.5.0", + "@smithy/protocol-http": "^4.1.7", + "@smithy/querystring-builder": "^3.0.10", + "@smithy/types": "^3.7.1", "@smithy/util-base64": "^3.0.0", "tslib": "^2.6.2" } }, "node_modules/@smithy/hash-blob-browser": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.7.tgz", - "integrity": "sha512-4yNlxVNJifPM5ThaA5HKnHkn7JhctFUHvcaz6YXxHlYOSIrzI6VKQPTN8Gs1iN5nqq9iFcwIR9THqchUCouIfg==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.9.tgz", + "integrity": "sha512-wOu78omaUuW5DE+PVWXiRKWRZLecARyP3xcq5SmkXUw9+utgN8HnSnBfrjL2B/4ZxgqPjaAJQkC/+JHf1ITVaQ==", "license": "Apache-2.0", "dependencies": { "@smithy/chunked-blob-reader": "^4.0.0", "@smithy/chunked-blob-reader-native": "^3.0.1", - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" } }, "node_modules/@smithy/hash-node": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.8.tgz", - "integrity": "sha512-tlNQYbfpWXHimHqrvgo14DrMAgUBua/cNoz9fMYcDmYej7MAmUcjav/QKQbFc3NrcPxeJ7QClER4tWZmfwoPng==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.10.tgz", + "integrity": "sha512-3zWGWCHI+FlJ5WJwx73Mw2llYR8aflVyZN5JhoqLxbdPZi6UyKSdCeXAWJw9ja22m6S6Tzz1KZ+kAaSwvydi0g==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "@smithy/util-buffer-from": "^3.0.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" @@ -3561,12 +2509,12 @@ } }, "node_modules/@smithy/hash-stream-node": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.1.7.tgz", - "integrity": "sha512-xMAsvJ3hLG63lsBVi1Hl6BBSfhd8/Qnp8fC06kjOpJvyyCEXdwHITa5Kvdsk6gaAXLhbZMhQMIGvgUbfnJDP6Q==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.1.9.tgz", + "integrity": "sha512-3XfHBjSP3oDWxLmlxnt+F+FqXpL3WlXs+XXaB6bV9Wo8BBu87fK1dSEsyH7Z4ZHRmwZ4g9lFMdf08m9hoX1iRA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, @@ -3575,12 +2523,12 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.8.tgz", - "integrity": "sha512-7Qynk6NWtTQhnGTTZwks++nJhQ1O54Mzi7fz4PqZOiYXb4Z1Flpb2yRvdALoggTS8xjtohWUM+RygOtB30YL3Q==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.10.tgz", + "integrity": "sha512-Lp2L65vFi+cj0vFMu2obpPW69DU+6O5g3086lmI4XcnRCG8PxvpWC7XyaVwJCxsZFzueHjXnrOH/E0pl0zikfA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" } }, @@ -3596,24 +2544,24 @@ } }, "node_modules/@smithy/md5-js": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-3.0.8.tgz", - "integrity": "sha512-LwApfTK0OJ/tCyNUXqnWCKoE2b4rDSr4BJlDAVCkiWYeHESr+y+d5zlAanuLW6fnitVJRD/7d9/kN/ZM9Su4mA==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-3.0.10.tgz", + "integrity": "sha512-m3bv6dApflt3fS2Y1PyWPUtRP7iuBlvikEOGwu0HsCZ0vE7zcIX+dBoh3e+31/rddagw8nj92j0kJg2TfV+SJA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" } }, "node_modules/@smithy/middleware-content-length": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.10.tgz", - "integrity": "sha512-T4dIdCs1d/+/qMpwhJ1DzOhxCZjZHbHazEPJWdB4GDi2HjIZllVzeBEcdJUN0fomV8DURsgOyrbEUzg3vzTaOg==", + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.12.tgz", + "integrity": "sha512-1mDEXqzM20yywaMDuf5o9ue8OkJ373lSPbaSjyEvkWdqELhFMyNNgKGWL/rCSf4KME8B+HlHKuR8u9kRj8HzEQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^4.1.5", - "@smithy/types": "^3.6.0", + "@smithy/protocol-http": "^4.1.7", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -3621,18 +2569,18 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.1.tgz", - "integrity": "sha512-wWO3xYmFm6WRW8VsEJ5oU6h7aosFXfszlz3Dj176pTij6o21oZnzkCLzShfmRaaCHDkBXWBdO0c4sQAvLFP6zA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.3.tgz", + "integrity": "sha512-Hdl9296i/EMptaX7agrSzJZDiz5Y8XPUeBbctTmMtnCguGpqfU3jVsTUan0VLaOhsnquqWLL8Bl5HrlbVGT1og==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^2.5.1", - "@smithy/middleware-serde": "^3.0.8", - "@smithy/node-config-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.9", - "@smithy/types": "^3.6.0", - "@smithy/url-parser": "^3.0.8", - "@smithy/util-middleware": "^3.0.8", + "@smithy/core": "^2.5.3", + "@smithy/middleware-serde": "^3.0.10", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/shared-ini-file-loader": "^3.1.11", + "@smithy/types": "^3.7.1", + "@smithy/url-parser": "^3.0.10", + "@smithy/util-middleware": "^3.0.10", "tslib": "^2.6.2" }, "engines": { @@ -3640,18 +2588,18 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "3.0.25", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.25.tgz", - "integrity": "sha512-m1F70cPaMBML4HiTgCw5I+jFNtjgz5z5UdGnUbG37vw6kh4UvizFYjqJGHvicfgKMkDL6mXwyPp5mhZg02g5sg==", + "version": "3.0.27", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.27.tgz", + "integrity": "sha512-H3J/PjJpLL7Tt+fxDKiOD25sMc94YetlQhCnYeNmina2LZscAdu0ZEZPas/kwePHABaEtqp7hqa5S4UJgMs1Tg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.5", - "@smithy/service-error-classification": "^3.0.8", - "@smithy/smithy-client": "^3.4.2", - "@smithy/types": "^3.6.0", - "@smithy/util-middleware": "^3.0.8", - "@smithy/util-retry": "^3.0.8", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/protocol-http": "^4.1.7", + "@smithy/service-error-classification": "^3.0.10", + "@smithy/smithy-client": "^3.4.4", + "@smithy/types": "^3.7.1", + "@smithy/util-middleware": "^3.0.10", + "@smithy/util-retry": "^3.0.10", "tslib": "^2.6.2", "uuid": "^9.0.1" }, @@ -3673,12 +2621,12 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.8.tgz", - "integrity": "sha512-Xg2jK9Wc/1g/MBMP/EUn2DLspN8LNt+GMe7cgF+Ty3vl+Zvu+VeZU5nmhveU+H8pxyTsjrAkci8NqY6OuvZnjA==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.10.tgz", + "integrity": "sha512-MnAuhh+dD14F428ubSJuRnmRsfOpxSzvRhaGVTvd/lrUDE3kxzCCmH8lnVTvoNQnV2BbJ4c15QwZ3UdQBtFNZA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -3686,12 +2634,12 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.8.tgz", - "integrity": "sha512-d7ZuwvYgp1+3682Nx0MD3D/HtkmZd49N3JUndYWQXfRZrYEnCWYc8BHcNmVsPAp9gKvlurdg/mubE6b/rPS9MA==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.10.tgz", + "integrity": "sha512-grCHyoiARDBBGPyw2BeicpjgpsDFWZZxptbVKb3CRd/ZA15F/T6rZjCCuBUjJwdck1nwUuIxYtsS4H9DDpbP5w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -3699,14 +2647,14 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.9.tgz", - "integrity": "sha512-qRHoah49QJ71eemjuS/WhUXB+mpNtwHRWQr77J/m40ewBVVwvo52kYAmb7iuaECgGTTcYxHS4Wmewfwy++ueew==", + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.11.tgz", + "integrity": "sha512-URq3gT3RpDikh/8MBJUB+QGZzfS7Bm6TQTqoh4CqE8NBuyPkWa5eUXj0XFcFfeZVgg3WMh1u19iaXn8FvvXxZw==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^3.1.8", - "@smithy/shared-ini-file-loader": "^3.1.9", - "@smithy/types": "^3.6.0", + "@smithy/property-provider": "^3.1.10", + "@smithy/shared-ini-file-loader": "^3.1.11", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -3714,15 +2662,15 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.2.5.tgz", - "integrity": "sha512-PkOwPNeKdvX/jCpn0A8n9/TyoxjGZB8WVoJmm9YzsnAgggTj4CrjpRHlTQw7dlLZ320n1mY1y+nTRUDViKi/3w==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.3.1.tgz", + "integrity": "sha512-fr+UAOMGWh6bn4YSEezBCpJn9Ukp9oR4D32sCjCo7U81evE11YePOQ58ogzyfgmjIO79YeOdfXXqr0jyhPQeMg==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^3.1.6", - "@smithy/protocol-http": "^4.1.5", - "@smithy/querystring-builder": "^3.0.8", - "@smithy/types": "^3.6.0", + "@smithy/abort-controller": "^3.1.8", + "@smithy/protocol-http": "^4.1.7", + "@smithy/querystring-builder": "^3.0.10", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -3730,12 +2678,12 @@ } }, "node_modules/@smithy/property-provider": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.8.tgz", - "integrity": "sha512-ukNUyo6rHmusG64lmkjFeXemwYuKge1BJ8CtpVKmrxQxc6rhUX0vebcptFA9MmrGsnLhwnnqeH83VTU9hwOpjA==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.10.tgz", + "integrity": "sha512-n1MJZGTorTH2DvyTVj+3wXnd4CzjJxyXeOgnTlgNVFxaaMeT4OteEp4QrzF8p9ee2yg42nvyVK6R/awLCakjeQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -3743,12 +2691,12 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", - "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.7.tgz", + "integrity": "sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -3756,12 +2704,12 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.8.tgz", - "integrity": "sha512-btYxGVqFUARbUrN6VhL9c3dnSviIwBYD9Rz1jHuN1hgh28Fpv2xjU1HeCeDJX68xctz7r4l1PBnFhGg1WBBPuA==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.10.tgz", + "integrity": "sha512-nT9CQF3EIJtIUepXQuBFb8dxJi3WVZS3XfuDksxSCSn+/CzZowRLdhDn+2acbBv8R6eaJqPupoI/aRFIImNVPQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "@smithy/util-uri-escape": "^3.0.0", "tslib": "^2.6.2" }, @@ -3770,12 +2718,12 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.8.tgz", - "integrity": "sha512-BtEk3FG7Ks64GAbt+JnKqwuobJNX8VmFLBsKIwWr1D60T426fGrV2L3YS5siOcUhhp6/Y6yhBw1PSPxA5p7qGg==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.10.tgz", + "integrity": "sha512-Oa0XDcpo9SmjhiDD9ua2UyM3uU01ZTuIrNdZvzwUTykW1PM8o2yJvMh1Do1rY5sUQg4NDV70dMi0JhDx4GyxuQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -3783,24 +2731,24 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.8.tgz", - "integrity": "sha512-uEC/kCCFto83bz5ZzapcrgGqHOh/0r69sZ2ZuHlgoD5kYgXJEThCoTuw/y1Ub3cE7aaKdznb+jD9xRPIfIwD7g==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.10.tgz", + "integrity": "sha512-zHe642KCqDxXLuhs6xmHVgRwy078RfqxP2wRDpIyiF8EmsWXptMwnMwbVa50lw+WOGNrYm9zbaEg0oDe3PTtvQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0" + "@smithy/types": "^3.7.1" }, "engines": { "node": ">=16.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.9.tgz", - "integrity": "sha512-/+OsJRNtoRbtsX0UpSgWVxFZLsJHo/4sTr+kBg/J78sr7iC+tHeOvOJrS5hCpVQ6sWBbhWLp1UNiuMyZhE6pmA==", + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.11.tgz", + "integrity": "sha512-AUdrIZHFtUgmfSN4Gq9nHu3IkHMa1YDcN+s061Nfm+6pQ0mJy85YQDB0tZBCmls0Vuj22pLwDPmL92+Hvfwwlg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -3808,16 +2756,16 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.1.tgz", - "integrity": "sha512-NsV1jF4EvmO5wqmaSzlnTVetemBS3FZHdyc5CExbDljcyJCEEkJr8ANu2JvtNbVg/9MvKAWV44kTrGS+Pi4INg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.3.tgz", + "integrity": "sha512-pPSQQ2v2vu9vc8iew7sszLd0O09I5TRc5zhY71KA+Ao0xYazIG+uLeHbTJfIWGO3BGVLiXjUr3EEeCcEQLjpWQ==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^3.0.0", - "@smithy/protocol-http": "^4.1.5", - "@smithy/types": "^3.6.0", + "@smithy/protocol-http": "^4.1.7", + "@smithy/types": "^3.7.1", "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-middleware": "^3.0.8", + "@smithy/util-middleware": "^3.0.10", "@smithy/util-uri-escape": "^3.0.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" @@ -3827,17 +2775,17 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.4.2.tgz", - "integrity": "sha512-dxw1BDxJiY9/zI3cBqfVrInij6ShjpV4fmGHesGZZUiP9OSE/EVfdwdRz0PgvkEvrZHpsj2htRaHJfftE8giBA==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.4.4.tgz", + "integrity": "sha512-dPGoJuSZqvirBq+yROapBcHHvFjChoAQT8YPWJ820aPHHiowBlB3RL1Q4kPT1hx0qKgJuf+HhyzKi5Gbof4fNA==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^2.5.1", - "@smithy/middleware-endpoint": "^3.2.1", - "@smithy/middleware-stack": "^3.0.8", - "@smithy/protocol-http": "^4.1.5", - "@smithy/types": "^3.6.0", - "@smithy/util-stream": "^3.2.1", + "@smithy/core": "^2.5.3", + "@smithy/middleware-endpoint": "^3.2.3", + "@smithy/middleware-stack": "^3.0.10", + "@smithy/protocol-http": "^4.1.7", + "@smithy/types": "^3.7.1", + "@smithy/util-stream": "^3.3.1", "tslib": "^2.6.2" }, "engines": { @@ -3845,9 +2793,9 @@ } }, "node_modules/@smithy/types": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", - "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", + "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -3857,13 +2805,13 @@ } }, "node_modules/@smithy/url-parser": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.8.tgz", - "integrity": "sha512-4FdOhwpTW7jtSFWm7SpfLGKIBC9ZaTKG5nBF0wK24aoQKQyDIKUw3+KFWCQ9maMzrgTJIuOvOnsV2lLGW5XjTg==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.10.tgz", + "integrity": "sha512-j90NUalTSBR2NaZTuruEgavSdh8MLirf58LoGSk4AtQfyIymogIhgnGUU2Mga2bkMkpSoC9gxb74xBXL5afKAQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^3.0.8", - "@smithy/types": "^3.6.0", + "@smithy/querystring-parser": "^3.0.10", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" } }, @@ -3924,14 +2872,14 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "3.0.25", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.25.tgz", - "integrity": "sha512-fRw7zymjIDt6XxIsLwfJfYUfbGoO9CmCJk6rjJ/X5cd20+d2Is7xjU5Kt/AiDt6hX8DAf5dztmfP5O82gR9emA==", + "version": "3.0.27", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.27.tgz", + "integrity": "sha512-GV8NvPy1vAGp7u5iD/xNKUxCorE4nQzlyl057qRac+KwpH5zq8wVq6rE3lPPeuFLyQXofPN6JwxL1N9ojGapiQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^3.1.8", - "@smithy/smithy-client": "^3.4.2", - "@smithy/types": "^3.6.0", + "@smithy/property-provider": "^3.1.10", + "@smithy/smithy-client": "^3.4.4", + "@smithy/types": "^3.7.1", "bowser": "^2.11.0", "tslib": "^2.6.2" }, @@ -3940,17 +2888,17 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "3.0.25", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.25.tgz", - "integrity": "sha512-H3BSZdBDiVZGzt8TG51Pd2FvFO0PAx/A0mJ0EH8a13KJ6iUCdYnw/Dk/MdC1kTd0eUuUGisDFaxXVXo4HHFL1g==", + "version": "3.0.27", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.27.tgz", + "integrity": "sha512-7+4wjWfZqZxZVJvDutO+i1GvL6bgOajEkop4FuR6wudFlqBiqwxw3HoH6M9NgeCd37km8ga8NPp2JacQEtAMPg==", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^3.0.10", - "@smithy/credential-provider-imds": "^3.2.5", - "@smithy/node-config-provider": "^3.1.9", - "@smithy/property-provider": "^3.1.8", - "@smithy/smithy-client": "^3.4.2", - "@smithy/types": "^3.6.0", + "@smithy/config-resolver": "^3.0.12", + "@smithy/credential-provider-imds": "^3.2.7", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/property-provider": "^3.1.10", + "@smithy/smithy-client": "^3.4.4", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -3958,13 +2906,13 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.1.4.tgz", - "integrity": "sha512-kPt8j4emm7rdMWQyL0F89o92q10gvCUa6sBkBtDJ7nV2+P7wpXczzOfoDJ49CKXe5CCqb8dc1W+ZdLlrKzSAnQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.1.6.tgz", + "integrity": "sha512-mFV1t3ndBh0yZOJgWxO9J/4cHZVn5UG1D8DeCc6/echfNkeEJWu9LD7mgGH5fHrEdR7LDoWw7PQO6QiGpHXhgA==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.9", - "@smithy/types": "^3.6.0", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -3984,12 +2932,12 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.8.tgz", - "integrity": "sha512-p7iYAPaQjoeM+AKABpYWeDdtwQNxasr4aXQEA/OmbOaug9V0odRVDy3Wx4ci8soljE/JXQo+abV0qZpW8NX0yA==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.10.tgz", + "integrity": "sha512-eJO+/+RsrG2RpmY68jZdwQtnfsxjmPxzMlQpnHKjFPwrYqvlcT+fHdT+ZVwcjlWSrByOhGr9Ff2GG17efc192A==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.6.0", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -3997,13 +2945,13 @@ } }, "node_modules/@smithy/util-retry": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.8.tgz", - "integrity": "sha512-TCEhLnY581YJ+g1x0hapPz13JFqzmh/pMWL2KEFASC51qCfw3+Y47MrTmea4bUE5vsdxQ4F6/KFbUeSz22Q1ow==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.10.tgz", + "integrity": "sha512-1l4qatFp4PiU6j7UsbasUHL2VU023NRB/gfaa1M0rDqVrRN4g3mCArLRyH3OuktApA4ye+yjWQHjdziunw2eWA==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^3.0.8", - "@smithy/types": "^3.6.0", + "@smithy/service-error-classification": "^3.0.10", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -4011,14 +2959,14 @@ } }, "node_modules/@smithy/util-stream": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.2.1.tgz", - "integrity": "sha512-R3ufuzJRxSJbE58K9AEnL/uSZyVdHzud9wLS8tIbXclxKzoe09CRohj2xV8wpx5tj7ZbiJaKYcutMm1eYgz/0A==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.3.1.tgz", + "integrity": "sha512-Ff68R5lJh2zj+AUTvbAU/4yx+6QPRzg7+pI7M1FbtQHcRIp7xvguxVsQBKyB3fwiOwhAKu0lnNyYBaQfSW6TNw==", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^4.0.0", - "@smithy/node-http-handler": "^3.2.5", - "@smithy/types": "^3.6.0", + "@smithy/fetch-http-handler": "^4.1.1", + "@smithy/node-http-handler": "^3.3.1", + "@smithy/types": "^3.7.1", "@smithy/util-base64": "^3.0.0", "@smithy/util-buffer-from": "^3.0.0", "@smithy/util-hex-encoding": "^3.0.0", @@ -4029,19 +2977,6 @@ "node": ">=16.0.0" } }, - "node_modules/@smithy/util-stream/node_modules/@smithy/fetch-http-handler": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.0.0.tgz", - "integrity": "sha512-MLb1f5tbBO2X6K4lMEKJvxeLooyg7guq48C2zKr4qM7F2Gpkz4dc+hdSgu77pCJ76jVqFBjZczHYAs6dp15N+g==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^4.1.5", - "@smithy/querystring-builder": "^3.0.8", - "@smithy/types": "^3.6.0", - "@smithy/util-base64": "^3.0.0", - "tslib": "^2.6.2" - } - }, "node_modules/@smithy/util-uri-escape": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", @@ -4067,13 +3002,13 @@ } }, "node_modules/@smithy/util-waiter": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.7.tgz", - "integrity": "sha512-d5yGlQtmN/z5eoTtIYgkvOw27US2Ous4VycnXatyoImIF9tzlcpnKqQ/V7qhvJmb2p6xZne1NopCLakdTnkBBQ==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.9.tgz", + "integrity": "sha512-/aMXPANhMOlMPjfPtSrDfPeVP8l56SJlz93xeiLmhLe5xvlXA5T3abZ2ilEsDEPeY9T/wnN/vNGn9wa1SbufWA==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^3.1.6", - "@smithy/types": "^3.6.0", + "@smithy/abort-controller": "^3.1.8", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -4166,6 +3101,7 @@ "version": "0.12.5", "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", + "license": "MIT", "optional": true }, "node_modules/@types/connect": { @@ -4230,6 +3166,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT", "optional": true }, "node_modules/@types/mime": { @@ -4238,12 +3175,12 @@ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" }, "node_modules/@types/node": { - "version": "22.3.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.3.0.tgz", - "integrity": "sha512-nrWpWVaDZuaVc5X84xJ0vNrLvomM205oQyLsRt7OHNZbSHslcWsvgFR7O7hire2ZonjLrWBbedmotmIlJDVd6g==", + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", "license": "MIT", "dependencies": { - "undici-types": "~6.18.2" + "undici-types": "~6.19.8" } }, "node_modules/@types/qs": { @@ -4260,6 +3197,7 @@ "version": "2.48.12", "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "license": "MIT", "optional": true, "dependencies": { "@types/caseless": "*", @@ -4269,19 +3207,42 @@ } }, "node_modules/@types/request/node_modules/form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.2.tgz", + "integrity": "sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==", + "license": "MIT", "optional": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" + "mime-types": "^2.1.12", + "safe-buffer": "^5.2.1" }, "engines": { "node": ">= 0.12" } }, + "node_modules/@types/request/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, "node_modules/@types/send": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", @@ -4302,9 +3263,10 @@ } }, "node_modules/@types/tough-cookie": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.11.tgz", - "integrity": "sha512-xtFyCxnfpItBS6wRt6M+be0PzNEP6J/CqTR0mHCf/OzIbbOOh6DQ1MjiyzDrzDctzgYSmRcHH3PBvTO2hYovLg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "license": "MIT", "optional": true }, "node_modules/@types/triple-beam": { @@ -4344,6 +3306,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "optional": true, "dependencies": { "event-target-shim": "^5.0.0" }, @@ -4447,6 +3411,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "license": "MIT", "optional": true, "engines": { "node": ">=8" @@ -4485,6 +3450,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "license": "MIT", "optional": true, "dependencies": { "retry": "0.13.1" @@ -4556,7 +3522,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/base64id": { "version": "2.0.0", @@ -4694,30 +3661,6 @@ "node": ">= 0.4.0" } }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -4816,9 +3759,9 @@ } }, "node_modules/chart.js": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.5.tgz", - "integrity": "sha512-CVVjg1RYTJV9OCC8WeJPMx8gsV8K6WIyIEQUE3ui4AR9Hfgls9URri6Ja3hyMVBbTF8Q2KFa19PE815gWcWhng==", + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.6.tgz", + "integrity": "sha512-8Y406zevUPbbIBA/HRk33khEmQPk5+cxeflWE/2rx1NJsjVWMPw/9mSP9rxHP5eqi6LNoPBVMfZHxbwLSgldYA==", "license": "MIT", "dependencies": { "@kurkle/color": "^0.3.0" @@ -5002,30 +3945,23 @@ } }, "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.5.tgz", + "integrity": "sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q==", + "license": "MIT", "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", + "bytes": "3.1.2", + "compressible": "~2.0.18", "debug": "2.6.9", + "negotiator": "~0.6.4", "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", + "safe-buffer": "5.2.1", "vary": "~1.1.2" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/compression/node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/compression/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -5039,6 +3975,35 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5564,15 +4529,16 @@ "dev": true }, "node_modules/duplexify": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", - "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "license": "MIT", "optional": true, "dependencies": { "end-of-stream": "^1.4.1", "inherits": "^2.0.3", "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" + "stream-shift": "^1.0.2" } }, "node_modules/eastasianwidth": { @@ -5631,6 +4597,7 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", "optional": true, "dependencies": { "once": "^1.4.0" @@ -5666,12 +4633,6 @@ "node": ">=10.0.0" } }, - "node_modules/ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", - "optional": true - }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -5794,19 +4755,12 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "optional": true, "engines": { "node": ">=6" } }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, "node_modules/express": { "version": "4.21.1", "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", @@ -5894,7 +4848,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "optional": true + "license": "MIT" }, "node_modules/extract-css": { "version": "3.0.1", @@ -5919,6 +4873,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT", "optional": true }, "node_modules/fast-levenshtein": { @@ -5957,6 +4912,7 @@ "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", "dependencies": { "websocket-driver": ">=0.5.1" }, @@ -6029,27 +4985,41 @@ "license": "MIT" }, "node_modules/firebase-admin": { - "version": "12.6.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-12.6.0.tgz", - "integrity": "sha512-gc0pDiUmxscxBhcjMcttmjvExJmnQdVRb+IIth95CvMm7F9rLdabrQZThW2mK02HR696P+rzd6NqkdUA3URu4w==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.0.0.tgz", + "integrity": "sha512-tgm4+NT051tv237g4rLz6L5TJ4l1QwPjzysBJKnukP8fvdJQuXUNpqQONptNbNeLEUkRAroGNuEg5v3aVPzkbw==", "license": "Apache-2.0", "dependencies": { "@fastify/busboy": "^3.0.0", - "@firebase/database-compat": "^1.0.2", - "@firebase/database-types": "^1.0.0", - "@types/node": "^22.0.1", + "@firebase/database-compat": "^2.0.0", + "@firebase/database-types": "^1.0.6", + "@types/node": "^22.8.7", "farmhash-modern": "^1.1.0", + "google-auth-library": "^9.14.2", "jsonwebtoken": "^9.0.0", "jwks-rsa": "^3.1.0", "node-forge": "^1.3.1", - "uuid": "^10.0.0" + "uuid": "^11.0.2" }, "engines": { - "node": ">=14" + "node": ">=18" }, "optionalDependencies": { - "@google-cloud/firestore": "^7.7.0", - "@google-cloud/storage": "^7.7.0" + "@google-cloud/firestore": "^7.10.0", + "@google-cloud/storage": "^7.14.0" + } + }, + "node_modules/firebase-admin/node_modules/uuid": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", + "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" } }, "node_modules/flat-util": { @@ -6225,6 +5195,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "license": "MIT", "optional": true }, "node_modules/gauge": { @@ -6253,25 +5224,26 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/gaxios": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.1.1.tgz", - "integrity": "sha512-bw8smrX+XlAoo9o1JAksBwX+hi/RG15J+NTSxmNPIclKC3ZVK6C2afwY8OSdRvOK0+ZLecUJYtj2MmjOt3Dm0w==", - "optional": true, + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", - "node-fetch": "^2.6.9" + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" }, "engines": { "node": ">=14" } }, "node_modules/gaxios/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", - "optional": true, + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "license": "MIT", "dependencies": { "debug": "^4.3.4" }, @@ -6280,10 +5252,10 @@ } }, "node_modules/gaxios/node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", - "optional": true, + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "license": "MIT", "dependencies": { "agent-base": "^7.0.2", "debug": "4" @@ -6292,11 +5264,24 @@ "node": ">= 14" } }, + "node_modules/gaxios/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/gcp-metadata": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", - "optional": true, + "license": "Apache-2.0", "dependencies": { "gaxios": "^6.0.0", "json-bigint": "^1.0.0" @@ -6415,10 +5400,10 @@ } }, "node_modules/google-auth-library": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.5.0.tgz", - "integrity": "sha512-OUbP509lWVlZxuMY+Cgomw49VzZFP9myIcVeYEpeBlbXJbPC4R+K4BmO9hd3ciYM5QIwm5W1PODcKjqxtkye9Q==", - "optional": true, + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.0.tgz", + "integrity": "sha512-7ccSEJFDFO7exFbO6NRyC+xH8/mZ1GZGG2xxx9iHxZWcjUjJpjWxIMw3cofAKcueZ6DATiukmmprD7yavQHOyQ==", + "license": "Apache-2.0", "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", @@ -6432,21 +5417,22 @@ } }, "node_modules/google-gax": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.3.4.tgz", - "integrity": "sha512-upnobdflCz9+Lq9+nOv0pm9EQ+fLhWckz6lQTgLAkLAGggIH2fl+CUj0WgczdbhQDAnA0BSNfXYHglhA/dmZpw==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.4.1.tgz", + "integrity": "sha512-Phyp9fMfA00J3sZbJxbbB4jC55b7DBjE3F6poyL3wKMEBVKA79q6BGuHcTiM28yOzVql0NDbRL8MLLh8Iwk9Dg==", + "license": "Apache-2.0", "optional": true, "dependencies": { - "@grpc/grpc-js": "~1.10.3", - "@grpc/proto-loader": "^0.7.0", + "@grpc/grpc-js": "^1.10.9", + "@grpc/proto-loader": "^0.7.13", "@types/long": "^4.0.0", "abort-controller": "^3.0.0", "duplexify": "^4.0.0", "google-auth-library": "^9.3.0", - "node-fetch": "^2.6.1", + "node-fetch": "^2.7.0", "object-hash": "^3.0.0", - "proto3-json-serializer": "^2.0.0", - "protobufjs": "7.3.0", + "proto3-json-serializer": "^2.0.2", + "protobufjs": "^7.3.2", "retry-request": "^7.0.0", "uuid": "^9.0.1" }, @@ -6462,6 +5448,7 @@ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], + "license": "MIT", "optional": true, "bin": { "uuid": "dist/bin/uuid" @@ -6505,10 +5492,10 @@ } }, "node_modules/gtoken": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.0.1.tgz", - "integrity": "sha512-KcFVtoP1CVFtQu0aSk3AyAt2og66PFhZAlkUOuWKwzMLoulHXG5W5wE5xAnHb+yl3/wEFoqGW7/cDGMU8igDZQ==", - "optional": true, + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", "dependencies": { "gaxios": "^6.0.0", "jws": "^4.0.0" @@ -6617,6 +5604,23 @@ "remote-content": "^3.0.1" } }, + "node_modules/html-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT", + "optional": true + }, "node_modules/htmlparser2": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", @@ -6653,7 +5657,8 @@ "node_modules/http-parser-js": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "license": "MIT" }, "node_modules/http-proxy-agent": { "version": "4.0.1", @@ -6691,26 +5696,6 @@ "node": ">=0.10.0" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -6743,9 +5728,10 @@ } }, "node_modules/intuit-oauth": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/intuit-oauth/-/intuit-oauth-4.1.2.tgz", - "integrity": "sha512-ABmJS0dJsjgM9VXo9LRAuxo5y2J19OsOuHqf5TR3KUxI1swpser6G1jEz6RZ51K+YyhzI1KPCFAwdYxlNhWSaw==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/intuit-oauth/-/intuit-oauth-4.1.3.tgz", + "integrity": "sha512-jamanOys33Z2Uw1bisf+v7M+2rE9syMPmPZzkOt0iUrTp+IUk99QLgirKPukXidfwVdhgURnkTVb2wUcp84D8g==", + "license": "Apache-2.0", "dependencies": { "atob": "2.1.2", "axios": "^1.5.1", @@ -6993,9 +5979,10 @@ } }, "node_modules/json11": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/json11/-/json11-1.1.2.tgz", - "integrity": "sha512-5r1RHT1/Gr/jsI/XZZj/P6F11BKM8xvTaftRuiLkQI9Z2PFDukM82Ysxw8yDszb3NJP/NKnRlSGmhUdG99rlBw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/json11/-/json11-2.0.0.tgz", + "integrity": "sha512-VuKJKUSPEJape+daTm70Nx7vdcdorf4S6LCyN2z0jUVH4UrQ4ftXo2kC0bnHpCREmxHuHqCNVPA75BjI3CB6Ag==", + "license": "MIT", "bin": { "json11": "dist/cli.mjs" } @@ -7052,7 +6039,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "optional": true, + "license": "MIT", "dependencies": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", @@ -7079,7 +6066,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "optional": true, + "license": "MIT", "dependencies": { "jwa": "^2.0.0", "safe-buffer": "^5.0.1" @@ -7131,6 +6118,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT", "optional": true }, "node_modules/lodash.clonedeep": { @@ -7204,9 +6192,9 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, "node_modules/logform": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.1.tgz", - "integrity": "sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", "license": "MIT", "dependencies": { "@colors/colors": "1.6.0", @@ -7224,6 +6212,7 @@ "version": "5.2.3", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "license": "Apache-2.0", "optional": true }, "node_modules/lru-cache": { @@ -7319,6 +6308,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", "optional": true, "bin": { "mime": "cli.js" @@ -7555,9 +6545,9 @@ } }, "node_modules/nodemailer": { - "version": "6.9.15", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.15.tgz", - "integrity": "sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ==", + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz", + "integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==", "license": "MIT-0", "engines": { "node": ">=6.0.0" @@ -7618,6 +6608,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", "optional": true, "engines": { "node": ">= 6" @@ -7706,6 +6697,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "license": "MIT", "optional": true, "dependencies": { "yocto-queue": "^0.1.0" @@ -7832,9 +6824,9 @@ "license": "MIT" }, "node_modules/phone": { - "version": "3.1.51", - "resolved": "https://registry.npmjs.org/phone/-/phone-3.1.51.tgz", - "integrity": "sha512-ggy58LoEAb6LPZEyz1iXz7dQKbDAwp6V6yQgRQUDSlx4HgKm2YGETA5Ns/39/HQ+QL74+OBR05xzdUhi6TMHLQ==", + "version": "3.1.53", + "resolved": "https://registry.npmjs.org/phone/-/phone-3.1.53.tgz", + "integrity": "sha512-+0sMjlxjcm1rjUDRLzXW06vRg/SePwa+MubuSt9WhHoUziGrGHjuC/tfFYfh2oXKU/dcckwCyMUMDAOaVArb6w==", "license": "MIT", "engines": { "node": ">=12" @@ -7871,15 +6863,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -7909,6 +6892,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", + "license": "Apache-2.0", "optional": true, "dependencies": { "protobufjs": "^7.2.5" @@ -7918,10 +6902,11 @@ } }, "node_modules/protobufjs": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.3.0.tgz", - "integrity": "sha512-YWD03n3shzV9ImZRX3ccbjqLxj7NokGN0V/ESiBV5xWqrommYHYiihuIyavq03pWSGqlyvYUFmfoMKd+1rPA/g==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", "hasInstallScript": true, + "license": "BSD-3-Clause", "optional": true, "dependencies": { "@protobufjs/aspromise": "^1.1.2", @@ -8161,6 +7146,7 @@ "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", "optional": true, "engines": { "node": ">= 4" @@ -8170,6 +7156,7 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "license": "MIT", "optional": true, "dependencies": { "@types/request": "^2.48.8", @@ -8479,9 +7466,9 @@ } }, "node_modules/soap": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/soap/-/soap-1.1.5.tgz", - "integrity": "sha512-6YJrwY+tXHwlk/wtS7+XSc0jyEWgNw8xJQYvY9m1jZlPaGkc2nzmwKAq98fwGIw51acywhsraaeq/6GFggaNYw==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/soap/-/soap-1.1.6.tgz", + "integrity": "sha512-em3PDqr5kQjzDRkWRQ4JMCPg32uMonSdLds0QgRJrJBLid1/LHdhUgQuPxJA6SFV1/58Wu7HWIypmW+vqmUPlw==", "license": "MIT", "dependencies": { "axios": "^1.7.7", @@ -8500,9 +7487,9 @@ } }, "node_modules/socket.io": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.0.tgz", - "integrity": "sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", "license": "MIT", "dependencies": { "accepts": "~1.3.4", @@ -8796,6 +7783,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "license": "MIT", "optional": true, "dependencies": { "stubs": "^3.0.0" @@ -8805,6 +7793,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "license": "MIT", "optional": true }, "node_modules/streamsearch": { @@ -8911,12 +7900,14 @@ "node_modules/strnum": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "license": "MIT" }, "node_modules/stubs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "license": "MIT", "optional": true }, "node_modules/style-data": { @@ -9043,6 +8034,7 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "license": "Apache-2.0", "optional": true, "dependencies": { "http-proxy-agent": "^5.0.0", @@ -9059,6 +8051,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "license": "MIT", "optional": true, "engines": { "node": ">= 10" @@ -9068,6 +8061,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "license": "MIT", "optional": true, "dependencies": { "@tootallnate/once": "2", @@ -9086,6 +8080,7 @@ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], + "license": "MIT", "optional": true, "bin": { "uuid": "dist/bin/uuid" @@ -9288,9 +8283,9 @@ } }, "node_modules/undici-types": { - "version": "6.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.18.2.tgz", - "integrity": "sha512-5ruQbENj95yDYJNS3TvcaxPMshV7aizdv/hWYjGIKoANWKjhWNBsr2YEuYZKodQulB1b8l7ILOuDQep3afowQQ==", + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "license": "MIT" }, "node_modules/universalify": { @@ -9381,6 +8376,7 @@ "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "license": "Apache-2.0", "dependencies": { "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", @@ -9394,6 +8390,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", "engines": { "node": ">=0.8.0" } @@ -9438,22 +8435,22 @@ } }, "node_modules/winston": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.15.0.tgz", - "integrity": "sha512-RhruH2Cj0bV0WgNL+lOfoUBI4DVfdUNjVnJGVovWZmrcKtrFTTRzgXYK2O9cymSGjrERCtaAeHwMNnUWXlwZow==", + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", "license": "MIT", "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", "async": "^3.2.3", "is-stream": "^2.0.0", - "logform": "^2.6.0", + "logform": "^2.7.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "safe-stable-stringify": "^2.3.1", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", - "winston-transport": "^4.7.0" + "winston-transport": "^4.9.0" }, "engines": { "node": ">= 12.0.0" @@ -9479,35 +8476,19 @@ } }, "node_modules/winston-transport": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.8.0.tgz", - "integrity": "sha512-qxSTKswC6llEMZKgCQdaWgDuMJQnhuvF5f2Nk3SNXc4byfQ+voo2mX1Px9dkNOuR8p0KAjfPG29PuYUSIb+vSA==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", "license": "MIT", "dependencies": { - "logform": "^2.6.1", - "readable-stream": "^4.5.2", + "logform": "^2.7.0", + "readable-stream": "^3.6.2", "triple-beam": "^1.3.0" }, "engines": { "node": ">= 12.0.0" } }, - "node_modules/winston-transport/node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -9700,6 +8681,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "license": "MIT", "optional": true, "engines": { "node": ">=10" diff --git a/package.json b/package.json index f51af45b5..a0b146f14 100644 --- a/package.json +++ b/package.json @@ -19,13 +19,13 @@ "makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\"" }, "dependencies": { - "@aws-sdk/client-cloudwatch-logs": "^3.679.0", - "@aws-sdk/client-elasticache": "^3.675.0", - "@aws-sdk/client-s3": "^3.689.0", - "@aws-sdk/client-secrets-manager": "^3.675.0", - "@aws-sdk/client-ses": "^3.675.0", - "@aws-sdk/credential-provider-node": "^3.675.0", - "@opensearch-project/opensearch": "^2.12.0", + "@aws-sdk/client-cloudwatch-logs": "^3.693.0", + "@aws-sdk/client-elasticache": "^3.693.0", + "@aws-sdk/client-s3": "^3.693.0", + "@aws-sdk/client-secrets-manager": "^3.693.0", + "@aws-sdk/client-ses": "^3.693.0", + "@aws-sdk/credential-provider-node": "^3.693.0", + "@opensearch-project/opensearch": "^2.13.0", "@socket.io/admin-ui": "^0.5.1", "@socket.io/redis-adapter": "^8.3.0", "aws4": "^1.13.2", @@ -34,20 +34,20 @@ "bluebird": "^3.7.2", "body-parser": "^1.20.3", "canvas": "^2.11.2", - "chart.js": "^4.4.5", + "chart.js": "^4.4.6", "cloudinary": "^2.5.1", - "compression": "^1.7.4", + "compression": "^1.7.5", "cookie-parser": "^1.4.7", "cors": "2.8.5", "csrf": "^3.1.0", "dinero.js": "^1.9.1", "dotenv": "^16.4.5", "express": "^4.21.1", - "firebase-admin": "^12.6.0", + "firebase-admin": "^13.0.0", "graphql": "^16.9.0", "graphql-request": "^6.1.0", "inline-css": "^4.0.2", - "intuit-oauth": "^4.1.2", + "intuit-oauth": "^4.1.3", "ioredis": "^5.4.1", "json-2-csv": "^5.5.6", "lodash": "^4.17.21", @@ -56,18 +56,18 @@ "multer": "^1.4.5-lts.1", "node-mailjet": "^6.0.6", "node-persist": "^4.0.3", - "nodemailer": "^6.9.15", - "phone": "^3.1.51", + "nodemailer": "^6.9.16", + "phone": "^3.1.53", "recursive-diff": "^1.0.9", "redis": "^4.7.0", "rimraf": "^6.0.1", - "soap": "^1.1.5", - "socket.io": "^4.8.0", + "soap": "^1.1.6", + "socket.io": "^4.8.1", "socket.io-adapter": "^2.5.5", "ssh2-sftp-client": "^10.0.3", "twilio": "^4.23.0", "uuid": "^10.0.0", - "winston": "^3.15.0", + "winston": "^3.17.0", "winston-cloudwatch": "^6.3.0", "xml2js": "^0.6.2", "xmlbuilder2": "^3.1.1" diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index 99e37bebe..0a2b6a853 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -87,6 +87,21 @@ mutation RECEIVE_MESSAGE($msg: [messages_insert_input!]!) { updated_at unreadcnt phone_num + label + job_conversations { + job { + id + ro_number + ownr_fn + ownr_ln + ownr_co_nm + } + } + messages_aggregate (where: { read: { _eq: false }, isoutbound: { _eq: false } }){ + aggregate { + count + } + } } conversationid created_at @@ -116,6 +131,7 @@ mutation INSERT_MESSAGE($msg: [messages_insert_input!]!, $conversationid: uuid!) id archived bodyshop { + id imexshopid } created_at @@ -144,6 +160,11 @@ mutation UPDATE_MESSAGE($msid: String!, $fields: messages_set_input!) { update_messages(where: { msid: { _eq: $msid } }, _set: $fields) { returning { id + status + conversationid + conversation{ + bodyshopid + } } } }`; @@ -2544,3 +2565,69 @@ exports.GET_JOBS_BY_PKS = `query GET_JOBS_BY_PKS($ids: [uuid!]!) { } } `; + +exports.GET_CONVERSATIONS = `query GET_CONVERSATIONS($bodyshopId: uuid!) { + conversations( + where: { bodyshopid: { _eq: $bodyshopId }, archived: { _eq: false } }, + order_by: { updated_at: desc }, + limit: 50 + ) { + phone_num + id + updated_at + unreadcnt + archived + label + messages_aggregate(where: { read: { _eq: false }, isoutbound: { _eq: false } }) { + aggregate { + count + } + } + job_conversations { + job { + id + ro_number + ownr_fn + ownr_ln + ownr_co_nm + } + } + } +} +`; + +exports.MARK_MESSAGES_AS_READ = `mutation MARK_MESSAGES_AS_READ($conversationId: uuid!) { + update_messages(where: { conversationid: { _eq: $conversationId } }, _set: { read: true }) { + affected_rows + } +} +`; + +exports.GET_CONVERSATION_DETAILS = ` + query GET_CONVERSATION_DETAILS($conversationId: uuid!) { + conversation: conversations_by_pk(id: $conversationId) { + id + phone_num + updated_at + label + job_conversations { + job { + id + ro_number + ownr_fn + ownr_ln + ownr_co_nm + } + } + } + messages: messages(where: { conversationid: { _eq: $conversationId } }, order_by: { created_at: asc }) { + id + text + created_at + read + isoutbound + userid + image_path + } + } +`; diff --git a/server/routes/miscellaneousRoutes.js b/server/routes/miscellaneousRoutes.js index ff643a4b0..7463e1757 100644 --- a/server/routes/miscellaneousRoutes.js +++ b/server/routes/miscellaneousRoutes.js @@ -13,6 +13,7 @@ const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLCl const { taskAssignedEmail, tasksRemindEmail } = require("../email/tasksEmails"); const { canvastest } = require("../render/canvas-handler"); const { alertCheck } = require("../alerts/alertcheck"); +const uuid = require("uuid").v4; //Test route to ensure Express is responding. router.get("/test", eventAuthorizationMiddleware, async function (req, res) { @@ -57,6 +58,59 @@ router.get("/test-logs", eventAuthorizationMiddleware, (req, res) => { return res.status(500).send("Logs tested."); }); +router.get("/wstest", eventAuthorizationMiddleware, (req, res) => { + const { ioRedis } = req; + ioRedis.to(`bodyshop-broadcast-room:bfec8c8c-b7f1-49e0-be4c-524455f4e582`).emit("new-message-summary", { + isoutbound: true, + conversationId: "2b44d692-a9e4-4ed4-9c6b-7d8b0c44a0f6", + msid: "SM5d053957bc0da29399b768c23bffcc0f", + summary: true + }); + + // TODO: Do we need to add more content here? + ioRedis + .to(`bodyshop-conversation-room:bfec8c8c-b7f1-49e0-be4c-524455f4e582:2b44d692-a9e4-4ed4-9c6b-7d8b0c44a0f6`) + .emit("new-message-detailed", { + // + // msid: "SMbbd7703a898fef7f2c07c148ade8a6cd", + // text: "test2", + // conversationid: "2b44d692-a9e4-4ed4-9c6b-7d8b0c44a0f6", + // isoutbound: true, + // userid: "patrick@imex.dev", + // image: false, + // image_path: [], + newMessage: { + conversation: { + id: uuid(), + archived: false, + bodyshop: { + id: "bfec8c8c-b7f1-49e0-be4c-524455f4e582", + imexshopid: "APPLE" + }, + created_at: "2024-11-19T19:46:38.984633+00:00", + updated_at: "2024-11-19T22:40:48.346875+00:00", + unreadcnt: 0, + phone_num: "+16138676684" + }, + conversationid: "2b44d692-a9e4-4ed4-9c6b-7d8b0c44a0f6", + created_at: "2024-11-19T22:40:48.346875+00:00", + id: "68604ea9-c411-43ec-ab83-899868e58819", + image_path: [], + image: false, + isoutbound: true, + msid: "SMbbd7703a898fef7f2c07c148ade8a6cd", + read: false, + text: `This is a test ${Math.round(Math.random() * 100)}`, + updated_at: "2024-11-19T22:40:48.346875+00:00", + status: "posted", + userid: "patrick@imex.dev" + }, + conversationId: "2b44d692-a9e4-4ed4-9c6b-7d8b0c44a0f6", + summary: false + }); // TODO: Do we need to add more content here? + + return res.status(500).send("Logs tested."); +}); // Search router.post("/search", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, os.search); diff --git a/server/sms/receive.js b/server/sms/receive.js index c34147833..72dcb0c39 100644 --- a/server/sms/receive.js +++ b/server/sms/receive.js @@ -11,7 +11,11 @@ const logger = require("../utils/logger"); const InstanceManager = require("../utils/instanceMgr").default; exports.receive = async (req, res) => { - //Perform request validation + // Perform request validation + const { + ioRedis, + ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom } + } = req; logger.log("sms-inbound", "DEBUG", "api", null, { msid: req.body.SmsMessageSid, @@ -20,7 +24,7 @@ exports.receive = async (req, res) => { image_path: generateMediaArray(req.body) }); - if (!!!req.body || !!!req.body.MessagingServiceSid || !!!req.body.SmsMessageSid) { + if (!req.body || !req.body.MessagingServiceSid || !req.body.SmsMessageSid) { logger.log("sms-inbound-error", "ERROR", "api", null, { msid: req.body.SmsMessageSid, text: req.body.Body, @@ -28,170 +32,195 @@ exports.receive = async (req, res) => { image_path: generateMediaArray(req.body), type: "malformed-request" }); - res.status(400); - res.json({ success: false, error: "Malformed Request" }); - } else { - try { - const response = await client.request(queries.FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID, { - mssid: req.body.MessagingServiceSid, - phone: phone(req.body.From).phoneNumber - }); + res.status(400).json({ success: false, error: "Malformed Request" }); + return; + } - let newMessage = { - msid: req.body.SmsMessageSid, - text: req.body.Body, - image: !!req.body.MediaUrl0, - image_path: generateMediaArray(req.body) - }; - if (response.bodyshops[0]) { - //Found a bodyshop - should always happen. - if (response.bodyshops[0].conversations.length === 0) { - //No conversation Found, create one. - //console.log("[SMS Receive] No conversation found. Creating one."); - newMessage.conversation = { - data: { - bodyshopid: response.bodyshops[0].id, - phone_num: phone(req.body.From).phoneNumber - } - }; - } else if (response.bodyshops[0].conversations.length === 1) { - //Just add it to the conversation - //console.log("[SMS Receive] Conversation found. Added ID."); - newMessage.conversationid = response.bodyshops[0].conversations[0].id; - } else { - //We should never get here. - logger.log("sms-inbound-error", "ERROR", "api", null, { - msid: req.body.SmsMessageSid, - text: req.body.Body, - image: !!req.body.MediaUrl0, - image_path: generateMediaArray(req.body), - messagingServiceSid: req.body.MessagingServiceSid, - type: "duplicate-phone" - }); - } - try { - let insertresp; - if (response.bodyshops[0].conversations[0]) { - insertresp = await client.request(queries.INSERT_MESSAGE, { - msg: newMessage, - conversationid: response.bodyshops[0].conversations[0] && response.bodyshops[0].conversations[0].id - }); - } else { - insertresp = await client.request(queries.RECEIVE_MESSAGE, { - msg: newMessage - }); + try { + const response = await client.request(queries.FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID, { + mssid: req.body.MessagingServiceSid, + phone: phone(req.body.From).phoneNumber + }); + + let newMessage = { + msid: req.body.SmsMessageSid, + text: req.body.Body, + image: !!req.body.MediaUrl0, + image_path: generateMediaArray(req.body) + }; + + if (response.bodyshops[0]) { + if (response.bodyshops[0].conversations.length === 0) { + // No conversation found, create one + newMessage.conversation = { + data: { + bodyshopid: response.bodyshops[0].id, + phone_num: phone(req.body.From).phoneNumber } - const message = insertresp.insert_messages.returning[0]; - const data = { - type: "messaging-inbound", - conversationid: message.conversationid || "", - text: message.text || "", - messageid: message.id || "", - phone_num: message.conversation.phone_num || "" - }; + }; - const fcmresp = await admin.messaging().send({ - topic: `${message.conversation.bodyshop.imexshopid}-messaging`, - notification: { - title: InstanceManager({ - imex: `ImEX Online Message - ${data.phone_num}`, - rome: `Rome Online Message - ${data.phone_num}`, - promanager: `ProManager Message - ${data.phone_num}` - }), - body: message.image_path ? `Image ${message.text}` : message.text - //imageUrl: "https://thinkimex.com/img/io-fcm.png", //TODO:AIO Resolve addresses for other instances - }, - data + try { + // Insert new conversation and message + const insertresp = await client.request(queries.RECEIVE_MESSAGE, { msg: newMessage }); + + // Safely access conversation and message + const createdConversation = insertresp?.insert_messages?.returning?.[0]?.conversation || null; + const message = insertresp?.insert_messages?.returning?.[0]; + + if (!createdConversation) { + throw new Error("Conversation data is missing from the response."); + } + + const broadcastRoom = getBodyshopRoom(r2.insert_messages.returning[0].conversation.bodyshop.id); + const conversationRoom = getBodyshopConversationRoom({ + bodyshopId: message.conversation.bodyshop.id, + conversationId: message.conversation.id + }); + // Broadcast new message to the conversation room + ioRedis.to(broadcastRoom).emit("new-message-summary", { + isoutbound: false, + existingConversation: false, + newConversation: createdConversation, + conversationId: createdConversation.id, + updated_at: message.updated_at, + msid: message.sid, + summary: true + }); + ioRedis.to(conversationRoom).emit("new-message-detailed", { + newMessage: message, + isoutbound: false, + newConversation: createdConversation, + existingConversation: false, + conversationId: createdConversation.id, + summary: false }); logger.log("sms-inbound-success", "DEBUG", "api", null, { newMessage, - fcmresp + createdConversation }); + res.status(200).send(""); - } catch (e2) { + return; + } catch (e) { logger.log("sms-inbound-error", "ERROR", "api", null, { msid: req.body.SmsMessageSid, text: req.body.Body, image: !!req.body.MediaUrl0, image_path: generateMediaArray(req.body), messagingServiceSid: req.body.MessagingServiceSid, - error: e2 + error: e }); - res.sendStatus(500).json(e2); + res.status(500).json(e); + return; } + } else if (response.bodyshops[0].conversations.length === 1) { + // Add to the existing conversation + // conversation UPDATED + newMessage.conversationid = response.bodyshops[0].conversations[0].id; + } else { + // Duplicate phone error + logger.log("sms-inbound-error", "ERROR", "api", null, { + msid: req.body.SmsMessageSid, + text: req.body.Body, + image: !!req.body.MediaUrl0, + image_path: generateMediaArray(req.body), + messagingServiceSid: req.body.MessagingServiceSid, + type: "duplicate-phone" + }); + res.status(400).json({ success: false, error: "Duplicate phone number" }); + return; + } + + try { + // Insert message into an existing conversation + const insertresp = await client.request(queries.INSERT_MESSAGE, { + msg: newMessage, + conversationid: newMessage.conversationid + }); + + const message = insertresp.insert_messages.returning[0]; + const data = { + type: "messaging-inbound", + conversationid: message.conversationid || "", + text: message.text || "", + messageid: message.id || "", + phone_num: message.conversation.phone_num || "" + }; + + const fcmresp = await admin.messaging().send({ + topic: `${message.conversation.bodyshop.imexshopid}-messaging`, + notification: { + title: InstanceManager({ + imex: `ImEX Online Message - ${data.phone_num}`, + rome: `Rome Online Message - ${data.phone_num}`, + promanager: `ProManager Message - ${data.phone_num}` + }), + body: message.image_path ? `Image ${message.text}` : message.text + }, + data + }); + + logger.log("sms-inbound-success", "DEBUG", "api", null, { + newMessage, + fcmresp + }); + + const broadcastRoom = getBodyshopRoom(r2.insert_messages.returning[0].conversation.bodyshop.id); + const conversationRoom = getBodyshopConversationRoom({ + bodyshopId: message.conversation.bodyshop.id, + conversationId: message.conversation.id + }); + // Broadcast new message to the conversation room + ioRedis.to(broadcastRoom).emit("new-message-summary", { + isoutbound: false, + existingConversation: true, + conversationId: conversationid, + updated_at: message.updated_at, + msid: message.sid, + summary: true + }); + ioRedis.to(conversationRoom).emit("new-message-detailed", { + newMessage: message, + isoutbound: false, + existingConversation: true, + conversationId: conversationid, + summary: false + }); + + res.status(200).send(""); + } catch (e) { + logger.log("sms-inbound-error", "ERROR", "api", null, { + msid: req.body.SmsMessageSid, + text: req.body.Body, + image: !!req.body.MediaUrl0, + image_path: generateMediaArray(req.body), + messagingServiceSid: req.body.MessagingServiceSid, + error: e + }); + + res.status(500).json(e); } - } catch (e1) { - logger.log("sms-inbound-error", "ERROR", "api", null, { - msid: req.body.SmsMessageSid, - text: req.body.Body, - image: !!req.body.MediaUrl0, - image_path: generateMediaArray(req.body), - messagingServiceSid: req.body.MessagingServiceSid, - error: e1 - }); - res.sendStatus(500).json(e1); } + } catch (e) { + logger.log("sms-inbound-error", "ERROR", "api", null, { + msid: req.body.SmsMessageSid, + text: req.body.Body, + image: !!req.body.MediaUrl0, + image_path: generateMediaArray(req.body), + messagingServiceSid: req.body.MessagingServiceSid, + error: e + }); + res.status(500).json(e); } }; -// const sampleMessage: { -// "ToCountry": "CA", -// "ToState": "BC", -// "SmsMessageSid": "SMad7bddaf3454c0904999d6018b1e8f49", -// "NumMedia": "0", -// "ToCity": "Vancouver", -// "FromZip": "", -// "SmsSid": "SMad7bddaf3454c0904999d6018b1e8f49", -// "FromState": "BC", -// "SmsStatus": "received", -// "FromCity": "VANCOUVER", -// "Body": "Hi", -// "FromCountry": "CA", -// "To": "+16043301606", -// "MessagingServiceSid": "MG6e259e2add04ffa0d0aa355038670ee1", -// "ToZip": "", -// "NumSegments": "1", -// "MessageSid": "SMad7bddaf3454c0904999d6018b1e8f49", -// "AccountSid": "AC6c09d337d6b9c68ab6488c2052bd457c", -// "From": "+16049992002", -// "ApiVersion": "2010-04-01" -// } -// ] req.body { -// [0] ToCountry: 'CA', -// [0] MediaContentType0: 'image/jpeg', -// [0] ToState: 'BC', -// [0] SmsMessageSid: 'MM14fa2851ba26e0dc2b62073f8e7cdf27', -// [0] NumMedia: '1', -// [0] ToCity: 'Vancouver', -// [0] FromZip: '', -// [0] SmsSid: 'MM14fa2851ba26e0dc2b62073f8e7cdf27', -// [0] FromState: 'BC', -// [0] SmsStatus: 'received', -// [0] FromCity: 'VANCOUVER', -// [0] Body: '', -// [0] FromCountry: 'CA', -// [0] To: '+16043301606', -// [0] MessagingServiceSid: 'MG6e259e2add04ffa0d0aa355038670ee1', -// [0] ToZip: '', -// [0] NumSegments: '1', -// [0] MessageSid: 'MM14fa2851ba26e0dc2b62073f8e7cdf27', -// [0] AccountSid: 'AC6c09d337d6b9c68ab6488c2052bd457c', -// [0] From: '+16049992002', -// [0] MediaUrl0: 'https://api.twilio.com/2010-04-01/Accounts/AC6c09d337d6b9c68ab6488c2052bd457c/Messages/MM14fa2851ba26e0dc2b62073f8e7cdf27/Media/MEf129dd37979852f395eb29ffb126e19e', -// [0] ApiVersion: '2010-04-01' -// [0] } - -// [0] MediaContentType0: 'image/jpeg', -// MediaContentType0: 'video/3gpp', - const generateMediaArray = (body) => { const { NumMedia } = body; if (parseInt(NumMedia) > 0) { - //stuff const ret = []; - for (var i = 0; i < parseInt(NumMedia); i++) { + for (let i = 0; i < parseInt(NumMedia); i++) { ret.push(body[`MediaUrl${i}`]); } return ret; diff --git a/server/sms/send.js b/server/sms/send.js index e86966342..7975cc2f9 100644 --- a/server/sms/send.js +++ b/server/sms/send.js @@ -9,11 +9,14 @@ const queries = require("../graphql-client/queries"); const logger = require("../utils/logger"); const client = twilio(process.env.TWILIO_AUTH_TOKEN, process.env.TWILIO_AUTH_KEY); const { admin } = require("../firebase/firebase-handler"); - const gqlClient = require("../graphql-client/graphql-client").client; exports.send = (req, res) => { const { to, messagingServiceSid, body, conversationid, selectedMedia, imexshopid } = req.body; + const { + ioRedis, + ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom } + } = req; logger.log("sms-outbound", "DEBUG", req.user.email, null, { messagingServiceSid: messagingServiceSid, @@ -59,18 +62,36 @@ exports.send = (req, res) => { conversationid: newMessage.conversationid || "" }; - admin.messaging().send({ - topic: `${imexshopid}-messaging`, - data + // TODO Verify + // const messageData = response.insert_messages.returning[0]; + + // Broadcast new message to conversation room + const broadcastRoom = getBodyshopRoom(r2.insert_messages.returning[0].conversation.bodyshop.id); + const conversationRoom = getBodyshopConversationRoom({ + bodyshopId: r2.insert_messages.returning[0].conversation.bodyshop.id, + conversationId: r2.insert_messages.returning[0].conversation.id }); + ioRedis.to(broadcastRoom).emit("new-message-summary", { + isoutbound: true, + conversationId: conversationid, + updated_at: r2.insert_messages.returning[0].updated_at, + msid: message.sid, + summary: true + }); + ioRedis.to(conversationRoom).emit("new-message-detailed", { + newMessage: r2.insert_messages.returning[0], + conversationId: conversationid, + summary: false + }); res.sendStatus(200); }) .catch((e2) => { logger.log("sms-outbound-error", "ERROR", req.user.email, null, { msid: message.sid, conversationid, - error: e2 + error: e2.message, + stack: e2.stack }); //res.json({ success: false, message: e2 }); @@ -80,7 +101,8 @@ exports.send = (req, res) => { //res.json({ success: false, message: error }); logger.log("sms-outbound-error", "ERROR", req.user.email, null, { conversationid, - error: e1 + error: e1.message, + stack: e1.stack }); }); } else { diff --git a/server/sms/status.js b/server/sms/status.js index 9b5aeb733..63c45b33e 100644 --- a/server/sms/status.js +++ b/server/sms/status.js @@ -11,6 +11,10 @@ const { admin } = require("../firebase/firebase-handler"); exports.status = (req, res) => { const { SmsSid, SmsStatus } = req.body; + const { + ioRedis, + ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom } + } = req; client .request(queries.UPDATE_MESSAGE_STATUS, { msid: SmsSid, @@ -21,6 +25,17 @@ exports.status = (req, res) => { msid: SmsSid, fields: { status: SmsStatus } }); + // TODO Verify + const message = response.update_messages.returning[0]; + + const conversationRoom = getBodyshopConversationRoom({ + bodyshopId: message.conversation.bodyshopid, + conversationId: message.conversationid + }); + + ioRedis.to(conversationRoom).emit("message-changed", { + message + }); }) .catch((error) => { logger.log("sms-status-update-error", "ERROR", "api", null, { @@ -34,18 +49,44 @@ exports.status = (req, res) => { exports.markConversationRead = async (req, res) => { const { conversationid, imexshopid } = req.body; - admin.messaging().send({ - topic: `${imexshopid}-messaging`, - // notification: { - // title: `ImEX Online Message - ${data.phone_num}`, - // body: message.image_path ? `Image ${message.text}` : message.text, - // imageUrl: "https://thinkimex.com/img/logo512.png", - // }, - data: { - type: "messaging-mark-conversation-read", - conversationid: conversationid || "" - } + const { + ioRedis, + ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom } + } = req; + + //Server side, mark the conversation as read + + //TODO: Convert this to run on server side. Stolen from chat-conversation.container.jsx + // const [markConversationRead] = useMutation(MARK_MESSAGES_AS_READ_BY_CONVERSATION, { + // variables: { conversationId: selectedConversation }, + // refetchQueries: ["UNREAD_CONVERSATION_COUNT"], + // + // update(cache) { + // cache.modify({ + // id: cache.identify({ + // __typename: "conversations", + // id: selectedConversation + // }), + // fields: { + // messages_aggregate(cached) { + // return { aggregate: { count: 0 } }; + // } + // } + // }); + // } + // }); + + const broadcastRoom = getBodyshopRoom(r2.insert_messages.returning[0].conversation.bodyshop.id); + + ioRedis.to(broadcastRoom).emit("conversation-changed", { + //type: "conversation-marked-unread" //TODO: Flush out what this looks like. + // isoutbound: true, + // conversationId: conversationid, + // updated_at: r2.insert_messages.returning[0].updated_at, + // msid: message.sid, + // summary: true }); + res.send(200); }; diff --git a/server/utils/ioHelpers.js b/server/utils/ioHelpers.js index 3b3b15adb..a95bd90b0 100644 --- a/server/utils/ioHelpers.js +++ b/server/utils/ioHelpers.js @@ -1,8 +1,12 @@ const applyIOHelpers = ({ app, api, io, logger }) => { const getBodyshopRoom = (bodyshopID) => `bodyshop-broadcast-room:${bodyshopID}`; + // Messaging - conversation specific room to handle detailed messages when the user has a conversation open. + const getBodyshopConversationRoom = ({bodyshopId, conversationId}) => + `bodyshop-conversation-room:${bodyshopId}:${conversationId}`; const ioHelpersAPI = { - getBodyshopRoom + getBodyshopRoom, + getBodyshopConversationRoom }; // Helper middleware diff --git a/server/web-sockets/redisSocketEvents.js b/server/web-sockets/redisSocketEvents.js index 7ae8cd8c4..51e8039d8 100644 --- a/server/web-sockets/redisSocketEvents.js +++ b/server/web-sockets/redisSocketEvents.js @@ -1,9 +1,17 @@ const { admin } = require("../firebase/firebase-handler"); +const { MARK_MESSAGES_AS_READ, GET_CONVERSATIONS, GET_CONVERSATION_DETAILS } = require("../graphql-client/queries"); +const { phone } = require("phone"); +const { client: gqlClient } = require("../graphql-client/graphql-client"); +const queries = require("../graphql-client/queries"); +const twilio = require("twilio"); +const client = require("../graphql-client/graphql-client").client; + +const twilioClient = twilio(process.env.TWILIO_AUTH_TOKEN, process.env.TWILIO_AUTH_KEY); const redisSocketEvents = ({ io, redisHelpers: { setSessionData, clearSessionData }, // Note: Used if we persist user to Redis - ioHelpers: { getBodyshopRoom }, + ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom }, logger }) => { // Logging helper functions @@ -113,6 +121,7 @@ const redisSocketEvents = ({ socket.on("leave-bodyshop-room", leaveBodyshopRoom); socket.on("broadcast-to-bodyshop", broadcastToBodyshopRoom); }; + // Disconnect Events const registerDisconnectEvents = (socket) => { const disconnect = () => { @@ -129,10 +138,37 @@ const redisSocketEvents = ({ socket.on("disconnect", disconnect); }; + // Messaging Events + const registerMessagingEvents = (socket) => { + const joinConversationRoom = async ({ bodyshopId, conversationId }) => { + try { + const room = getBodyshopConversationRoom({ bodyshopId, conversationId }); + socket.join(room); + } catch (error) { + logger.log("error", "Failed to join conversation", error); + socket.emit("error", { message: "Failed to join conversation" }); + } + }; + + const leaveConversationRoom = ({ bodyshopId, conversationId }) => { + try { + const room = getBodyshopConversationRoom({ bodyshopId, conversationId }); + socket.leave(room); + // Optionally notify the client + //socket.emit("conversation-left", { conversationId }); + } catch (error) { + socket.emit("error", { message: "Failed to leave conversation" }); + } + }; + + socket.on("join-bodyshop-conversation", joinConversationRoom); + socket.on("leave-bodyshop-conversation", leaveConversationRoom); + }; // Call Handlers registerRoomAndBroadcastEvents(socket); registerUpdateEvents(socket); + registerMessagingEvents(socket); registerDisconnectEvents(socket); }; From 45ac56e0bc2011131c12f10ad721853be01fde36 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 20 Nov 2024 10:57:59 -0800 Subject: [PATCH 09/74] IO-2921 Chatter modifications as per Dave Bourbeau @ Chatter Signed-off-by: Allan Carr --- certs/{id_rsa => id_rsa.key} | 0 certs/io-ftp-test.key | 12 ++++++ certs/io-ftp-test.key.pub | 1 + certs/io-ftp-test.ppk | 12 ++++++ docker-build.ps1 | 14 +++++++ docker-build.sh | 16 ++++++++ docker-compose.yml | 9 +++-- server/data/chatter.js | 75 +++++++++++++++--------------------- 8 files changed, 91 insertions(+), 48 deletions(-) rename certs/{id_rsa => id_rsa.key} (100%) create mode 100644 certs/io-ftp-test.key create mode 100644 certs/io-ftp-test.key.pub create mode 100644 certs/io-ftp-test.ppk create mode 100644 docker-build.ps1 create mode 100644 docker-build.sh diff --git a/certs/id_rsa b/certs/id_rsa.key similarity index 100% rename from certs/id_rsa rename to certs/id_rsa.key diff --git a/certs/io-ftp-test.key b/certs/io-ftp-test.key new file mode 100644 index 000000000..ed1e6c8ef --- /dev/null +++ b/certs/io-ftp-test.key @@ -0,0 +1,12 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS +1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQBYJnAujo17diR0fM2Ze1d1Ft6XHm5 +U31pXdFEN+rGC4SoYTdZE8q3relxMS5GwwBOvgvVUuayfid2XS8ls/CMDiMBJAYqEK4CRY +PbbPB7lLnMWsF7muFhvs+SIpPQC+vtDwM2TKlxF0Y8p+iVRpvCADoggsSze7skmJWKmMTt +8jEdEOcAAAEQIyXsOSMl7DkAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ +AAAIUEAWCZwLo6Ne3YkdHzNmXtXdRbelx5uVN9aV3RRDfqxguEqGE3WRPKt63pcTEuRsMA +Tr4L1VLmsn4ndl0vJbPwjA4jASQGKhCuAkWD22zwe5S5zFrBe5rhYb7PkiKT0Avr7Q8DNk +ypcRdGPKfolUabwgA6IILEs3u7JJiVipjE7fIxHRDnAAAAQUO5dO9G7i0bxGTP0zV3eIwv +5g0NhrQJfW/bMHS6XWwaxdpr+QZ+DbBJVzZPwYC0wLMW4bJAf+kjqUnj4wGocoTeAAAAD2 +lvLWZ0cC10ZXN0LWtleQECAwQ= +-----END OPENSSH PRIVATE KEY----- diff --git a/certs/io-ftp-test.key.pub b/certs/io-ftp-test.key.pub new file mode 100644 index 000000000..128a6cb41 --- /dev/null +++ b/certs/io-ftp-test.key.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAFgmcC6OjXt2JHR8zZl7V3UW3pceblTfWld0UQ36sYLhKhhN1kTyret6XExLkbDAE6+C9VS5rJ+J3ZdLyWz8IwOIwEkBioQrgJFg9ts8HuUucxawXua4WG+z5Iik9AL6+0PAzZMqXEXRjyn6JVGm8IAOiCCxLN7uySYlYqYxO3yMR0Q5w== io-ftp-test-key diff --git a/certs/io-ftp-test.ppk b/certs/io-ftp-test.ppk new file mode 100644 index 000000000..ced0cec5e --- /dev/null +++ b/certs/io-ftp-test.ppk @@ -0,0 +1,12 @@ +PuTTY-User-Key-File-3: ecdsa-sha2-nistp521 +Encryption: none +Comment: io-ftp-test-key +Public-Lines: 4 +AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAFgmcC6OjXt +2JHR8zZl7V3UW3pceblTfWld0UQ36sYLhKhhN1kTyret6XExLkbDAE6+C9VS5rJ+ +J3ZdLyWz8IwOIwEkBioQrgJFg9ts8HuUucxawXua4WG+z5Iik9AL6+0PAzZMqXEX +Rjyn6JVGm8IAOiCCxLN7uySYlYqYxO3yMR0Q5w== +Private-Lines: 2 +AAAAQUO5dO9G7i0bxGTP0zV3eIwv5g0NhrQJfW/bMHS6XWwaxdpr+QZ+DbBJVzZP +wYC0wLMW4bJAf+kjqUnj4wGocoTe +Private-MAC: d67001d47e13c43dc8bdb9c68a25356a96c1c4a6714f3c5a1836fca646b78b54 diff --git a/docker-build.ps1 b/docker-build.ps1 new file mode 100644 index 000000000..663acfc32 --- /dev/null +++ b/docker-build.ps1 @@ -0,0 +1,14 @@ +# Stop and remove all containers, images, and networks from the Compose file +docker compose down --rmi all + +# Prune all unused Docker objects including volumes +docker system prune --all --volumes --force + +# Prune unused build cache +docker builder prune --all --force + +# Prune all unused volumes +docker volume prune --all --force + +# Rebuild and start the containers +docker compose up --build \ No newline at end of file diff --git a/docker-build.sh b/docker-build.sh new file mode 100644 index 000000000..8fc253f13 --- /dev/null +++ b/docker-build.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# Stop and remove all containers, images, and networks from the Compose file +docker compose down --rmi all + +# Prune all unused Docker objects including volumes +docker system prune --all --volumes --force + +# Prune unused build cache +docker builder prune --all --force + +# Prune all unused volumes +docker volume prune --all --force + +# Rebuild and start the containers +docker compose up --build \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 5c9cdc14b..c5c456ee6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -114,8 +114,8 @@ services: " aws --endpoint-url=http://localstack:4566 ses verify-domain-identity --domain imex.online --region ca-central-1 aws --endpoint-url=http://localstack:4566 ses verify-email-identity --email-address noreply@imex.online --region ca-central-1 - aws --endpoint-url=http://localstack:4566 secretsmanager create-secret --name CHATTER_PRIVATE_KEY --secret-string file:///tmp/certs/id_rsa - aws --endpoint-url=http://localstack:4566 logs create-log-group --log-group-name development --region ca-central-1 + aws --endpoint-url=http://localstack:4566 secretsmanager create-secret --name CHATTER_PRIVATE_KEY --secret-string file:///tmp/certs/io-ftp-test.key + aws --endpoint-url=http://localstack:4566 logs create-log-group --log-group-name development --region ca-central-1 aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-large-log --create-bucket-configuration LocationConstraint=ca-central-1 " # Node App: The Main IMEX API @@ -169,7 +169,7 @@ services: # - redis-insight-data:/db # ##Optional Container for SFTP/SSH Server for testing -# ssh-sftp-server: +# ssh-sftp-server: # image: atmoz/sftp:alpine # Using an image with SFTP support # container_name: ssh-sftp-server # hostname: ssh-sftp-server @@ -178,9 +178,10 @@ services: # ports: # - "2222:22" # Expose port 22 for SSH/SFTP (mapped to 2222 on the host) # volumes: -# - ./certs/id_rsa.pub:/home/user/.ssh/keys/id_rsa.pub:ro # Mount the SSH public key +# - ./certs/io-ftp-test.key.pub:/home/user/.ssh/keys/io-ftp-test.key.pub:ro # Mount the SSH public key as authorized_keys # - ./upload:/home/user/upload # Mount a local directory for SFTP uploads # environment: +# # - SFTP_USERS=user::1000::upload # - SFTP_USERS=user:password:1000::upload # command: > # /bin/sh -c " diff --git a/server/data/chatter.js b/server/data/chatter.js index 8890fafe9..282ff6421 100644 --- a/server/data/chatter.js +++ b/server/data/chatter.js @@ -23,7 +23,7 @@ const ftpSetup = { } }; -const allcsvsToUpload = []; +const allChatterObjects = []; const allErrors = []; exports.default = async (req, res) => { @@ -62,29 +62,26 @@ exports.default = async (req, res) => { return; } - const batchPromises = []; for (let i = 0; i < shopsToProcess.length; i += batchSize) { const batch = shopsToProcess.slice(i, i + batchSize); - const batchPromise = (async () => { - await processBatch(batch, start, end); - if (skipUpload) { - for (const csvObj of allcsvsToUpload) { - await fs.promises.writeFile(`./logs/${csvObj.filename}`, csvObj.csv); - } - } else { - await uploadViaSFTP(allcsvsToUpload); - } - })(); - batchPromises.push(batchPromise); + await processBatch(batch, start, end); } - await Promise.all(batchPromises); + + const csvToUpload = { + count: allChatterObjects.length, + csv: converter.json2csv(allChatterObjects, { emptyFieldValue: "" }), + filename: `IMEX_ONLINE_solicitation_${moment().format("YYYYMMDD")}.csv` + }; + if (skipUpload) { + await fs.promises.writeFile(`./logs/${csvToUpload.filename}`, csvToUpload.csv); + } else { + await uploadViaSFTP(csvToUpload); + } + await sendServerEmail({ subject: `Chatter Report ${moment().format("MM-DD-YY")}`, - text: `Errors:\n${JSON.stringify(allErrors, null, 2)}\n\nUploaded:\n${JSON.stringify( - allcsvsToUpload.map((x) => ({ filename: x.filename, count: x.count, result: x.result })), - null, - 2 - )}` + text: `Errors:\n${JSON.stringify(allErrors, null, 2)}\n\n + Uploaded:\n${JSON.stringify({ filename: csvToUpload.filename, count: csvToUpload.count, result: csvToUpload.result }, null, 2)}` }); logger.log("chatter-end", "DEBUG", "api", null, null); @@ -116,15 +113,7 @@ async function processBatch(batch, start, end) { phone_number: j.ownr_ph1 }; }); - - const ret = converter.json2csv(chatterObject, { emptyFieldValue: "" }); - - allcsvsToUpload.push({ - count: chatterObject.length, - csv: ret, - filename: `${bodyshop.shopname}_solicitation_${moment().format("YYYYMMDD")}.csv` - }); - + allChatterObjects.push(...chatterObject); logger.log("chatter-end-shop-extract", "DEBUG", "api", bodyshop.id, { shopname: bodyshop.shopname }); @@ -166,7 +155,7 @@ async function getPrivateKey() { } } -async function uploadViaSFTP(allcsvsToUpload) { +async function uploadViaSFTP(csvToUpload) { const sftp = new Client(); sftp.on("error", (errors) => logger.log("chatter-sftp-connection-error", "ERROR", "api", null, { error: errors.message, stack: errors.stack }) @@ -178,21 +167,19 @@ async function uploadViaSFTP(allcsvsToUpload) { //Connect to the FTP and upload all. await sftp.connect({ ...ftpSetup, privateKey }); - for (const csvObj of allcsvsToUpload) { - try { - csvObj.result = await sftp.put(Buffer.from(csvObj.csv), `${csvObj.filename}`); - logger.log("chatter-sftp-upload", "DEBUG", "api", null, { - filename: csvObj.filename, - result: csvObj.result - }); - } catch (error) { - logger.log("chatter-sftp-upload-error", "ERROR", "api", null, { - filename: csvObj.filename, - error: error.message, - stack: error.stack - }); - throw error; - } + try { + csvToUpload.result = await sftp.put(Buffer.from(csvToUpload.csv), `${csvToUpload.filename}`); + logger.log("chatter-sftp-upload", "DEBUG", "api", null, { + filename: csvToUpload.filename, + result: csvToUpload.result + }); + } catch (error) { + logger.log("chatter-sftp-upload-error", "ERROR", "api", null, { + filename: csvToUpload.filename, + error: csvToUpload.message, + stack: csvToUpload.stack + }); + throw error; } } catch (error) { logger.log("chatter-sftp-error", "ERROR", "api", null, { error: error.message, stack: error.stack }); From 261353b51113f11737cc61ac1717461f2a689e95 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 20 Nov 2024 11:35:30 -0800 Subject: [PATCH 10/74] feature/IO-3000-messaging-sockets-migrations2 - Base cleanup Signed-off-by: Dave Richer --- .../chat-affix/chat-affix.container.jsx | 79 +-------- .../registerMessagingSocketHandlers.js | 27 ++-- .../chat-conversation.container.jsx | 15 +- .../profile-shops/profile-shops.container.jsx | 2 +- client/src/contexts/SocketIO/useSocket.js | 1 - client/src/utils/fcm-handler.js | 70 -------- server/sms/receive.js | 78 ++++----- server/sms/send.js | 151 ++++++++---------- server/web-sockets/redisSocketEvents.js | 27 ++-- 9 files changed, 145 insertions(+), 305 deletions(-) delete mode 100644 client/src/utils/fcm-handler.js diff --git a/client/src/components/chat-affix/chat-affix.container.jsx b/client/src/components/chat-affix/chat-affix.container.jsx index 0c13d8327..951783f8a 100644 --- a/client/src/components/chat-affix/chat-affix.container.jsx +++ b/client/src/components/chat-affix/chat-affix.container.jsx @@ -1,11 +1,6 @@ import { useApolloClient } from "@apollo/client"; -import { getToken, onMessage } from "@firebase/messaging"; -import { Button, notification, Space } from "antd"; -import axios from "axios"; import React, { useContext, useEffect } from "react"; import { useTranslation } from "react-i18next"; -import { messaging, requestForToken } from "../../firebase/firebase.utils"; -import FcmHandler from "../../utils/fcm-handler"; import ChatPopupComponent from "../chat-popup/chat-popup.component"; import "./chat-affix.styles.scss"; import SocketContext from "../../contexts/SocketIO/socketContext"; @@ -20,81 +15,17 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) { if (!bodyshop || !bodyshop.messagingservicesid) return; //Register WS handlers - registerMessagingHandlers({ socket, client }); - - async function SubscribeToTopic() { - try { - const r = await axios.post("/notifications/subscribe", { - fcm_tokens: await getToken(messaging, { - vapidKey: import.meta.env.VITE_APP_FIREBASE_PUBLIC_VAPID_KEY - }), - type: "messaging", - imexshopid: bodyshop.imexshopid - }); - console.log("FCM Topic Subscription", r.data); - } catch (error) { - console.log("Error attempting to subscribe to messaging topic: ", error); - notification.open({ - key: "fcm", - type: "warning", - message: t("general.errors.fcm"), - btn: ( - - - - - ) - }); - } + if (socket && socket.connected) { + registerMessagingHandlers({ socket, client }); } - SubscribeToTopic(); - // eslint-disable-next-line react-hooks/exhaustive-deps - return () => { - unregisterMessagingHandlers({ socket }); + if (socket && socket.connected) { + unregisterMessagingHandlers({ socket }); + } }; }, [bodyshop, socket, t, client]); - useEffect(() => { - function handleMessage(payload) { - FcmHandler({ - client, - payload: (payload && payload.data && payload.data.data) || payload.data - }); - } - - let stopMessageListener, channel; - try { - stopMessageListener = onMessage(messaging, handleMessage); - channel = new BroadcastChannel("imex-sw-messages"); - channel.addEventListener("message", handleMessage); - } catch (error) { - console.log("Unable to set event listeners."); - } - return () => { - stopMessageListener && stopMessageListener(); - channel && channel.removeEventListener("message", handleMessage); - }; - }, [client]); - if (!bodyshop || !bodyshop.messagingservicesid) return <>; return ( diff --git a/client/src/components/chat-affix/registerMessagingSocketHandlers.js b/client/src/components/chat-affix/registerMessagingSocketHandlers.js index 08d2e86bb..c6cd66c0e 100644 --- a/client/src/components/chat-affix/registerMessagingSocketHandlers.js +++ b/client/src/components/chat-affix/registerMessagingSocketHandlers.js @@ -1,8 +1,9 @@ import { CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries"; -export function registerMessagingHandlers({ socket, client }) { +export const registerMessagingHandlers = ({ socket, client }) => { if (!(socket && client)) return; - function handleNewMessageSummary(message) { + + const handleNewMessageSummary = (message) => { console.log("🚀 ~ SUMMARY CONSOLE LOG:", message); if (!message.isoutbound) { @@ -69,9 +70,9 @@ export function registerMessagingHandlers({ socket, client }) { } }); } - } + }; - function handleNewMessageDetailed(message) { + const handleNewMessageDetailed = (message) => { console.log("🚀 ~ DETAIL CONSOLE LOG:", message); //They're looking at the conversation right now. Need to merge into the list of messages i.e. append to the end. //Add the message to the overall cache. @@ -97,9 +98,9 @@ export function registerMessagingHandlers({ socket, client }) { // We got this as a receive. else { } - } + }; - function handleMessageChanged(message) { + const handleMessageChanged = (message) => { //Find it in the cache, and just update it based on what was sent. client.cache.modify({ id: client.cache.identify({ @@ -114,23 +115,23 @@ export function registerMessagingHandlers({ socket, client }) { } } }); - } + }; - function handleConversationChanged(conversation) { + const handleConversationChanged = (conversation) => { //If it was archived, marked unread, etc. - } + }; socket.on("new-message-summary", handleNewMessageSummary); socket.on("new-message-detailed", handleNewMessageDetailed); socket.on("message-changed", handleMessageChanged); - socket.on("conversation-changed", handleConversationChanged); //TODO: Unread, mark as read, archived, unarchive, etc. -} + socket.on("conversation-changed", handleConversationChanged); //TODO: Unread, mark as read, archived, unarchive, etc. +}; -export function unregisterMessagingHandlers({ socket }) { +export const unregisterMessagingHandlers = ({ socket }) => { if (!socket) return; socket.off("new-message-summary"); socket.off("new-message-detailed"); socket.off("message-changed"); socket.off("message-changed"); socket.off("conversation-changed"); -} +}; diff --git a/client/src/components/chat-conversation/chat-conversation.container.jsx b/client/src/components/chat-conversation/chat-conversation.container.jsx index 2ea760e3c..9a57a43e2 100644 --- a/client/src/components/chat-conversation/chat-conversation.container.jsx +++ b/client/src/components/chat-conversation/chat-conversation.container.jsx @@ -1,11 +1,10 @@ -import { useMutation, useQuery } from "@apollo/client"; +import { useQuery } from "@apollo/client"; import axios from "axios"; -import React, { useEffect, useState, useContext } from "react"; +import React, { useContext, useEffect, useState } from "react"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import SocketContext from "../../contexts/SocketIO/socketContext"; import { GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries"; -import { MARK_MESSAGES_AS_READ_BY_CONVERSATION } from "../../graphql/messages.queries"; import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors"; import ChatConversationComponent from "./chat-conversation.component"; @@ -31,10 +30,16 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) { const { socket } = useContext(SocketContext); useEffect(() => { - socket.emit("join-bodyshop-conversation", { bodyshopId: bodyshop.id, conversationId: selectedConversation }); + socket.emit("join-bodyshop-conversation", { + bodyshopId: bodyshop.id, + conversationId: selectedConversation + }); return () => { - socket.emit("leave-bodyshop-conversation", { bodyshopId: bodyshop.id, conversationId: selectedConversation }); + socket.emit("leave-bodyshop-conversation", { + bodyshopId: bodyshop.id, + conversationId: selectedConversation + }); }; }, [selectedConversation, bodyshop, socket]); diff --git a/client/src/components/profile-shops/profile-shops.container.jsx b/client/src/components/profile-shops/profile-shops.container.jsx index 4c5150913..46fe59aa2 100644 --- a/client/src/components/profile-shops/profile-shops.container.jsx +++ b/client/src/components/profile-shops/profile-shops.container.jsx @@ -5,7 +5,7 @@ import { QUERY_ALL_ASSOCIATIONS, UPDATE_ACTIVE_ASSOCIATION } from "../../graphql import AlertComponent from "../alert/alert.component"; import ProfileShopsComponent from "./profile-shops.component"; import axios from "axios"; -import { getToken } from "firebase/messaging"; +import { getToken } from " firebase/messaging"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; diff --git a/client/src/contexts/SocketIO/useSocket.js b/client/src/contexts/SocketIO/useSocket.js index 69c9402bd..885602fe3 100644 --- a/client/src/contexts/SocketIO/useSocket.js +++ b/client/src/contexts/SocketIO/useSocket.js @@ -67,7 +67,6 @@ const useSocket = (bodyshop) => { store.dispatch(setWssStatus("disconnected")); }; - //TODO: Check these handlers. socketInstance.on("connect", handleConnect); socketInstance.on("reconnect", handleReconnect); socketInstance.on("connect_error", handleConnectionError); diff --git a/client/src/utils/fcm-handler.js b/client/src/utils/fcm-handler.js deleted file mode 100644 index e3f7c8d43..000000000 --- a/client/src/utils/fcm-handler.js +++ /dev/null @@ -1,70 +0,0 @@ -export default async function FcmHandler({ client, payload }) { - console.log("FCM", payload); - // switch (payload.type) { - // case "messaging-inbound": - // client.cache.modify({ - // id: client.cache.identify({ - // __typename: "conversations", - // id: payload.conversationid - // }), - // fields: { - // messages_aggregate(cached) { - // return { aggregate: { count: cached.aggregate.count + 1 } }; - // } - // } - // }); - // client.cache.modify({ - // fields: { - // messages_aggregate(cached) { - // return { aggregate: { count: cached.aggregate.count + 1 } }; - // } - // } - // }); - // break; - // case "messaging-outbound": - // client.cache.modify({ - // id: client.cache.identify({ - // __typename: "conversations", - // id: payload.conversationid - // }), - // fields: { - // updated_at(oldupdated0) { - // return new Date(); - // } - // // messages_aggregate(cached) { - // // return { aggregate: { count: cached.aggregate.count + 1 } }; - // // }, - // } - // }); - // break; - // case "messaging-mark-conversation-read": - // let previousUnreadCount = 0; - // client.cache.modify({ - // id: client.cache.identify({ - // __typename: "conversations", - // id: payload.conversationid - // }), - // fields: { - // messages_aggregate(cached) { - // previousUnreadCount = cached.aggregate.count; - // return { aggregate: { count: 0 } }; - // } - // } - // }); - // client.cache.modify({ - // fields: { - // messages_aggregate(cached) { - // return { - // aggregate: { - // count: cached.aggregate.count - previousUnreadCount - // } - // }; - // } - // } - // }); - // break; - // default: - // console.log("No payload type set."); - // break; - // } -} diff --git a/server/sms/receive.js b/server/sms/receive.js index 72dcb0c39..3fb721eec 100644 --- a/server/sms/receive.js +++ b/server/sms/receive.js @@ -11,7 +11,6 @@ const logger = require("../utils/logger"); const InstanceManager = require("../utils/instanceMgr").default; exports.receive = async (req, res) => { - // Perform request validation const { ioRedis, ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom } @@ -50,20 +49,17 @@ exports.receive = async (req, res) => { }; if (response.bodyshops[0]) { - if (response.bodyshops[0].conversations.length === 0) { - // No conversation found, create one + const bodyshop = response.bodyshops[0]; + if (bodyshop.conversations.length === 0) { newMessage.conversation = { data: { - bodyshopid: response.bodyshops[0].id, + bodyshopid: bodyshop.id, phone_num: phone(req.body.From).phoneNumber } }; try { - // Insert new conversation and message const insertresp = await client.request(queries.RECEIVE_MESSAGE, { msg: newMessage }); - - // Safely access conversation and message const createdConversation = insertresp?.insert_messages?.returning?.[0]?.conversation || null; const message = insertresp?.insert_messages?.returning?.[0]; @@ -71,12 +67,12 @@ exports.receive = async (req, res) => { throw new Error("Conversation data is missing from the response."); } - const broadcastRoom = getBodyshopRoom(r2.insert_messages.returning[0].conversation.bodyshop.id); + const broadcastRoom = getBodyshopRoom(createdConversation.bodyshop.id); const conversationRoom = getBodyshopConversationRoom({ bodyshopId: message.conversation.bodyshop.id, conversationId: message.conversation.id }); - // Broadcast new message to the conversation room + ioRedis.to(broadcastRoom).emit("new-message-summary", { isoutbound: false, existingConversation: false, @@ -86,6 +82,7 @@ exports.receive = async (req, res) => { msid: message.sid, summary: true }); + ioRedis.to(conversationRoom).emit("new-message-detailed", { newMessage: message, isoutbound: false, @@ -103,24 +100,12 @@ exports.receive = async (req, res) => { res.status(200).send(""); return; } catch (e) { - logger.log("sms-inbound-error", "ERROR", "api", null, { - msid: req.body.SmsMessageSid, - text: req.body.Body, - image: !!req.body.MediaUrl0, - image_path: generateMediaArray(req.body), - messagingServiceSid: req.body.MessagingServiceSid, - error: e - }); - - res.status(500).json(e); + handleError(req, e, res, "RECEIVE_MESSAGE"); return; } - } else if (response.bodyshops[0].conversations.length === 1) { - // Add to the existing conversation - // conversation UPDATED - newMessage.conversationid = response.bodyshops[0].conversations[0].id; + } else if (bodyshop.conversations.length === 1) { + newMessage.conversationid = bodyshop.conversations[0].id; } else { - // Duplicate phone error logger.log("sms-inbound-error", "ERROR", "api", null, { msid: req.body.SmsMessageSid, text: req.body.Body, @@ -134,7 +119,6 @@ exports.receive = async (req, res) => { } try { - // Insert message into an existing conversation const insertresp = await client.request(queries.INSERT_MESSAGE, { msg: newMessage, conversationid: newMessage.conversationid @@ -167,52 +151,36 @@ exports.receive = async (req, res) => { fcmresp }); - const broadcastRoom = getBodyshopRoom(r2.insert_messages.returning[0].conversation.bodyshop.id); + const broadcastRoom = getBodyshopRoom(message.conversation.bodyshop.id); const conversationRoom = getBodyshopConversationRoom({ bodyshopId: message.conversation.bodyshop.id, conversationId: message.conversation.id }); - // Broadcast new message to the conversation room + ioRedis.to(broadcastRoom).emit("new-message-summary", { isoutbound: false, existingConversation: true, - conversationId: conversationid, + conversationId: message.conversationid, updated_at: message.updated_at, msid: message.sid, summary: true }); + ioRedis.to(conversationRoom).emit("new-message-detailed", { newMessage: message, isoutbound: false, existingConversation: true, - conversationId: conversationid, + conversationId: message.conversationid, summary: false }); res.status(200).send(""); } catch (e) { - logger.log("sms-inbound-error", "ERROR", "api", null, { - msid: req.body.SmsMessageSid, - text: req.body.Body, - image: !!req.body.MediaUrl0, - image_path: generateMediaArray(req.body), - messagingServiceSid: req.body.MessagingServiceSid, - error: e - }); - - res.status(500).json(e); + handleError(req, e, res, "INSERT_MESSAGE"); } } } catch (e) { - logger.log("sms-inbound-error", "ERROR", "api", null, { - msid: req.body.SmsMessageSid, - text: req.body.Body, - image: !!req.body.MediaUrl0, - image_path: generateMediaArray(req.body), - messagingServiceSid: req.body.MessagingServiceSid, - error: e - }); - res.status(500).json(e); + handleError(req, e, res, "FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID"); } }; @@ -228,3 +196,17 @@ const generateMediaArray = (body) => { return null; } }; + +const handleError = (req, error, res, context) => { + logger.log("sms-inbound-error", "ERROR", "api", null, { + msid: req.body.SmsMessageSid, + text: req.body.Body, + image: !!req.body.MediaUrl0, + image_path: generateMediaArray(req.body), + messagingServiceSid: req.body.MessagingServiceSid, + context, + error + }); + + res.status(500).json({ error: error.message || "Internal Server Error" }); +}; diff --git a/server/sms/send.js b/server/sms/send.js index 7975cc2f9..eb8cb6e5f 100644 --- a/server/sms/send.js +++ b/server/sms/send.js @@ -8,10 +8,9 @@ const { phone } = require("phone"); const queries = require("../graphql-client/queries"); const logger = require("../utils/logger"); const client = twilio(process.env.TWILIO_AUTH_TOKEN, process.env.TWILIO_AUTH_KEY); -const { admin } = require("../firebase/firebase-handler"); const gqlClient = require("../graphql-client/graphql-client").client; -exports.send = (req, res) => { +exports.send = async (req, res) => { const { to, messagingServiceSid, body, conversationid, selectedMedia, imexshopid } = req.body; const { ioRedis, @@ -19,7 +18,7 @@ exports.send = (req, res) => { } = req; logger.log("sms-outbound", "DEBUG", req.user.email, null, { - messagingServiceSid: messagingServiceSid, + messagingServiceSid, to: phone(to).phoneNumber, mediaUrl: selectedMedia.map((i) => i.src), text: body, @@ -30,85 +29,10 @@ exports.send = (req, res) => { image_path: req.body.selectedMedia.length > 0 ? selectedMedia.map((i) => i.src) : [] }); - if (!!to && !!messagingServiceSid && (!!body || !!selectedMedia.length > 0) && !!conversationid) { - client.messages - .create({ - body: body, - messagingServiceSid: messagingServiceSid, - to: phone(to).phoneNumber, - mediaUrl: selectedMedia.map((i) => i.src) - }) - .then((message) => { - let newMessage = { - msid: message.sid, - text: body, - conversationid, - isoutbound: true, - userid: req.user.email, - image: req.body.selectedMedia.length > 0, - image_path: req.body.selectedMedia.length > 0 ? selectedMedia.map((i) => i.src) : [] - }; - gqlClient - .request(queries.INSERT_MESSAGE, { msg: newMessage, conversationid }) - .then((r2) => { - //console.log("Responding GQL Message ID", JSON.stringify(r2)); - logger.log("sms-outbound-success", "DEBUG", req.user.email, null, { - msid: message.sid, - conversationid - }); - - const data = { - type: "messaging-outbound", - conversationid: newMessage.conversationid || "" - }; - - // TODO Verify - // const messageData = response.insert_messages.returning[0]; - - // Broadcast new message to conversation room - const broadcastRoom = getBodyshopRoom(r2.insert_messages.returning[0].conversation.bodyshop.id); - const conversationRoom = getBodyshopConversationRoom({ - bodyshopId: r2.insert_messages.returning[0].conversation.bodyshop.id, - conversationId: r2.insert_messages.returning[0].conversation.id - }); - - ioRedis.to(broadcastRoom).emit("new-message-summary", { - isoutbound: true, - conversationId: conversationid, - updated_at: r2.insert_messages.returning[0].updated_at, - msid: message.sid, - summary: true - }); - ioRedis.to(conversationRoom).emit("new-message-detailed", { - newMessage: r2.insert_messages.returning[0], - conversationId: conversationid, - summary: false - }); - res.sendStatus(200); - }) - .catch((e2) => { - logger.log("sms-outbound-error", "ERROR", req.user.email, null, { - msid: message.sid, - conversationid, - error: e2.message, - stack: e2.stack - }); - - //res.json({ success: false, message: e2 }); - }); - }) - .catch((e1) => { - //res.json({ success: false, message: error }); - logger.log("sms-outbound-error", "ERROR", req.user.email, null, { - conversationid, - error: e1.message, - stack: e1.stack - }); - }); - } else { + if (!to || !messagingServiceSid || (!body && selectedMedia.length === 0) || !conversationid) { logger.log("sms-outbound-error", "ERROR", req.user.email, null, { type: "missing-parameters", - messagingServiceSid: messagingServiceSid, + messagingServiceSid, to: phone(to).phoneNumber, text: body, conversationid, @@ -118,5 +42,72 @@ exports.send = (req, res) => { image_path: req.body.selectedMedia.length > 0 ? selectedMedia.map((i) => i.src) : [] }); res.status(400).json({ success: false, message: "Missing required parameter(s)." }); + return; + } + + try { + const message = await client.messages.create({ + body, + messagingServiceSid, + to: phone(to).phoneNumber, + mediaUrl: selectedMedia.map((i) => i.src) + }); + + const newMessage = { + msid: message.sid, + text: body, + conversationid, + isoutbound: true, + userid: req.user.email, + image: req.body.selectedMedia.length > 0, + image_path: req.body.selectedMedia.length > 0 ? selectedMedia.map((i) => i.src) : [] + }; + + try { + const gqlResponse = await gqlClient.request(queries.INSERT_MESSAGE, { msg: newMessage, conversationid }); + + logger.log("sms-outbound-success", "DEBUG", req.user.email, null, { + msid: message.sid, + conversationid + }); + + const insertedMessage = gqlResponse.insert_messages.returning[0]; + const broadcastRoom = getBodyshopRoom(insertedMessage.conversation.bodyshop.id); + const conversationRoom = getBodyshopConversationRoom({ + bodyshopId: insertedMessage.conversation.bodyshop.id, + conversationId: insertedMessage.conversation.id + }); + + ioRedis.to(broadcastRoom).emit("new-message-summary", { + isoutbound: true, + conversationId: conversationid, + updated_at: insertedMessage.updated_at, + msid: message.sid, + summary: true + }); + + ioRedis.to(conversationRoom).emit("new-message-detailed", { + newMessage: insertedMessage, + conversationId: conversationid, + summary: false + }); + + res.sendStatus(200); + } catch (gqlError) { + logger.log("sms-outbound-error", "ERROR", req.user.email, null, { + msid: message.sid, + conversationid, + error: gqlError.message, + stack: gqlError.stack + }); + res.status(500).json({ success: false, message: "Failed to insert message into database." }); + } + } catch (twilioError) { + logger.log("sms-outbound-error", "ERROR", req.user.email, null, { + conversationid, + error: twilioError.message, + stack: twilioError.stack + }); + res.status(500).json({ success: false, message: "Failed to send message through Twilio." }); } }; diff --git a/server/web-sockets/redisSocketEvents.js b/server/web-sockets/redisSocketEvents.js index 51e8039d8..8e1358994 100644 --- a/server/web-sockets/redisSocketEvents.js +++ b/server/web-sockets/redisSocketEvents.js @@ -1,12 +1,4 @@ const { admin } = require("../firebase/firebase-handler"); -const { MARK_MESSAGES_AS_READ, GET_CONVERSATIONS, GET_CONVERSATION_DETAILS } = require("../graphql-client/queries"); -const { phone } = require("phone"); -const { client: gqlClient } = require("../graphql-client/graphql-client"); -const queries = require("../graphql-client/queries"); -const twilio = require("twilio"); -const client = require("../graphql-client/graphql-client").client; - -const twilioClient = twilio(process.env.TWILIO_AUTH_TOKEN, process.env.TWILIO_AUTH_KEY); const redisSocketEvents = ({ io, @@ -84,6 +76,7 @@ const redisSocketEvents = ({ }; socket.on("update-token", updateToken); }; + // Room Broadcast Events const registerRoomAndBroadcastEvents = (socket) => { const joinBodyshopRoom = (bodyshopUUID) => { @@ -138,6 +131,7 @@ const redisSocketEvents = ({ socket.on("disconnect", disconnect); }; + // Messaging Events const registerMessagingEvents = (socket) => { const joinConversationRoom = async ({ bodyshopId, conversationId }) => { @@ -145,19 +139,26 @@ const redisSocketEvents = ({ const room = getBodyshopConversationRoom({ bodyshopId, conversationId }); socket.join(room); } catch (error) { - logger.log("error", "Failed to join conversation", error); + logger.log("Failed to Join Conversation Room", "error", "io-redis", null, { + bodyshopId, + conversationId, + error: error.message, + stack: error.stack + }); socket.emit("error", { message: "Failed to join conversation" }); } }; - const leaveConversationRoom = ({ bodyshopId, conversationId }) => { try { const room = getBodyshopConversationRoom({ bodyshopId, conversationId }); socket.leave(room); - // Optionally notify the client - //socket.emit("conversation-left", { conversationId }); } catch (error) { - socket.emit("error", { message: "Failed to leave conversation" }); + logger.log("Failed to Leave Conversation Room", "error", "io-redis", null, { + bodyshopId, + conversationId, + error: error.message, + stack: error.stack + }); } }; From e15384d0bf61dd9121f067de227708698c72eaea Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 20 Nov 2024 12:23:50 -0800 Subject: [PATCH 11/74] feature/IO-3000-messaging-sockets-migrations2 - Everything but tagging and labels works Signed-off-by: Dave Richer --- .../registerMessagingSocketHandlers.js | 198 +++++++++++------- .../chat-conversation.container.jsx | 11 +- server/sms/status.js | 119 ++++++----- 3 files changed, 186 insertions(+), 142 deletions(-) diff --git a/client/src/components/chat-affix/registerMessagingSocketHandlers.js b/client/src/components/chat-affix/registerMessagingSocketHandlers.js index c6cd66c0e..339acd9a6 100644 --- a/client/src/components/chat-affix/registerMessagingSocketHandlers.js +++ b/client/src/components/chat-affix/registerMessagingSocketHandlers.js @@ -4,127 +4,169 @@ export const registerMessagingHandlers = ({ socket, client }) => { if (!(socket && client)) return; const handleNewMessageSummary = (message) => { - console.log("🚀 ~ SUMMARY CONSOLE LOG:", message); + const { conversationId, newConversation, existingConversation, isoutbound } = message; - if (!message.isoutbound) { - //It's an inbound message. - if (!message.existingConversation) { - //Do a read query. - const queryResults = client.cache.readQuery({ - query: CONVERSATION_LIST_QUERY, - variables: {} - }); - // Do a write query. Assume 0 unread messages to utilize code below. - client.cache.writeQuery({ - query: CONVERSATION_LIST_QUERY, - variables: {}, - data: { - conversations: [ - { ...message.newConversation, messages_aggregate: { aggregate: { count: 0 } } }, - ...queryResults - ] - } - }); - } + if (!existingConversation && newConversation?.phone_num) { + const queryResults = client.cache.readQuery({ + query: CONVERSATION_LIST_QUERY, + variables: { offset: 0 } + }); + + const fullConversation = { + ...newConversation, + phone_num: newConversation.phone_num, + id: newConversation.id, + updated_at: newConversation.updated_at || new Date().toISOString(), + unreadcnt: newConversation.unreadcnt || 0, + archived: newConversation.archived || false, + label: newConversation.label || null, + job_conversations: newConversation.job_conversations || [], + messages_aggregate: newConversation.messages_aggregate || { + aggregate: { count: isoutbound ? 0 : 1 } + } + }; + + client.cache.writeQuery({ + query: CONVERSATION_LIST_QUERY, + variables: { offset: 0 }, + data: { + conversations: [fullConversation, ...(queryResults?.conversations || [])] + } + }); + } else { client.cache.modify({ id: client.cache.identify({ __typename: "conversations", - id: message.conversationId + id: conversationId }), fields: { updated_at: () => new Date(), messages_aggregate(cached) { - return { aggregate: { count: cached.aggregate.count + 1 } }; + // Increment unread count only if the message is inbound + if (!isoutbound) { + return { aggregate: { count: cached.aggregate.count + 1 } }; + } + return cached; } } }); - client.cache.modify({ - fields: { - conversations(existingConversations = [], { readField }) { - return [ - { __ref: `conversations:${message.conversationId}` }, // TODO: This throws the cache merging error in apollo. - ...existingConversations.filter((c) => c.__ref !== `conversations:${message.conversationId}`) - ]; - } - } - }); - - client.cache.modify({ - fields: { - messages_aggregate(cached) { - return { aggregate: { count: cached.aggregate.count + 1 } }; - } - } - }); - } else { - //It's an outbound message - //Update the last updated for conversations in the list. If it's new, add it in. - // If it isn't just update the last updated at. - client.cache.modify({ - id: client.cache.identify({ - __typename: "conversations", - id: message.conversationId - }), - fields: { - updated_at: () => message.newMessage.updated_at - } - }); } }; - const handleNewMessageDetailed = (message) => { - console.log("🚀 ~ DETAIL CONSOLE LOG:", message); - //They're looking at the conversation right now. Need to merge into the list of messages i.e. append to the end. - //Add the message to the overall cache. + const { conversationId, newMessage } = message; - //Handle outbound messages - if (message.newMessage.isoutbound) { - const queryResults = client.cache.readQuery({ - query: GET_CONVERSATION_DETAILS, - variables: { conversationId: message.newMessage.conversationid } - }); + // Append the new message to the conversation's message list + const queryResults = client.cache.readQuery({ + query: GET_CONVERSATION_DETAILS, + variables: { conversationId } + }); + + if (queryResults) { client.cache.writeQuery({ query: GET_CONVERSATION_DETAILS, - variables: { conversationId: message.newMessage.conversationid }, + variables: { conversationId }, data: { ...queryResults, conversations_by_pk: { ...queryResults.conversations_by_pk, - messages: [...queryResults.conversations_by_pk.messages, message.newMessage] + messages: [...queryResults.conversations_by_pk.messages, newMessage] } } }); } - // We got this as a receive. - else { - } }; const handleMessageChanged = (message) => { - //Find it in the cache, and just update it based on what was sent. + // Find the message in the cache and update all fields dynamically client.cache.modify({ id: client.cache.identify({ __typename: "messages", id: message.id }), fields: { - //TODO: see if there is a way to have this update all fields e.g. only spread in updates rather than prescribing - updated_at: () => new Date(), - status(cached) { - return message.status; - } + // Dynamically update all fields based on the incoming message object + __typename: (existingType) => existingType || "messages", // Ensure __typename is preserved + ...Object.fromEntries( + Object.entries(message).map(([key, value]) => [ + key, + (cached) => (value !== undefined ? value : cached) // Update with new value or keep existing + ]) + ) } }); }; - const handleConversationChanged = (conversation) => { - //If it was archived, marked unread, etc. + const handleConversationChanged = (data) => { + const { type, conversationId, jobId, label } = data; + + switch (type) { + case "conversation-marked-read": + client.cache.modify({ + id: client.cache.identify({ + __typename: "conversations", + id: conversationId + }), + fields: { + messages_aggregate: () => ({ aggregate: { count: 0 } }) + } + }); + + // Optionally, refetch queries if needed + // client.refetchQueries({ + // include: [CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS] + // }); + break; + case "tag-added": + client.cache.modify({ + id: client.cache.identify({ + __typename: "conversations", + id: conversationId + }), + fields: { + job_conversations(existingJobConversations = []) { + return [...existingJobConversations, { __ref: `jobs:${jobId}` }]; + } + } + }); + break; + + case "tag-removed": + client.cache.modify({ + id: client.cache.identify({ + __typename: "conversations", + id: conversationId + }), + fields: { + job_conversations(existingJobConversations = []) { + return existingJobConversations.filter((jobRef) => jobRef.__ref !== `jobs:${jobId}`); + } + } + }); + break; + + case "label-changed": + client.cache.modify({ + id: client.cache.identify({ + __typename: "conversations", + id: conversationId + }), + fields: { + label() { + return label; + } + } + }); + break; + + default: + console.warn(`Unhandled conversation change type: ${type}`); + } }; socket.on("new-message-summary", handleNewMessageSummary); socket.on("new-message-detailed", handleNewMessageDetailed); socket.on("message-changed", handleMessageChanged); - socket.on("conversation-changed", handleConversationChanged); //TODO: Unread, mark as read, archived, unarchive, etc. + socket.on("conversation-changed", handleConversationChanged); }; export const unregisterMessagingHandlers = ({ socket }) => { diff --git a/client/src/components/chat-conversation/chat-conversation.container.jsx b/client/src/components/chat-conversation/chat-conversation.container.jsx index 9a57a43e2..58107ad7c 100644 --- a/client/src/components/chat-conversation/chat-conversation.container.jsx +++ b/client/src/components/chat-conversation/chat-conversation.container.jsx @@ -30,6 +30,9 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) { const { socket } = useContext(SocketContext); useEffect(() => { + // Early gate, we have no socket, bail. + if (!socket || !socket.connected) return; + socket.emit("join-bodyshop-conversation", { bodyshopId: bodyshop.id, conversationId: selectedConversation @@ -43,10 +46,6 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) { }; }, [selectedConversation, bodyshop, socket]); - // const { loading, error, data } = useSubscription(CONVERSATION_SUBSCRIPTION_BY_PK, { - // variables: { conversationId: selectedConversation } - // }); - const [markingAsReadInProgress, setMarkingAsReadInProgress] = useState(false); const unreadCount = @@ -60,10 +59,10 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) { const handleMarkConversationAsRead = async () => { if (unreadCount > 0 && !!selectedConversation && !markingAsReadInProgress) { setMarkingAsReadInProgress(true); - // await markConversationRead({}); await axios.post("/sms/markConversationRead", { conversationid: selectedConversation, - imexshopid: bodyshop.imexshopid + imexshopid: bodyshop.imexshopid, + bodyshopid: bodyshop.id }); setMarkingAsReadInProgress(false); } diff --git a/server/sms/status.js b/server/sms/status.js index 63c45b33e..9527ed5f8 100644 --- a/server/sms/status.js +++ b/server/sms/status.js @@ -5,29 +5,31 @@ require("dotenv").config({ const client = require("../graphql-client/graphql-client").client; const queries = require("../graphql-client/queries"); -const { phone } = require("phone"); const logger = require("../utils/logger"); -const { admin } = require("../firebase/firebase-handler"); -exports.status = (req, res) => { +exports.status = async (req, res) => { const { SmsSid, SmsStatus } = req.body; const { ioRedis, ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom } } = req; - client - .request(queries.UPDATE_MESSAGE_STATUS, { + + try { + // Update message status in the database + const response = await client.request(queries.UPDATE_MESSAGE_STATUS, { msid: SmsSid, fields: { status: SmsStatus } - }) - .then((response) => { + }); + + const message = response.update_messages.returning[0]; + + if (message) { logger.log("sms-status-update", "DEBUG", "api", null, { msid: SmsSid, fields: { status: SmsStatus } }); - // TODO Verify - const message = response.update_messages.returning[0]; + // Emit WebSocket event to notify the change in message status const conversationRoom = getBodyshopConversationRoom({ bodyshopId: message.conversation.bodyshopid, conversationId: message.conversationid @@ -36,69 +38,70 @@ exports.status = (req, res) => { ioRedis.to(conversationRoom).emit("message-changed", { message }); - }) - .catch((error) => { - logger.log("sms-status-update-error", "ERROR", "api", null, { + } else { + logger.log("sms-status-update-warning", "WARN", "api", null, { msid: SmsSid, fields: { status: SmsStatus }, - error + warning: "No message returned from the database update." }); + } + res.sendStatus(200); + } catch (error) { + logger.log("sms-status-update-error", "ERROR", "api", null, { + msid: SmsSid, + fields: { status: SmsStatus }, + error }); - res.sendStatus(200); + res.status(500).json({ error: "Failed to update message status." }); + } }; exports.markConversationRead = async (req, res) => { - const { conversationid, imexshopid } = req.body; + const { conversationid, imexshopid, bodyshopid } = req.body; const { ioRedis, ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom } } = req; - //Server side, mark the conversation as read + try { + // Mark messages in the conversation as read + const response = await client.request(queries.MARK_MESSAGES_AS_READ, { + conversationId: conversationid + }); - //TODO: Convert this to run on server side. Stolen from chat-conversation.container.jsx - // const [markConversationRead] = useMutation(MARK_MESSAGES_AS_READ_BY_CONVERSATION, { - // variables: { conversationId: selectedConversation }, - // refetchQueries: ["UNREAD_CONVERSATION_COUNT"], - // - // update(cache) { - // cache.modify({ - // id: cache.identify({ - // __typename: "conversations", - // id: selectedConversation - // }), - // fields: { - // messages_aggregate(cached) { - // return { aggregate: { count: 0 } }; - // } - // } - // }); - // } - // }); + const updatedMessages = response.update_messages.affected_rows; - const broadcastRoom = getBodyshopRoom(r2.insert_messages.returning[0].conversation.bodyshop.id); + logger.log("conversation-mark-read", "DEBUG", "api", null, { + conversationid, + imexshopid, + bodyshopid, + updatedMessages + }); - ioRedis.to(broadcastRoom).emit("conversation-changed", { - //type: "conversation-marked-unread" //TODO: Flush out what this looks like. - // isoutbound: true, - // conversationId: conversationid, - // updated_at: r2.insert_messages.returning[0].updated_at, - // msid: message.sid, - // summary: true - }); + const broadcastRoom = getBodyshopRoom(bodyshopid); - res.send(200); + const conversationRoom = getBodyshopConversationRoom({ + bodyshopId: bodyshopid, + conversationId: conversationid + }); + + ioRedis.to(broadcastRoom).emit("conversation-changed", { + type: "conversation-marked-read", + conversationId: conversationid + }); + + ioRedis.to(conversationRoom).emit("message-changed", { + type: "all-messages-marked-read", + conversationId: conversationid + }); + + res.status(200).json({ success: true, message: "Conversation marked as read." }); + } catch (error) { + logger.log("conversation-mark-read-error", "ERROR", "api", null, { + conversationid, + imexshopid, + error + }); + res.status(500).json({ error: "Failed to mark conversation as read." }); + } }; - -// Inbound Sample -// { -// "SmsSid": "SM5205ea340e06437799d9345e7283457c", -// "SmsStatus": "queued", -// "MessageStatus": "queued", -// "To": "+16049992002", -// "MessagingServiceSid": "MG6e259e2add04ffa0d0aa355038670ee1", -// "MessageSid": "SM5205ea340e06437799d9345e7283457c", -// "AccountSid": "AC6c09d337d6b9c68ab6488c2052bd457c", -// "From": "+16043301606", -// "ApiVersion": "2010-04-01" -// } From f2d96268889e53b6d8a5d2fdf7a8d745f5a0574f Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 20 Nov 2024 12:43:03 -0800 Subject: [PATCH 12/74] IO-3001 Reverse n_ttl and g_ttl logic for audatex estimates. --- server/job/job-totals-USA.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/job/job-totals-USA.js b/server/job/job-totals-USA.js index 2ebf5910f..0b2c530af 100644 --- a/server/job/job-totals-USA.js +++ b/server/job/job-totals-USA.js @@ -71,8 +71,8 @@ async function TotalsServerSide(req, res) { // Sub total scrubbbing. const emsTotal = job.cieca_ttl.data.n_ttl_amt === job.cieca_ttl.data.g_ttl_amt //It looks like sometimes, gross and net are the same, but they shouldn't be. - ? job.cieca_ttl.data.g_ttl_amt - job.cieca_ttl.data.g_tax //If they are, adjust the gross total down by the tax amount. - : job.cieca_ttl.data.n_ttl_amt - job.cieca_ttl.data.g_tax; + ? job.cieca_ttl.data.n_ttl_amt - job.cieca_ttl.data.g_tax + : job.cieca_ttl.data.g_ttl_amt - job.cieca_ttl.data.g_tax; //If they are, adjust the gross total down by the tax amount. const ttlDifference = emsTotal - ret.totals.subtotal.getAmount() / 100; if (Math.abs(ttlDifference) > 0.0) { From fbc7168bde0b8d23a47f94d577e8e511cbefc34d Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 20 Nov 2024 14:03:34 -0800 Subject: [PATCH 13/74] IO-3001 branch cleanup --- .../job-lines-part-price-change.component.jsx | 1 - .../jobs-available-table.container.jsx | 6 ++++-- server/job/job-totals-USA.js | 8 ++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/client/src/components/job-detail-lines/job-lines-part-price-change.component.jsx b/client/src/components/job-detail-lines/job-lines-part-price-change.component.jsx index 13798eb0e..6d03fb3ac 100644 --- a/client/src/components/job-detail-lines/job-lines-part-price-change.component.jsx +++ b/client/src/components/job-detail-lines/job-lines-part-price-change.component.jsx @@ -83,7 +83,6 @@ export function JobLinesPartPriceChange({ job, line, refetch, technician }) { return ( - {import.meta.env.DEV && } {line.db_ref === "900510" || line.db_ref === "900511" ? line.prt_dsmk_m : line.act_price} 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 6266e2dc5..8e9df7fe3 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 @@ -408,7 +408,8 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail updateSchComp={updateSchComp} setSchComp={setSchComp} /> - {currentUser.email.includes("@rome.") || currentUser.email.includes("@imex.") ? ( + { + {/* currentUser.email.includes("@rome.") || currentUser.email.includes("@imex.") ? ( - ) : null} + ) : null */} + } { try { From 250faa672fd2997f728869a29f31876d77f03ed1 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 20 Nov 2024 14:53:01 -0800 Subject: [PATCH 14/74] feature/IO-3000-messaging-sockets-migrations2 - Updated Polling Intervals is now socket based over FCMToken based Signed-off-by: Dave Richer --- .../chat-popup/chat-popup.component.jsx | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/client/src/components/chat-popup/chat-popup.component.jsx b/client/src/components/chat-popup/chat-popup.component.jsx index b9a500166..b2625261d 100644 --- a/client/src/components/chat-popup/chat-popup.component.jsx +++ b/client/src/components/chat-popup/chat-popup.component.jsx @@ -1,7 +1,7 @@ import { InfoCircleOutlined, MessageOutlined, ShrinkOutlined, SyncOutlined } from "@ant-design/icons"; import { useLazyQuery, useQuery } from "@apollo/client"; import { Badge, Card, Col, Row, Space, Tag, Tooltip, Typography } from "antd"; -import React, { useCallback, useEffect, useState } from "react"; +import React, { useCallback, useContext, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; @@ -13,18 +13,21 @@ import ChatConversationContainer from "../chat-conversation/chat-conversation.co import ChatNewConversation from "../chat-new-conversation/chat-new-conversation.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import "./chat-popup.styles.scss"; +import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; const mapStateToProps = createStructuredSelector({ selectedConversation: selectSelectedConversation, chatVisible: selectChatVisible }); + const mapDispatchToProps = (dispatch) => ({ toggleChatVisible: () => dispatch(toggleChatVisible()) }); export function ChatPopupComponent({ chatVisible, selectedConversation, toggleChatVisible }) { const { t } = useTranslation(); - const [pollInterval, setpollInterval] = useState(0); + const [pollInterval, setPollInterval] = useState(0); + const { socket } = useContext(SocketContext); const { data: unreadData } = useQuery(UNREAD_CONVERSATION_COUNT, { fetchPolicy: "network-only", @@ -39,16 +42,29 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh ...(pollInterval > 0 ? { pollInterval } : {}) }); - const fcmToken = sessionStorage.getItem("fcmtoken"); - - //TODO: Change to be a fallback incase sockets shit the bed useEffect(() => { - if (fcmToken) { - setpollInterval(0); - } else { - setpollInterval(90000); + const handleSocketStatus = () => { + if (socket?.connected) { + setPollInterval(15 * 60 * 1000); // 15 minutes + } else { + setPollInterval(60 * 1000); // 60 seconds + } + }; + + handleSocketStatus(); + + if (socket) { + socket.on("connect", handleSocketStatus); + socket.on("disconnect", handleSocketStatus); } - }, [fcmToken]); + + return () => { + if (socket) { + socket.off("connect", handleSocketStatus); + socket.off("disconnect", handleSocketStatus); + } + }; + }, [socket]); useEffect(() => { if (chatVisible) @@ -56,6 +72,8 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh variables: { offset: 0 } + }).catch((err) => { + console.error(`Error fetching conversations: ${(err, err.message || "")}`); }); }, [chatVisible, getConversations]); @@ -65,10 +83,12 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh variables: { offset: data.conversations.length } + }).catch((err) => { + console.error(`Error fetching more conversations: ${(err, err.message || "")}`); }); }, [data, fetchMore]); - const unreadCount = unreadData?.messages_aggregate.aggregate.count || 0; + const unreadCount = unreadData?.messages_aggregate?.aggregate?.count || 0; return ( From 06afd6da5bdff46aa63bad3307e5ea501164c7a3 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 20 Nov 2024 18:22:27 -0800 Subject: [PATCH 15/74] feature/IO-3000-messaging-sockets-migrations2 - - Conversation Labels Synced - Job Tagging Synced Signed-off-by: Dave Richer --- .../registerMessagingSocketHandlers.js | 97 +++++++------------ ...chat-conversation-title-tags.component.jsx | 16 ++- .../chat-conversation-title.component.jsx | 11 ++- .../chat-conversation.component.jsx | 10 +- .../chat-conversation.container.jsx | 1 + .../chat-label/chat-label.component.jsx | 14 ++- .../chat-tag-ro/chat-tag-ro.container.jsx | 32 +++++- client/src/utils/GraphQLClient.js | 17 ++++ server/web-sockets/redisSocketEvents.js | 19 +++- 9 files changed, 142 insertions(+), 75 deletions(-) diff --git a/client/src/components/chat-affix/registerMessagingSocketHandlers.js b/client/src/components/chat-affix/registerMessagingSocketHandlers.js index 339acd9a6..120c184dd 100644 --- a/client/src/components/chat-affix/registerMessagingSocketHandlers.js +++ b/client/src/components/chat-affix/registerMessagingSocketHandlers.js @@ -97,70 +97,45 @@ export const registerMessagingHandlers = ({ socket, client }) => { }; const handleConversationChanged = (data) => { - const { type, conversationId, jobId, label } = data; + const { conversationId, type, job_conversations, ...fields } = data; - switch (type) { - case "conversation-marked-read": - client.cache.modify({ - id: client.cache.identify({ - __typename: "conversations", - id: conversationId - }), - fields: { - messages_aggregate: () => ({ aggregate: { count: 0 } }) - } - }); + // Identify the conversation in the Apollo cache + const cacheId = client.cache.identify({ + __typename: "conversations", + id: conversationId + }); - // Optionally, refetch queries if needed - // client.refetchQueries({ - // include: [CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS] - // }); - break; - case "tag-added": - client.cache.modify({ - id: client.cache.identify({ - __typename: "conversations", - id: conversationId - }), - fields: { - job_conversations(existingJobConversations = []) { - return [...existingJobConversations, { __ref: `jobs:${jobId}` }]; - } - } - }); - break; - - case "tag-removed": - client.cache.modify({ - id: client.cache.identify({ - __typename: "conversations", - id: conversationId - }), - fields: { - job_conversations(existingJobConversations = []) { - return existingJobConversations.filter((jobRef) => jobRef.__ref !== `jobs:${jobId}`); - } - } - }); - break; - - case "label-changed": - client.cache.modify({ - id: client.cache.identify({ - __typename: "conversations", - id: conversationId - }), - fields: { - label() { - return label; - } - } - }); - break; - - default: - console.warn(`Unhandled conversation change type: ${type}`); + if (!cacheId) { + console.error(`Could not find conversation with id: ${conversationId}`); + return; } + + client.cache.modify({ + id: cacheId, + fields: { + ...Object.fromEntries( + Object.entries(fields).map(([key, value]) => [ + key, + (cached) => (value !== undefined ? value : cached) // Update with new value or keep existing + ]) + ), + ...(type === "conversation-marked-read" && { + messages_aggregate: () => ({ + aggregate: { count: 0 } // Reset unread count + }) + }), + ...(type === "tag-added" && { + job_conversations: (existing = []) => { + // Merge existing job_conversations with new ones + return [...existing, ...job_conversations]; + } + }), + ...(type === "tag-removed" && { + job_conversations: (existing = [], { readField }) => + existing.filter((jobConversationRef) => readField("jobid", jobConversationRef) !== data.jobId) + }) + } + }); }; socket.on("new-message-summary", handleNewMessageSummary); diff --git a/client/src/components/chat-conversation-title-tags/chat-conversation-title-tags.component.jsx b/client/src/components/chat-conversation-title-tags/chat-conversation-title-tags.component.jsx index d851e6c92..944ade8b0 100644 --- a/client/src/components/chat-conversation-title-tags/chat-conversation-title-tags.component.jsx +++ b/client/src/components/chat-conversation-title-tags/chat-conversation-title-tags.component.jsx @@ -1,13 +1,15 @@ import { useMutation } from "@apollo/client"; import { Tag } from "antd"; -import React from "react"; +import React, { useContext } from "react"; import { Link } from "react-router-dom"; import { logImEXEvent } from "../../firebase/firebase.utils"; import { REMOVE_CONVERSATION_TAG } from "../../graphql/job-conversations.queries"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; +import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; -export default function ChatConversationTitleTags({ jobConversations }) { +export default function ChatConversationTitleTags({ jobConversations, bodyshop }) { const [removeJobConversation] = useMutation(REMOVE_CONVERSATION_TAG); + const { socket } = useContext(SocketContext); const handleRemoveTag = (jobId) => { const convId = jobConversations[0].conversationid; @@ -27,6 +29,16 @@ export default function ChatConversationTitleTags({ jobConversations }) { } }); } + }).then(() => { + if (socket) { + // Emit the `conversation-modified` event + socket.emit("conversation-modified", { + bodyshopId: bodyshop.id, + conversationId: convId, + type: "tag-removed", + jobId: jobId + }); + } }); logImEXEvent("messaging_remove_job_tag", { conversationId: convId, diff --git a/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx b/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx index 41cf0b441..243f699a0 100644 --- a/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx +++ b/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx @@ -7,14 +7,17 @@ import ChatLabelComponent from "../chat-label/chat-label.component"; import ChatPrintButton from "../chat-print-button/chat-print-button.component"; import ChatTagRoContainer from "../chat-tag-ro/chat-tag-ro.container"; -export default function ChatConversationTitle({ conversation }) { +export default function ChatConversationTitle({ conversation, bodyshop }) { return ( {conversation && conversation.phone_num} - + - - + + ); diff --git a/client/src/components/chat-conversation/chat-conversation.component.jsx b/client/src/components/chat-conversation/chat-conversation.component.jsx index d4a9695c0..32e52d5f2 100644 --- a/client/src/components/chat-conversation/chat-conversation.component.jsx +++ b/client/src/components/chat-conversation/chat-conversation.component.jsx @@ -6,7 +6,13 @@ import ChatSendMessage from "../chat-send-message/chat-send-message.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx"; import "./chat-conversation.styles.scss"; -export default function ChatConversationComponent({ subState, conversation, messages, handleMarkConversationAsRead }) { +export default function ChatConversationComponent({ + subState, + conversation, + messages, + handleMarkConversationAsRead, + bodyshop +}) { const [loading, error] = subState; if (loading) return ; @@ -18,7 +24,7 @@ export default function ChatConversationComponent({ subState, conversation, mess onMouseDown={handleMarkConversationAsRead} onKeyDown={handleMarkConversationAsRead} > - +
diff --git a/client/src/components/chat-conversation/chat-conversation.container.jsx b/client/src/components/chat-conversation/chat-conversation.container.jsx index 58107ad7c..0fb60aea4 100644 --- a/client/src/components/chat-conversation/chat-conversation.container.jsx +++ b/client/src/components/chat-conversation/chat-conversation.container.jsx @@ -74,6 +74,7 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) { conversation={convoData ? convoData.conversations_by_pk : {}} messages={convoData ? convoData.conversations_by_pk.messages : []} handleMarkConversationAsRead={handleMarkConversationAsRead} + bodyshop={bodyshop} /> ); } diff --git a/client/src/components/chat-label/chat-label.component.jsx b/client/src/components/chat-label/chat-label.component.jsx index 7157d02c8..e577bbf23 100644 --- a/client/src/components/chat-label/chat-label.component.jsx +++ b/client/src/components/chat-label/chat-label.component.jsx @@ -1,14 +1,16 @@ import { PlusOutlined } from "@ant-design/icons"; import { useMutation } from "@apollo/client"; import { Input, notification, Spin, Tag, Tooltip } from "antd"; -import React, { useState } from "react"; +import React, { useContext, useState } from "react"; import { useTranslation } from "react-i18next"; import { UPDATE_CONVERSATION_LABEL } from "../../graphql/conversations.queries"; +import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; -export default function ChatLabel({ conversation }) { +export default function ChatLabel({ conversation, bodyshop }) { const [loading, setLoading] = useState(false); const [editing, setEditing] = useState(false); const [value, setValue] = useState(conversation.label); + const { socket } = useContext(SocketContext); const { t } = useTranslation(); const [updateLabel] = useMutation(UPDATE_CONVERSATION_LABEL); @@ -26,6 +28,14 @@ export default function ChatLabel({ conversation }) { }) }); } else { + if (socket) { + socket.emit("conversation-modified", { + type: "label-updated", + conversationId: conversation.id, + bodyshopId: bodyshop.id, + label: value + }); + } setEditing(false); } } catch (error) { diff --git a/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx b/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx index f9b6fa5ad..6e01ea5ed 100644 --- a/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx +++ b/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx @@ -2,16 +2,18 @@ import { PlusOutlined } from "@ant-design/icons"; import { useLazyQuery, useMutation } from "@apollo/client"; import { Tag } from "antd"; import _ from "lodash"; -import React, { useState } from "react"; +import React, { useContext, useState } from "react"; import { useTranslation } from "react-i18next"; import { logImEXEvent } from "../../firebase/firebase.utils"; import { INSERT_CONVERSATION_TAG } from "../../graphql/job-conversations.queries"; import { SEARCH_FOR_JOBS } from "../../graphql/jobs.queries"; import ChatTagRo from "./chat-tag-ro.component"; +import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; -export default function ChatTagRoContainer({ conversation }) { +export default function ChatTagRoContainer({ conversation, bodyshop }) { const { t } = useTranslation(); const [open, setOpen] = useState(false); + const { socket } = useContext(SocketContext); const [loadRo, { loading, data }] = useLazyQuery(SEARCH_FOR_JOBS); @@ -32,7 +34,31 @@ export default function ChatTagRoContainer({ conversation }) { const handleInsertTag = (value, option) => { logImEXEvent("messaging_add_job_tag"); - insertTag({ variables: { jobId: option.key } }); + + insertTag({ + variables: { jobId: option.key } + }).then(() => { + if (socket) { + // Find the job details from the search data + const selectedJob = data?.search_jobs.find((job) => job.id === option.key); + if (!selectedJob) return; + const newJobConversation = { + __typename: "job_conversations", + jobid: selectedJob.id, + conversationid: conversation.id, + job: { + __typename: "jobs", + ...selectedJob + } + }; + socket.emit("conversation-modified", { + conversationId: conversation.id, + bodyshopId: bodyshop.id, + type: "tag-added", + job_conversations: [newJobConversation] + }); + } + }); setOpen(false); }; diff --git a/client/src/utils/GraphQLClient.js b/client/src/utils/GraphQLClient.js index 789bfdf10..e0d409bd7 100644 --- a/client/src/utils/GraphQLClient.js +++ b/client/src/utils/GraphQLClient.js @@ -149,6 +149,23 @@ const cache = new InMemoryCache({ fields: { conversations: offsetLimitPagination() } + }, + conversations: { + fields: { + job_conversations: { + keyArgs: false, // Indicates that all job_conversations share the same key + merge(existing = [], incoming) { + // Merge existing and incoming job_conversations + const merged = [ + ...existing, + ...incoming.filter( + (incomingItem) => !existing.some((existingItem) => existingItem.__ref === incomingItem.__ref) + ) + ]; + return merged; + } + } + } } } }); diff --git a/server/web-sockets/redisSocketEvents.js b/server/web-sockets/redisSocketEvents.js index 8e1358994..d2dd9eb7b 100644 --- a/server/web-sockets/redisSocketEvents.js +++ b/server/web-sockets/redisSocketEvents.js @@ -145,7 +145,6 @@ const redisSocketEvents = ({ error: error.message, stack: error.stack }); - socket.emit("error", { message: "Failed to join conversation" }); } }; const leaveConversationRoom = ({ bodyshopId, conversationId }) => { @@ -162,6 +161,24 @@ const redisSocketEvents = ({ } }; + const conversationModified = ({ bodyshopId, conversationId, ...fields }) => { + try { + // Retrieve the room name for the conversation + const room = getBodyshopConversationRoom({ bodyshopId, conversationId }); + // Emit the updated data to all clients in the room + io.to(room).emit("conversation-changed", { + conversationId, + ...fields + }); + } catch (error) { + logger.log("Failed to handle conversation modification", "error", "io-redis", null, { + error: error.message, + stack: error.stack + }); + } + }; + + socket.on("conversation-modified", conversationModified); socket.on("join-bodyshop-conversation", joinConversationRoom); socket.on("leave-bodyshop-conversation", leaveConversationRoom); }; From 15151cb4ac0bb943cda1125cbf51e44383632259 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 20 Nov 2024 19:23:35 -0800 Subject: [PATCH 16/74] feature/IO-3000-messaging-sockets-migrations2 - - sync send - fix status events Signed-off-by: Dave Richer --- .../registerMessagingSocketHandlers.js | 78 +++++++++++++++---- .../chat-send-message.component.jsx | 17 +++- server/sms/status.js | 14 +--- server/web-sockets/redisSocketEvents.js | 13 ++++ 4 files changed, 92 insertions(+), 30 deletions(-) diff --git a/client/src/components/chat-affix/registerMessagingSocketHandlers.js b/client/src/components/chat-affix/registerMessagingSocketHandlers.js index 120c184dd..44b79c3b3 100644 --- a/client/src/components/chat-affix/registerMessagingSocketHandlers.js +++ b/client/src/components/chat-affix/registerMessagingSocketHandlers.js @@ -1,4 +1,5 @@ import { CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries"; +import { gql } from "@apollo/client"; export const registerMessagingHandlers = ({ socket, client }) => { if (!(socket && client)) return; @@ -14,8 +15,6 @@ export const registerMessagingHandlers = ({ socket, client }) => { const fullConversation = { ...newConversation, - phone_num: newConversation.phone_num, - id: newConversation.id, updated_at: newConversation.updated_at || new Date().toISOString(), unreadcnt: newConversation.unreadcnt || 0, archived: newConversation.archived || false, @@ -77,21 +76,43 @@ export const registerMessagingHandlers = ({ socket, client }) => { }; const handleMessageChanged = (message) => { - // Find the message in the cache and update all fields dynamically client.cache.modify({ - id: client.cache.identify({ - __typename: "messages", - id: message.id - }), + id: client.cache.identify({ __typename: "conversations", id: message.conversationid }), fields: { - // Dynamically update all fields based on the incoming message object - __typename: (existingType) => existingType || "messages", // Ensure __typename is preserved - ...Object.fromEntries( - Object.entries(message).map(([key, value]) => [ - key, - (cached) => (value !== undefined ? value : cached) // Update with new value or keep existing - ]) - ) + ...(message.type === "status-changed" && { + messages(existing = [], { readField }) { + return existing.map((messageRef) => { + // Match the message by ID + if (readField("id", messageRef) === message.id) { + const currentStatus = readField("status", messageRef); + + // Prevent overwriting if the current status is already "delivered" + if (currentStatus === "delivered") { + return messageRef; + } + + // Update the existing message fields + return client.cache.writeFragment({ + id: messageRef.__ref, + fragment: gql` + fragment UpdatedMessage on messages { + id + status + conversationid + __typename + } + `, + data: { + __typename: "messages", + ...message // Only update the fields provided in the message object + } + }); + } + + return messageRef; // Keep other messages unchanged + }); + } + }) } }); }; @@ -113,6 +134,7 @@ export const registerMessagingHandlers = ({ socket, client }) => { client.cache.modify({ id: cacheId, fields: { + // This is a catch-all for just sending it fields off conversation ...Object.fromEntries( Object.entries(fields).map(([key, value]) => [ key, @@ -138,6 +160,30 @@ export const registerMessagingHandlers = ({ socket, client }) => { }); }; + const handleNewMessage = ({ conversationId, message }) => { + client.cache.modify({ + id: client.cache.identify({ __typename: "conversations", id: conversationId }), + fields: { + messages(existing = []) { + const newMessageRef = client.cache.writeFragment({ + data: message, + fragment: gql` + fragment NewMessage on messages { + id + body + createdAt + selectedMedia + imexshopid + } + ` + }); + return [...existing, newMessageRef]; + } + } + }); + }; + + socket.on("new-message", handleNewMessage); socket.on("new-message-summary", handleNewMessageSummary); socket.on("new-message-detailed", handleNewMessageDetailed); socket.on("message-changed", handleMessageChanged); @@ -146,9 +192,9 @@ export const registerMessagingHandlers = ({ socket, client }) => { export const unregisterMessagingHandlers = ({ socket }) => { if (!socket) return; + socket.off("new-message"); socket.off("new-message-summary"); socket.off("new-message-detailed"); socket.off("message-changed"); - socket.off("message-changed"); socket.off("conversation-changed"); }; diff --git a/client/src/components/chat-send-message/chat-send-message.component.jsx b/client/src/components/chat-send-message/chat-send-message.component.jsx index 918be1b5f..2ea90322e 100644 --- a/client/src/components/chat-send-message/chat-send-message.component.jsx +++ b/client/src/components/chat-send-message/chat-send-message.component.jsx @@ -1,6 +1,6 @@ import { LoadingOutlined, SendOutlined } from "@ant-design/icons"; import { Input, Spin } from "antd"; -import React, { useEffect, useRef, useState } from "react"; +import React, { useContext, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; @@ -10,6 +10,7 @@ import { selectIsSending, selectMessage } from "../../redux/messaging/messaging. import { selectBodyshop } from "../../redux/user/user.selectors"; import ChatMediaSelector from "../chat-media-selector/chat-media-selector.component"; import ChatPresetsComponent from "../chat-presets/chat-presets.component"; +import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -25,6 +26,8 @@ const mapDispatchToProps = (dispatch) => ({ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSending, message, setMessage }) { const inputArea = useRef(null); const [selectedMedia, setSelectedMedia] = useState([]); + const { socket } = useContext(SocketContext); + useEffect(() => { inputArea.current.focus(); }, [isSending, setMessage]); @@ -37,14 +40,22 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi logImEXEvent("messaging_send_message"); if (selectedImages.length < 11) { - sendMessage({ + const newMessage = { to: conversation.phone_num, body: message || "", messagingServiceSid: bodyshop.messagingservicesid, conversationid: conversation.id, selectedMedia: selectedImages, imexshopid: bodyshop.imexshopid - }); + }; + sendMessage(newMessage); + if (socket) { + socket.emit("message-added", { + conversationId: conversation.id, + bodyshopId: bodyshop.id, + message: newMessage + }); + } setSelectedMedia( selectedMedia.map((i) => { return { ...i, isSelected: false }; diff --git a/server/sms/status.js b/server/sms/status.js index 9527ed5f8..bfdeb5ed2 100644 --- a/server/sms/status.js +++ b/server/sms/status.js @@ -36,7 +36,9 @@ exports.status = async (req, res) => { }); ioRedis.to(conversationRoom).emit("message-changed", { - message + ...message, + status: SmsStatus, + type: "status-changed" }); } else { logger.log("sms-status-update-warning", "WARN", "api", null, { @@ -80,21 +82,11 @@ exports.markConversationRead = async (req, res) => { const broadcastRoom = getBodyshopRoom(bodyshopid); - const conversationRoom = getBodyshopConversationRoom({ - bodyshopId: bodyshopid, - conversationId: conversationid - }); - ioRedis.to(broadcastRoom).emit("conversation-changed", { type: "conversation-marked-read", conversationId: conversationid }); - ioRedis.to(conversationRoom).emit("message-changed", { - type: "all-messages-marked-read", - conversationId: conversationid - }); - res.status(200).json({ success: true, message: "Conversation marked as read." }); } catch (error) { logger.log("conversation-mark-read-error", "ERROR", "api", null, { diff --git a/server/web-sockets/redisSocketEvents.js b/server/web-sockets/redisSocketEvents.js index d2dd9eb7b..33faab361 100644 --- a/server/web-sockets/redisSocketEvents.js +++ b/server/web-sockets/redisSocketEvents.js @@ -178,6 +178,19 @@ const redisSocketEvents = ({ } }; + const messageAdded = ({ bodyshopId, conversationId, message }) => { + try { + const room = getBodyshopConversationRoom({ bodyshopId, conversationId }); + io.to(room).emit("new-message", message); + } catch (error) { + logger.log("Failed to handle new message", "error", "io-redis", null, { + error: error.message, + stack: error.stack + }); + } + }; + + socket.on("message-added", messageAdded); socket.on("conversation-modified", conversationModified); socket.on("join-bodyshop-conversation", joinConversationRoom); socket.on("leave-bodyshop-conversation", leaveConversationRoom); From 5392659db6d8b846bbf7ee2c83bba589bfea9a79 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 21 Nov 2024 11:32:43 -0800 Subject: [PATCH 17/74] feature/IO-3000-messaging-sockets-migrations2 - - Checkpoint Signed-off-by: Dave Richer --- .../registerMessagingSocketHandlers.js | 92 +++++++++++++++---- .../chat-archive-button.component.jsx | 20 +++- .../chat-conversation-title.component.jsx | 2 +- .../chat-conversation.container.jsx | 1 - .../chat-send-message.component.jsx | 3 +- server/sms/status.js | 6 ++ server/web-sockets/redisSocketEvents.js | 2 +- 7 files changed, 100 insertions(+), 26 deletions(-) diff --git a/client/src/components/chat-affix/registerMessagingSocketHandlers.js b/client/src/components/chat-affix/registerMessagingSocketHandlers.js index 44b79c3b3..ab4473e97 100644 --- a/client/src/components/chat-affix/registerMessagingSocketHandlers.js +++ b/client/src/components/chat-affix/registerMessagingSocketHandlers.js @@ -1,35 +1,45 @@ import { CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries"; import { gql } from "@apollo/client"; +const logLocal = (message, ...args) => { + if (import.meta.env.PROD) { + return; + } + console.log(`==================== ${message} ====================`); + console.dir({ ...args }); +}; + export const registerMessagingHandlers = ({ socket, client }) => { if (!(socket && client)) return; const handleNewMessageSummary = (message) => { const { conversationId, newConversation, existingConversation, isoutbound } = message; + logLocal("handleNewMessageSummary", message); + if (!existingConversation && newConversation?.phone_num) { const queryResults = client.cache.readQuery({ query: CONVERSATION_LIST_QUERY, variables: { offset: 0 } }); - - const fullConversation = { - ...newConversation, - updated_at: newConversation.updated_at || new Date().toISOString(), - unreadcnt: newConversation.unreadcnt || 0, - archived: newConversation.archived || false, - label: newConversation.label || null, - job_conversations: newConversation.job_conversations || [], - messages_aggregate: newConversation.messages_aggregate || { - aggregate: { count: isoutbound ? 0 : 1 } - } - }; - client.cache.writeQuery({ query: CONVERSATION_LIST_QUERY, variables: { offset: 0 }, data: { - conversations: [fullConversation, ...(queryResults?.conversations || [])] + conversations: [ + { + ...newConversation, + updated_at: newConversation.updated_at || new Date().toISOString(), + unreadcnt: newConversation.unreadcnt || 0, + archived: newConversation.archived || false, + label: newConversation.label || null, + job_conversations: newConversation.job_conversations || [], + messages_aggregate: newConversation.messages_aggregate || { + aggregate: { count: isoutbound ? 0 : 1 } + } + }, + ...(queryResults?.conversations || []) + ] } }); } else { @@ -51,9 +61,12 @@ export const registerMessagingHandlers = ({ socket, client }) => { }); } }; + const handleNewMessageDetailed = (message) => { const { conversationId, newMessage } = message; + logLocal("handleNewMessageDetailed", message); + // Append the new message to the conversation's message list const queryResults = client.cache.readQuery({ query: GET_CONVERSATION_DETAILS, @@ -76,6 +89,10 @@ export const registerMessagingHandlers = ({ socket, client }) => { }; const handleMessageChanged = (message) => { + if (!message) return; + + logLocal("handleMessageChanged", message); + client.cache.modify({ id: client.cache.identify({ __typename: "conversations", id: message.conversationid }), fields: { @@ -118,8 +135,12 @@ export const registerMessagingHandlers = ({ socket, client }) => { }; const handleConversationChanged = (data) => { + if (!data) return; + const { conversationId, type, job_conversations, ...fields } = data; + logLocal("handleConversationChanged", data); + // Identify the conversation in the Apollo cache const cacheId = client.cache.identify({ __typename: "conversations", @@ -161,23 +182,60 @@ export const registerMessagingHandlers = ({ socket, client }) => { }; const handleNewMessage = ({ conversationId, message }) => { + if (!conversationId || !message.id || !message.text) { + return; + } + + logLocal("handleNewMessage", { conversationId, message }); + client.cache.modify({ id: client.cache.identify({ __typename: "conversations", id: conversationId }), fields: { messages(existing = []) { + // Ensure that the `message` object matches the schema const newMessageRef = client.cache.writeFragment({ - data: message, + data: { + __typename: "messages", + id: message.id, + body: message.text, + selectedMedia: message.image_path || [], + imexshopid: message.userid, + status: message.status, + created_at: message.created_at, + read: message.read + }, fragment: gql` fragment NewMessage on messages { id body - createdAt selectedMedia imexshopid + status + created_at + read } ` }); - return [...existing, newMessageRef]; + + // Prevent duplicates by checking if the message already exists + const isDuplicate = existing.some( + (msgRef) => + client.cache.readFragment({ + id: msgRef.__ref, + fragment: gql` + fragment CheckMessage on messages { + id + } + ` + })?.id === message.id + ); + + // We already have it, so return the existing list + if (isDuplicate) { + return existing; + } + + return [...existing, newMessageRef]; // Add the new message reference } } }); diff --git a/client/src/components/chat-archive-button/chat-archive-button.component.jsx b/client/src/components/chat-archive-button/chat-archive-button.component.jsx index 755e8f514..f7ed348c1 100644 --- a/client/src/components/chat-archive-button/chat-archive-button.component.jsx +++ b/client/src/components/chat-archive-button/chat-archive-button.component.jsx @@ -1,21 +1,31 @@ import { useMutation } from "@apollo/client"; import { Button } from "antd"; -import React, { useState } from "react"; +import React, { useContext, useState } from "react"; import { useTranslation } from "react-i18next"; import { TOGGLE_CONVERSATION_ARCHIVE } from "../../graphql/conversations.queries"; +import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; -export default function ChatArchiveButton({ conversation }) { +export default function ChatArchiveButton({ conversation, bodyshop }) { const [loading, setLoading] = useState(false); const { t } = useTranslation(); const [updateConversation] = useMutation(TOGGLE_CONVERSATION_ARCHIVE); + const { socket } = useContext(SocketContext); + const handleToggleArchive = async () => { setLoading(true); - await updateConversation({ - variables: { id: conversation.id, archived: !conversation.archived }, - refetchQueries: ["CONVERSATION_LIST_QUERY"] + const updatedConversation = await updateConversation({ + variables: { id: conversation.id, archived: !conversation.archived } }); + if (socket) { + socket.emit("conversation-modified", { + conversationId: conversation.id, + bodyshopId: bodyshop.id, + archived: updatedConversation.data.update_conversations_by_pk.archived + }); + } + setLoading(false); }; diff --git a/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx b/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx index 243f699a0..7754ea347 100644 --- a/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx +++ b/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx @@ -18,7 +18,7 @@ export default function ChatConversationTitle({ conversation, bodyshop }) { bodyshop={bodyshop} /> - + ); } diff --git a/client/src/components/chat-conversation/chat-conversation.container.jsx b/client/src/components/chat-conversation/chat-conversation.container.jsx index 0fb60aea4..613d96288 100644 --- a/client/src/components/chat-conversation/chat-conversation.container.jsx +++ b/client/src/components/chat-conversation/chat-conversation.container.jsx @@ -26,7 +26,6 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) { fetchPolicy: "network-only", nextFetchPolicy: "network-only" }); - const { socket } = useContext(SocketContext); useEffect(() => { diff --git a/client/src/components/chat-send-message/chat-send-message.component.jsx b/client/src/components/chat-send-message/chat-send-message.component.jsx index 2ea90322e..e86531ee4 100644 --- a/client/src/components/chat-send-message/chat-send-message.component.jsx +++ b/client/src/components/chat-send-message/chat-send-message.component.jsx @@ -50,10 +50,11 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi }; sendMessage(newMessage); if (socket) { + const lastMessage = conversation.messages?.[conversation.messages.length - 1]; // Get the last message socket.emit("message-added", { conversationId: conversation.id, bodyshopId: bodyshop.id, - message: newMessage + message: lastMessage }); } setSelectedMedia( diff --git a/server/sms/status.js b/server/sms/status.js index bfdeb5ed2..51d2f7ecb 100644 --- a/server/sms/status.js +++ b/server/sms/status.js @@ -15,6 +15,11 @@ exports.status = async (req, res) => { } = req; try { + // Ignore status 'queued' + if (SmsStatus === "queued") { + return res.status(200).json({ message: "Status 'queued' disregarded." }); + } + // Update message status in the database const response = await client.request(queries.UPDATE_MESSAGE_STATUS, { msid: SmsSid, @@ -47,6 +52,7 @@ exports.status = async (req, res) => { warning: "No message returned from the database update." }); } + res.sendStatus(200); } catch (error) { logger.log("sms-status-update-error", "ERROR", "api", null, { diff --git a/server/web-sockets/redisSocketEvents.js b/server/web-sockets/redisSocketEvents.js index 33faab361..a8e753f2f 100644 --- a/server/web-sockets/redisSocketEvents.js +++ b/server/web-sockets/redisSocketEvents.js @@ -181,7 +181,7 @@ const redisSocketEvents = ({ const messageAdded = ({ bodyshopId, conversationId, message }) => { try { const room = getBodyshopConversationRoom({ bodyshopId, conversationId }); - io.to(room).emit("new-message", message); + io.to(room).emit("new-message", { message, conversationId }); } catch (error) { logger.log("Failed to handle new message", "error", "io-redis", null, { error: error.message, From 12aec3e3a0ab5c8df53ca3c11dead878d63290ae Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 21 Nov 2024 12:03:01 -0800 Subject: [PATCH 18/74] feature/IO-3000-messaging-sockets-migrations2 - - Fix Chat Icon logger error - Fix Socket Robustness - added additional wss status for error - Installed ant-design icons Signed-off-by: Dave Richer --- client/package-lock.json | 1 + client/package.json | 1 + .../chat-message-list.component.jsx | 52 +----- .../chat-messages-list/renderMessage.jsx | 42 +++++ .../wss-status-display.component.jsx | 29 +++- client/src/contexts/SocketIO/useSocket.js | 149 +++++++++++------- server/web-sockets/redisSocketEvents.js | 34 ++-- 7 files changed, 180 insertions(+), 128 deletions(-) create mode 100644 client/src/components/chat-messages-list/renderMessage.jsx diff --git a/client/package-lock.json b/client/package-lock.json index 93b492582..141c819b6 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -85,6 +85,7 @@ "web-vitals": "^3.5.2" }, "devDependencies": { + "@ant-design/icons": "^5.5.1", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/preset-react": "^7.24.7", "@dotenvx/dotenvx": "^1.14.1", diff --git a/client/package.json b/client/package.json index 38d148706..fdb2b6b34 100644 --- a/client/package.json +++ b/client/package.json @@ -132,6 +132,7 @@ "@rollup/rollup-linux-x64-gnu": "4.6.1" }, "devDependencies": { + "@ant-design/icons": "^5.5.1", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/preset-react": "^7.24.7", "@dotenvx/dotenvx": "^1.14.1", diff --git a/client/src/components/chat-messages-list/chat-message-list.component.jsx b/client/src/components/chat-messages-list/chat-message-list.component.jsx index 076941d8c..81d3296fc 100644 --- a/client/src/components/chat-messages-list/chat-message-list.component.jsx +++ b/client/src/components/chat-messages-list/chat-message-list.component.jsx @@ -1,11 +1,6 @@ -import Icon from "@ant-design/icons"; -import { Tooltip } from "antd"; -import i18n from "i18next"; -import dayjs from "../../utils/day"; -import React, { useRef, useEffect } from "react"; -import { MdDone, MdDoneAll } from "react-icons/md"; +import React, { useEffect, useRef } from "react"; import { Virtuoso } from "react-virtuoso"; -import { DateTimeFormatter } from "../../utils/DateFormatter"; +import { renderMessage } from "./renderMessage"; import "./chat-message-list.styles.scss"; export default function ChatMessageListComponent({ messages }) { @@ -21,7 +16,7 @@ export default function ChatMessageListComponent({ messages }) { }); } }, 100); // Delay of 100ms to allow rendering - return () => clearTimeout(timer); // Cleanup the timer on unmount + return () => clearTimeout(timer); }, [messages.length]); // Run only once on component mount // Scroll to the bottom after the new messages are rendered @@ -37,52 +32,13 @@ export default function ChatMessageListComponent({ messages }) { }, 50); // Slight delay to ensure layout recalculates } }, [messages]); // Triggered when new messages are added - //TODO: Does this one need to come into the render of the method? - const renderMessage = (index) => { - const message = messages[index]; - return ( -
-
- -
- {message.image_path && - message.image_path.map((i, idx) => ( -
- - Received - -
- ))} -
{message.text}
-
-
- {message.status && ( -
- -
- )} -
- {message.isoutbound && ( -
- {i18n.t("messaging.labels.sentby", { - by: message.userid, - time: dayjs(message.created_at).format("MM/DD/YYYY @ hh:mm a") - })} -
- )} -
- ); - }; return (
renderMessage(index)} + itemContent={(index) => renderMessage(messages, index)} // Pass `messages` to renderMessage followOutput="smooth" // Ensure smooth scrolling when new data is appended style={{ height: "100%", width: "100%" }} /> diff --git a/client/src/components/chat-messages-list/renderMessage.jsx b/client/src/components/chat-messages-list/renderMessage.jsx new file mode 100644 index 000000000..e6982f72b --- /dev/null +++ b/client/src/components/chat-messages-list/renderMessage.jsx @@ -0,0 +1,42 @@ +import Icon from "@ant-design/icons"; +import { Tooltip } from "antd"; +import i18n from "i18next"; +import dayjs from "../../utils/day"; +import { MdDone, MdDoneAll } from "react-icons/md"; +import { DateTimeFormatter } from "../../utils/DateFormatter"; + +export const renderMessage = (messages, index) => { + const message = messages[index]; + return ( +
+
+ +
+ {message.image_path && + message.image_path.map((i, idx) => ( +
+ + Received + +
+ ))} +
{message.text}
+
+
+ {message.status && (message.status === "sent" || message.status === "delivered") && ( +
+ +
+ )} +
+ {message.isoutbound && ( +
+ {i18n.t("messaging.labels.sentby", { + by: message.userid, + time: dayjs(message.created_at).format("MM/DD/YYYY @ hh:mm a") + })} +
+ )} +
+ ); +}; diff --git a/client/src/components/wss-status-display/wss-status-display.component.jsx b/client/src/components/wss-status-display/wss-status-display.component.jsx index 40ff0e42a..6eb2c9223 100644 --- a/client/src/components/wss-status-display/wss-status-display.component.jsx +++ b/client/src/components/wss-status-display/wss-status-display.component.jsx @@ -1,18 +1,33 @@ import { connect } from "react-redux"; -import { GlobalOutlined } from "@ant-design/icons"; +import { GlobalOutlined, WarningOutlined } from "@ant-design/icons"; import { createStructuredSelector } from "reselect"; import React from "react"; import { selectWssStatus } from "../../redux/application/application.selectors"; + const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser wssStatus: selectWssStatus }); -const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) -}); -export default connect(mapStateToProps, mapDispatchToProps)(WssStatusDisplay); + +const mapDispatchToProps = (dispatch) => ({}); export function WssStatusDisplay({ wssStatus }) { console.log("🚀 ~ WssStatusDisplay ~ wssStatus:", wssStatus); - return ; + + let icon; + let color; + + if (wssStatus === "connected") { + icon = ; + color = "green"; + } else if (wssStatus === "error") { + icon = ; + color = "red"; + } else { + icon = ; + color = "gray"; // Default for other statuses like "disconnected" + } + + return {icon}; } + +export default connect(mapStateToProps, mapDispatchToProps)(WssStatusDisplay); diff --git a/client/src/contexts/SocketIO/useSocket.js b/client/src/contexts/SocketIO/useSocket.js index 885602fe3..c13141ca7 100644 --- a/client/src/contexts/SocketIO/useSocket.js +++ b/client/src/contexts/SocketIO/useSocket.js @@ -3,75 +3,102 @@ import SocketIO from "socket.io-client"; import { auth } from "../../firebase/firebase.utils"; import { store } from "../../redux/store"; import { addAlerts, setWssStatus } from "../../redux/application/application.actions"; -import { useDispatch } from "react-redux"; const useSocket = (bodyshop) => { const socketRef = useRef(null); const [clientId, setClientId] = useState(null); - const dispatch = useDispatch(); useEffect(() => { + const initializeSocket = async (token) => { + if (!bodyshop || !bodyshop.id) return; + + const endpoint = import.meta.env.PROD ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL : ""; + + const socketInstance = SocketIO(endpoint, { + path: "/wss", + withCredentials: true, + auth: { token }, + reconnectionAttempts: Infinity, + reconnectionDelay: 2000, + reconnectionDelayMax: 10000 + }); + + socketRef.current = socketInstance; + + // Handle socket events + const handleBodyshopMessage = (message) => { + if (!message || !message.type) return; + + switch (message.type) { + case "alert-update": + store.dispatch(addAlerts(message.payload)); + break; + default: + break; + } + + if (!import.meta.env.DEV) return; + console.log(`Received message for bodyshop ${bodyshop.id}:`, message); + }; + + const handleConnect = () => { + socketInstance.emit("join-bodyshop-room", bodyshop.id); + setClientId(socketInstance.id); + store.dispatch(setWssStatus("connected")); + }; + + const handleReconnect = () => { + store.dispatch(setWssStatus("connected")); + }; + + const handleConnectionError = (err) => { + console.error("Socket connection error:", err); + + // Handle token expiration + if (err.message.includes("auth/id-token-expired")) { + console.warn("Token expired, refreshing..."); + auth.currentUser?.getIdToken(true).then((newToken) => { + socketInstance.auth = { token: newToken }; // Update socket auth + socketInstance.connect(); // Retry connection + }); + } else { + store.dispatch(setWssStatus("error")); + } + }; + + const handleDisconnect = (reason) => { + console.warn("Socket disconnected:", reason); + store.dispatch(setWssStatus("disconnected")); + + // Manually trigger reconnection if necessary + if (!socketInstance.connected && reason !== "io server disconnect") { + setTimeout(() => { + if (socketInstance.disconnected) { + console.log("Manually triggering reconnection..."); + socketInstance.connect(); + } + }, 2000); // Retry after 2 seconds + } + }; + + // Register event handlers + socketInstance.on("connect", handleConnect); + socketInstance.on("reconnect", handleReconnect); + socketInstance.on("connect_error", handleConnectionError); + socketInstance.on("disconnect", handleDisconnect); + socketInstance.on("bodyshop-message", handleBodyshopMessage); + }; + const unsubscribe = auth.onIdTokenChanged(async (user) => { if (user) { - const newToken = await user.getIdToken(); + const token = await user.getIdToken(); if (socketRef.current) { - // Send new token to server - socketRef.current.emit("update-token", newToken); - } else if (bodyshop && bodyshop.id) { - // Initialize the socket - const endpoint = import.meta.env.PROD ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL : ""; - - const socketInstance = SocketIO(endpoint, { - path: "/wss", - withCredentials: true, - auth: { token: newToken }, - reconnectionAttempts: Infinity, - reconnectionDelay: 2000, - reconnectionDelayMax: 10000 - }); - - socketRef.current = socketInstance; - - const handleBodyshopMessage = (message) => { - if (!message || !message?.type) return; - - switch (message.type) { - case "alert-update": - store.dispatch(addAlerts(message.payload)); - break; - default: - break; - } - - if (!import.meta.env.DEV) return; - console.log(`Received message for bodyshop ${bodyshop.id}:`, message); - }; - - const handleConnect = () => { - socketInstance.emit("join-bodyshop-room", bodyshop.id); - setClientId(socketInstance.id); - store.dispatch(setWssStatus("connected")); - }; - - const handleReconnect = (attempt) => { - store.dispatch(setWssStatus("connected")); - }; - - const handleConnectionError = (err) => { - console.error("Socket connection error:", err); - store.dispatch(setWssStatus("error")); - }; - - const handleDisconnect = () => { - store.dispatch(setWssStatus("disconnected")); - }; - - socketInstance.on("connect", handleConnect); - socketInstance.on("reconnect", handleReconnect); - socketInstance.on("connect_error", handleConnectionError); - socketInstance.on("disconnect", handleDisconnect); - socketInstance.on("bodyshop-message", handleBodyshopMessage); + // Update token if socket exists + socketRef.current.emit("update-token", token); + } else { + // Initialize socket if not already connected + initializeSocket(token); } } else { // User is not authenticated @@ -82,7 +109,7 @@ const useSocket = (bodyshop) => { } }); - // Clean up the listener on unmount + // Clean up on unmount return () => { unsubscribe(); if (socketRef.current) { @@ -90,7 +117,7 @@ const useSocket = (bodyshop) => { socketRef.current = null; } }; - }, [bodyshop, dispatch]); + }, [bodyshop]); return { socket: socketRef.current, clientId }; }; diff --git a/server/web-sockets/redisSocketEvents.js b/server/web-sockets/redisSocketEvents.js index a8e753f2f..90893a505 100644 --- a/server/web-sockets/redisSocketEvents.js +++ b/server/web-sockets/redisSocketEvents.js @@ -46,18 +46,25 @@ const redisSocketEvents = ({ // Token Update Events const registerUpdateEvents = (socket) => { + let latestTokenTimestamp = 0; + const updateToken = async (newToken) => { + const currentTimestamp = Date.now(); + latestTokenTimestamp = currentTimestamp; + try { - // noinspection UnnecessaryLocalVariableJS + // Verify token with Firebase Admin SDK const user = await admin.auth().verifyIdToken(newToken, true); + + // Skip outdated token validations + if (currentTimestamp < latestTokenTimestamp) { + createLogEvent(socket, "warn", "Outdated token validation skipped."); + return; + } + socket.user = user; - // If We ever want to persist user Data across workers - // await setSessionData(socket.id, "user", user); - - // Uncomment for further testing - // createLogEvent(socket, "debug", "Token updated successfully"); - + createLogEvent(socket, "debug", `Token updated successfully for socket ID: ${socket.id}`); socket.emit("token-updated", { success: true }); } catch (error) { if (error.code === "auth/id-token-expired") { @@ -66,14 +73,17 @@ const redisSocketEvents = ({ success: false, error: "Stale token." }); - } else { - createLogEvent(socket, "error", `Token update failed: ${error.message}`); - socket.emit("token-updated", { success: false, error: error.message }); - // For any other errors, optionally disconnect the socket - socket.disconnect(); + return; // Avoid disconnecting for expired tokens } + + createLogEvent(socket, "error", `Token update failed for socket ID: ${socket.id}, Error: ${error.message}`); + socket.emit("token-updated", { success: false, error: error.message }); + + // Optionally disconnect for invalid tokens or other errors + socket.disconnect(); } }; + socket.on("update-token", updateToken); }; From e734da7adce82d17f4711e0eeffe9992c03b26ad Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 21 Nov 2024 12:04:32 -0800 Subject: [PATCH 19/74] feature/IO-3000-messaging-sockets-migrations2 - - Fix Bug in import Signed-off-by: Dave Richer --- client/src/components/profile-shops/profile-shops.container.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/profile-shops/profile-shops.container.jsx b/client/src/components/profile-shops/profile-shops.container.jsx index 46fe59aa2..4c5150913 100644 --- a/client/src/components/profile-shops/profile-shops.container.jsx +++ b/client/src/components/profile-shops/profile-shops.container.jsx @@ -5,7 +5,7 @@ import { QUERY_ALL_ASSOCIATIONS, UPDATE_ACTIVE_ASSOCIATION } from "../../graphql import AlertComponent from "../alert/alert.component"; import ProfileShopsComponent from "./profile-shops.component"; import axios from "axios"; -import { getToken } from " firebase/messaging"; +import { getToken } from "firebase/messaging"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; From 1cdd905037d7e65bef5a7156367802de2da1fbd7 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 21 Nov 2024 13:23:28 -0800 Subject: [PATCH 20/74] feature/IO-3000-messaging-sockets-migrations2 - - Checkpoint, archiving works, cannot unarchive yet Signed-off-by: Dave Richer --- .../registerMessagingSocketHandlers.js | 32 +++++++++++++++++++ .../chat-archive-button.component.jsx | 1 + .../chat-conversation.component.jsx | 1 + server/web-sockets/redisSocketEvents.js | 2 +- 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/client/src/components/chat-affix/registerMessagingSocketHandlers.js b/client/src/components/chat-affix/registerMessagingSocketHandlers.js index ab4473e97..325770541 100644 --- a/client/src/components/chat-affix/registerMessagingSocketHandlers.js +++ b/client/src/components/chat-affix/registerMessagingSocketHandlers.js @@ -152,6 +152,38 @@ export const registerMessagingHandlers = ({ socket, client }) => { return; } + if (type === "conversation-archived") { + // Remove all messages associated with this conversation + const messageRefs = client.cache.readFragment({ + id: cacheId, + fragment: gql` + fragment ConversationMessages on conversations { + messages { + id + } + } + ` + }); + + if (messageRefs?.messages) { + messageRefs.messages.forEach((message) => { + const messageCacheId = client.cache.identify({ + __typename: "messages", + id: message.id + }); + if (messageCacheId) { + client.cache.evict({ id: messageCacheId }); + } + }); + } + + // Evict the conversation itself + client.cache.evict({ id: cacheId }); + client.cache.gc(); // Trigger garbage collection to clean up unused entries + + return; + } + client.cache.modify({ id: cacheId, fields: { diff --git a/client/src/components/chat-archive-button/chat-archive-button.component.jsx b/client/src/components/chat-archive-button/chat-archive-button.component.jsx index f7ed348c1..2b8bcd054 100644 --- a/client/src/components/chat-archive-button/chat-archive-button.component.jsx +++ b/client/src/components/chat-archive-button/chat-archive-button.component.jsx @@ -20,6 +20,7 @@ export default function ChatArchiveButton({ conversation, bodyshop }) { if (socket) { socket.emit("conversation-modified", { + type: "conversation-archived", conversationId: conversation.id, bodyshopId: bodyshop.id, archived: updatedConversation.data.update_conversations_by_pk.archived diff --git a/client/src/components/chat-conversation/chat-conversation.component.jsx b/client/src/components/chat-conversation/chat-conversation.component.jsx index 32e52d5f2..4188fbabe 100644 --- a/client/src/components/chat-conversation/chat-conversation.component.jsx +++ b/client/src/components/chat-conversation/chat-conversation.component.jsx @@ -15,6 +15,7 @@ export default function ChatConversationComponent({ }) { const [loading, error] = subState; + if (conversation?.archived) return null; if (loading) return ; if (error) return ; diff --git a/server/web-sockets/redisSocketEvents.js b/server/web-sockets/redisSocketEvents.js index 90893a505..366461adc 100644 --- a/server/web-sockets/redisSocketEvents.js +++ b/server/web-sockets/redisSocketEvents.js @@ -174,7 +174,7 @@ const redisSocketEvents = ({ const conversationModified = ({ bodyshopId, conversationId, ...fields }) => { try { // Retrieve the room name for the conversation - const room = getBodyshopConversationRoom({ bodyshopId, conversationId }); + const room = getBodyshopRoom(bodyshopId); // Emit the updated data to all clients in the room io.to(room).emit("conversation-changed", { conversationId, From 8ad1dd83c6e21936580475a616ace4d875679292 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 21 Nov 2024 14:24:31 -0800 Subject: [PATCH 21/74] IO-3027 Datapump Refactor Remove Batch and sftp transfer for each shop during processing Signed-off-by: Allan Carr --- server/data/autohouse.js | 99 ++++++++++++++++++++------------------- server/data/claimscorp.js | 99 ++++++++++++++++++++------------------- server/data/kaizen.js | 98 +++++++++++++++++++------------------- 3 files changed, 152 insertions(+), 144 deletions(-) diff --git a/server/data/autohouse.js b/server/data/autohouse.js index 1384771e6..19078c274 100644 --- a/server/data/autohouse.js +++ b/server/data/autohouse.js @@ -25,17 +25,15 @@ const ftpSetup = { port: process.env.AUTOHOUSE_PORT, username: process.env.AUTOHOUSE_USER, password: process.env.AUTOHOUSE_PASSWORD, - debug: process.env.NODE_ENV !== "production" - ? (message, ...data) => logger.log(message, "DEBUG", "api", null, data) - : () => {}, + debug: + process.env.NODE_ENV !== "production" + ? (message, ...data) => logger.log(message, "DEBUG", "api", null, data) + : () => {}, algorithms: { serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"] } }; -const allxmlsToUpload = []; -const allErrors = []; - exports.default = async (req, res) => { // Only process if in production environment. if (process.env.NODE_ENV !== "production") { @@ -57,12 +55,13 @@ exports.default = async (req, res) => { try { logger.log("autohouse-start", "DEBUG", "api", null, null); + const allXMLResults = []; + const allErrors = []; + const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS); //Query for the List of Bodyshop Clients. const specificShopIds = req.body.bodyshopIds; // ['uuid]; const { start, end, skipUpload } = req.body; //YYYY-MM-DD - const batchSize = 10; - const shopsToProcess = specificShopIds?.length > 0 ? bodyshops.filter((shop) => specificShopIds.includes(shop.id)) : bodyshops; logger.log("autohouse-shopsToProcess-generated", "DEBUG", "api", null, null); @@ -71,27 +70,18 @@ exports.default = async (req, res) => { logger.log("autohouse-shopsToProcess-empty", "DEBUG", "api", null, null); return; } - const batchPromises = []; - for (let i = 0; i < shopsToProcess.length; i += batchSize) { - const batch = shopsToProcess.slice(i, i + batchSize); - const batchPromise = (async () => { - await processBatch(batch, start, end); - if (skipUpload) { - for (const xmlObj of allxmlsToUpload) { - fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml); - } - } else { - await uploadViaSFTP(allxmlsToUpload); - } - })(); - batchPromises.push(batchPromise); - } - await Promise.all(batchPromises); + await processShopData(shopsToProcess, start, end, skipUpload, allXMLResults, allErrors); + await sendServerEmail({ subject: `Autohouse Report ${moment().format("MM-DD-YY")}`, text: `Errors:\n${JSON.stringify(allErrors, null, 2)}\n\nUploaded:\n${JSON.stringify( - allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count, result: x.result })), + allXMLResults.map((x) => ({ + imexshopid: x.imexshopid, + filename: x.filename, + count: x.count, + result: x.result + })), null, 2 )}` @@ -103,8 +93,8 @@ exports.default = async (req, res) => { } }; -async function processBatch(batch, start, end) { - for (const bodyshop of batch) { +async function processShopData(shopsToProcess, start, end, skipUpload, allXMLResults, allErrors) { + for (const bodyshop of shopsToProcess) { const erroredJobs = []; try { logger.log("autohouse-start-shop-extract", "DEBUG", "api", bodyshop.id, { @@ -134,12 +124,26 @@ async function processBatch(batch, start, end) { }); } - const ret = builder.create({}, autoHouseObject).end({ allowEmptyTags: true }); + const xmlObj = { + imexshopid: bodyshop.imexshopid, + xml: builder.create({}, autoHouseObject).end({ allowEmptyTags: true }), + filename: `IM_${bodyshop.autohouseid}_${moment().format("DDMMYYYY_HHMMss")}.xml`, + count: autoHouseObject.AutoHouseExport.RepairOrder.length + }; - allxmlsToUpload.push({ - count: autoHouseObject.AutoHouseExport.RepairOrder.length, - xml: ret, - filename: `IM_${bodyshop.autohouseid}_${moment().format("DDMMYYYY_HHMMss")}.xml` + if (skipUpload) { + fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml); + } else { + await uploadViaSFTP(xmlObj); + } + + allXMLResults.push({ + bodyshopid: bodyshop.id, + imexshopid: bodyshop.imexshopid, + autohouseid: bodyshop.autohouseid, + count: xmlObj.count, + filename: xmlObj.filename, + result: xmlObj.result }); logger.log("autohouse-end-shop-extract", "DEBUG", "api", bodyshop.id, { @@ -171,7 +175,7 @@ async function processBatch(batch, start, end) { } } -async function uploadViaSFTP(allxmlsToUpload) { +async function uploadViaSFTP(xmlObj) { const sftp = new Client(); sftp.on("error", (errors) => logger.log("autohouse-sftp-connection-error", "ERROR", "api", null, { error: errors.message, stack: errors.stack }) @@ -180,21 +184,20 @@ async function uploadViaSFTP(allxmlsToUpload) { //Connect to the FTP and upload all. await sftp.connect(ftpSetup); - for (const xmlObj of allxmlsToUpload) { - try { - xmlObj.result = await sftp.put(Buffer.from(xmlObj.xml), `${xmlObj.filename}`); - logger.log("autohouse-sftp-upload", "DEBUG", "api", null, { - filename: xmlObj.filename, - result: xmlObj.result - }); - } catch (error) { - logger.log("autohouse-sftp-upload-error", "ERROR", "api", null, { - filename: xmlObj.filename, - error: error.message, - stack: error.stack - }); - throw error; - } + try { + xmlObj.result = await sftp.put(Buffer.from(xmlObj.xml), `${xmlObj.filename}`); + logger.log("autohouse-sftp-upload", "DEBUG", "api", null, { + imexshopid: xmlObj.imexshopid, + filename: xmlObj.filename, + result: xmlObj.result + }); + } catch (error) { + logger.log("autohouse-sftp-upload-error", "ERROR", "api", null, { + filename: xmlObj.filename, + error: error.message, + stack: error.stack + }); + throw error; } } catch (error) { logger.log("autohouse-sftp-error", "ERROR", "api", null, { error: error.message, stack: error.stack }); diff --git a/server/data/claimscorp.js b/server/data/claimscorp.js index b8c77a018..3db09cbcf 100644 --- a/server/data/claimscorp.js +++ b/server/data/claimscorp.js @@ -24,17 +24,15 @@ const ftpSetup = { port: process.env.CLAIMSCORP_PORT, username: process.env.CLAIMSCORP_USER, password: process.env.CLAIMSCORP_PASSWORD, - debug: process.env.NODE_ENV !== "production" - ? (message, ...data) => logger.log(message, "DEBUG", "api", null, data) - : () => {}, + debug: + process.env.NODE_ENV !== "production" + ? (message, ...data) => logger.log(message, "DEBUG", "api", null, data) + : () => {}, algorithms: { serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"] } }; -const allxmlsToUpload = []; -const allErrors = []; - exports.default = async (req, res) => { // Only process if in production environment. if (process.env.NODE_ENV !== "production") { @@ -56,12 +54,13 @@ exports.default = async (req, res) => { try { logger.log("claimscorp-start", "DEBUG", "api", null, null); + const allXMLResults = []; + const allErrors = []; + const { bodyshops } = await client.request(queries.GET_CLAIMSCORP_SHOPS); //Query for the List of Bodyshop Clients. const specificShopIds = req.body.bodyshopIds; // ['uuid]; const { start, end, skipUpload } = req.body; //YYYY-MM-DD - const batchSize = 10; - const shopsToProcess = specificShopIds?.length > 0 ? bodyshops.filter((shop) => specificShopIds.includes(shop.id)) : bodyshops; logger.log("claimscorp-shopsToProcess-generated", "DEBUG", "api", null, null); @@ -70,27 +69,18 @@ exports.default = async (req, res) => { logger.log("claimscorp-shopsToProcess-empty", "DEBUG", "api", null, null); return; } - const batchPromises = []; - for (let i = 0; i < shopsToProcess.length; i += batchSize) { - const batch = shopsToProcess.slice(i, i + batchSize); - const batchPromise = (async () => { - await processBatch(batch, start, end); - if (skipUpload) { - for (const xmlObj of allxmlsToUpload) { - fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml); - } - } else { - await uploadViaSFTP(allxmlsToUpload); - } - })(); - batchPromises.push(batchPromise); - } - await Promise.all(batchPromises); + await processShopData(shopsToProcess, start, end, skipUpload, allXMLResults, allErrors); + await sendServerEmail({ subject: `ClaimsCorp Report ${moment().format("MM-DD-YY")}`, text: `Errors:\n${JSON.stringify(allErrors, null, 2)}\n\nUploaded:\n${JSON.stringify( - allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count, result: x.result })), + allXMLResults.map((x) => ({ + imexshopid: x.imexshopid, + filename: x.filename, + count: x.count, + result: x.result + })), null, 2 )}` @@ -102,8 +92,8 @@ exports.default = async (req, res) => { } }; -async function processBatch(batch, start, end) { - for (const bodyshop of batch) { +async function processShopData(shopsToProcess, start, end, skipUpload, allXMLResults, allErrors) { + for (const bodyshop of shopsToProcess) { const erroredJobs = []; try { logger.log("claimscorp-start-shop-extract", "DEBUG", "api", bodyshop.id, { @@ -137,12 +127,26 @@ async function processBatch(batch, start, end) { }); } - const ret = builder.create({}, claimsCorpObject).end({ allowEmptyTags: true }); + const xmlObj = { + imexshopid: bodyshop.imexshopid, + xml: builder.create({}, claimsCorpObject).end({ allowEmptyTags: true }), + filename: `${bodyshop.claimscorpid}-${moment().format("YYYYMMDDTHHMMss")}.xml`, + count: claimsCorpObject.DataFeed.ShopInfo.RO.length + }; - allxmlsToUpload.push({ - count: claimsCorpObject.DataFeed.ShopInfo.RO.length, - xml: ret, - filename: `${bodyshop.claimscorpid}-${moment().format("YYYYMMDDTHHMMss")}.xml` + if (skipUpload) { + fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml); + } else { + await uploadViaSFTP(xmlObj); + } + + allXMLResults.push({ + bodyshopid: bodyshop.id, + imexshopid: bodyshop.imexshopid, + claimscorpid: bodyshop.claimscorpid, + count: xmlObj.count, + filename: xmlObj.filename, + result: xmlObj.result }); logger.log("claimscorp-end-shop-extract", "DEBUG", "api", bodyshop.id, { @@ -174,7 +178,7 @@ async function processBatch(batch, start, end) { } } -async function uploadViaSFTP(allxmlsToUpload) { +async function uploadViaSFTP(xmlObj) { const sftp = new Client(); sftp.on("error", (errors) => logger.log("claimscorp-sftp-connection-error", "ERROR", "api", null, { error: errors.message, stack: errors.stack }) @@ -183,21 +187,20 @@ async function uploadViaSFTP(allxmlsToUpload) { //Connect to the FTP and upload all. await sftp.connect(ftpSetup); - for (const xmlObj of allxmlsToUpload) { - try { - xmlObj.result = await sftp.put(Buffer.from(xmlObj.xml), `${xmlObj.filename}`); - logger.log("claimscorp-sftp-upload", "DEBUG", "api", null, { - filename: xmlObj.filename, - result: xmlObj.result - }); - } catch (error) { - logger.log("claimscorp-sftp-upload-error", "ERROR", "api", null, { - filename: xmlObj.filename, - error: error.message, - stack: error.stack - }); - throw error; - } + try { + xmlObj.result = await sftp.put(Buffer.from(xmlObj.xml), `${xmlObj.filename}`); + logger.log("claimscorp-sftp-upload", "DEBUG", "api", null, { + imexshopid: xmlObj.imexshopid, + filename: xmlObj.filename, + result: xmlObj.result + }); + } catch (error) { + logger.log("claimscorp-sftp-upload-error", "ERROR", "api", null, { + filename: xmlObj.filename, + error: error.message, + stack: error.stack + }); + throw error; } } catch (error) { logger.log("claimscorp-sftp-error", "ERROR", "api", null, { error: error.message, stack: error.stack }); diff --git a/server/data/kaizen.js b/server/data/kaizen.js index bbb758a93..eb6abc056 100644 --- a/server/data/kaizen.js +++ b/server/data/kaizen.js @@ -23,17 +23,15 @@ const ftpSetup = { port: process.env.KAIZEN_PORT, username: process.env.KAIZEN_USER, password: process.env.KAIZEN_PASSWORD, - debug: process.env.NODE_ENV !== "production" - ? (message, ...data) => logger.log(message, "DEBUG", "api", null, data) - : () => {}, + debug: + process.env.NODE_ENV !== "production" + ? (message, ...data) => logger.log(message, "DEBUG", "api", null, data) + : () => {}, algorithms: { serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"] } }; -const allxmlsToUpload = []; -const allErrors = []; - exports.default = async (req, res) => { // Only process if in production environment. if (process.env.NODE_ENV !== "production") { @@ -55,12 +53,13 @@ exports.default = async (req, res) => { try { logger.log("kaizen-start", "DEBUG", "api", null, null); + const allXMLResults = []; + const allErrors = []; + const { bodyshops } = await client.request(queries.GET_KAIZEN_SHOPS, { imexshopid: kaizenShopsIDs }); //Query for the List of Bodyshop Clients. const specificShopIds = req.body.bodyshopIds; // ['uuid]; const { start, end, skipUpload } = req.body; //YYYY-MM-DD - const batchSize = 10; - const shopsToProcess = specificShopIds?.length > 0 ? bodyshops.filter((shop) => specificShopIds.includes(shop.id)) : bodyshops; logger.log("kaizen-shopsToProcess-generated", "DEBUG", "api", null, null); @@ -69,27 +68,18 @@ exports.default = async (req, res) => { logger.log("kaizen-shopsToProcess-empty", "DEBUG", "api", null, null); return; } - const batchPromises = []; - for (let i = 0; i < shopsToProcess.length; i += batchSize) { - const batch = shopsToProcess.slice(i, i + batchSize); - const batchPromise = (async () => { - await processBatch(batch, start, end); - if (skipUpload) { - for (const xmlObj of allxmlsToUpload) { - fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml); - } - } else { - await uploadViaSFTP(allxmlsToUpload); - } - })(); - batchPromises.push(batchPromise); - } - await Promise.all(batchPromises); + await processShopData(shopsToProcess, start, end, skipUpload, allXMLResults, allErrors); + await sendServerEmail({ subject: `Kaizen Report ${moment().format("MM-DD-YY")}`, text: `Errors:\n${JSON.stringify(allErrors, null, 2)}\n\nUploaded:\n${JSON.stringify( - allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count, result: x.result })), + allXMLResults.map((x) => ({ + imexshopid: x.imexshopid, + filename: x.filename, + count: x.count, + result: x.result + })), null, 2 )}` @@ -101,8 +91,8 @@ exports.default = async (req, res) => { } }; -async function processBatch(batch, start, end) { - for (const bodyshop of batch) { +async function processShopData(shopsToProcess, start, end, skipUpload, allXMLResults, allErrors) { + for (const bodyshop of shopsToProcess) { const erroredJobs = []; try { logger.log("kaizen-start-shop-extract", "DEBUG", "api", bodyshop.id, { @@ -135,12 +125,25 @@ async function processBatch(batch, start, end) { }); } - const ret = builder.create({}, kaizenObject).end({ allowEmptyTags: true }); + const xmlObj = { + imexshopid: bodyshop.imexshopid, + xml: builder.create({}, kaizenObject).end({ allowEmptyTags: true }), + filename: `${bodyshop.shopname}-${moment().format("YYYYMMDDTHHMMss")}.xml`, + count: kaizenObject.DataFeed.ShopInfo.Jobs.length + }; - allxmlsToUpload.push({ - count: kaizenObject.DataFeed.ShopInfo.Jobs.length, - xml: ret, - filename: `${bodyshop.shopname}-${moment().format("YYYYMMDDTHHMMss")}.xml` + if (skipUpload) { + fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml); + } else { + await uploadViaSFTP(xmlObj); + } + + allXMLResults.push({ + bodyshopid: bodyshop.id, + imexshopid: bodyshop.imexshopid, + count: xmlObj.count, + filename: xmlObj.filename, + result: xmlObj.result }); logger.log("kaizen-end-shop-extract", "DEBUG", "api", bodyshop.id, { @@ -172,7 +175,7 @@ async function processBatch(batch, start, end) { } } -async function uploadViaSFTP(allxmlsToUpload) { +async function uploadViaSFTP(xmlObj) { const sftp = new Client(); sftp.on("error", (errors) => logger.log("kaizen-sftp-connection-error", "ERROR", "api", null, { error: errors.message, stack: errors.stack }) @@ -181,21 +184,20 @@ async function uploadViaSFTP(allxmlsToUpload) { //Connect to the FTP and upload all. await sftp.connect(ftpSetup); - for (const xmlObj of allxmlsToUpload) { - try { - xmlObj.result = await sftp.put(Buffer.from(xmlObj.xml), `${xmlObj.filename}`); - logger.log("kaizen-sftp-upload", "DEBUG", "api", null, { - filename: xmlObj.filename, - result: xmlObj.result - }); - } catch (error) { - logger.log("kaizen-sftp-upload-error", "ERROR", "api", null, { - filename: xmlObj.filename, - error: error.message, - stack: error.stack - }); - throw error; - } + try { + xmlObj.result = await sftp.put(Buffer.from(xmlObj.xml), `${xmlObj.filename}`); + logger.log("kaizen-sftp-upload", "DEBUG", "api", null, { + imexshopid: xmlObj.imexshopid, + filename: xmlObj.filename, + result: xmlObj.result + }); + } catch (error) { + logger.log("kaizen-sftp-upload-error", "ERROR", "api", null, { + filename: xmlObj.filename, + error: error.message, + stack: error.stack + }); + throw error; } } catch (error) { logger.log("kaizen-sftp-error", "ERROR", "api", null, { error: error.message, stack: error.stack }); From dd4ba8a467ccc01f88426dcbc6f3a9949d09fd0a Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 21 Nov 2024 14:40:36 -0800 Subject: [PATCH 22/74] IO-2921 Chatter Datapump Adjustment Signed-off-by: Allan Carr --- server/data/chatter.js | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/server/data/chatter.js b/server/data/chatter.js index 282ff6421..8c3f8e634 100644 --- a/server/data/chatter.js +++ b/server/data/chatter.js @@ -17,15 +17,15 @@ const ftpSetup = { port: process.env.CHATTER_PORT, username: process.env.CHATTER_USER, privateKey: null, - debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data), + debug: + process.env.NODE_ENV !== "production" + ? (message, ...data) => logger.log(message, "DEBUG", "api", null, data) + : () => {}, algorithms: { serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"] } }; -const allChatterObjects = []; -const allErrors = []; - exports.default = async (req, res) => { // Only process if in production environment. if (process.env.NODE_ENV !== "production") { @@ -47,12 +47,13 @@ exports.default = async (req, res) => { try { logger.log("chatter-start", "DEBUG", "api", null, null); + const allChatterObjects = []; + const allErrors = []; + const { bodyshops } = await client.request(queries.GET_CHATTER_SHOPS); //Query for the List of Bodyshop Clients. const specificShopIds = req.body.bodyshopIds; // ['uuid]; const { start, end, skipUpload } = req.body; //YYYY-MM-DD - const batchSize = 10; - const shopsToProcess = specificShopIds?.length > 0 ? bodyshops.filter((shop) => specificShopIds.includes(shop.id)) : bodyshops; logger.log("chatter-shopsToProcess-generated", "DEBUG", "api", null, null); @@ -62,16 +63,14 @@ exports.default = async (req, res) => { return; } - for (let i = 0; i < shopsToProcess.length; i += batchSize) { - const batch = shopsToProcess.slice(i, i + batchSize); - await processBatch(batch, start, end); - } + await processBatch(shopsToProcess, start, end, allChatterObjects, allErrors); const csvToUpload = { count: allChatterObjects.length, csv: converter.json2csv(allChatterObjects, { emptyFieldValue: "" }), filename: `IMEX_ONLINE_solicitation_${moment().format("YYYYMMDD")}.csv` }; + if (skipUpload) { await fs.promises.writeFile(`./logs/${csvToUpload.filename}`, csvToUpload.csv); } else { @@ -90,8 +89,8 @@ exports.default = async (req, res) => { } }; -async function processBatch(batch, start, end) { - for (const bodyshop of batch) { +async function processBatch(shopsToProcess, start, end, allChatterObjects, allErrors) { + for (const bodyshop of shopsToProcess) { try { logger.log("chatter-start-shop-extract", "DEBUG", "api", bodyshop.id, { shopname: bodyshop.shopname From cd592b671c68370c1017ff7f415d031b643944b9 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 21 Nov 2024 15:05:52 -0800 Subject: [PATCH 23/74] feature/IO-3000-messaging-sockets-migrations2 - - Checkpoint, Signed-off-by: Dave Richer --- .../registerMessagingSocketHandlers.js | 10 +++++++++- client/src/graphql/conversations.queries.js | 18 +++++++++++++++++- server/sms/receive.js | 3 ++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/client/src/components/chat-affix/registerMessagingSocketHandlers.js b/client/src/components/chat-affix/registerMessagingSocketHandlers.js index 325770541..b50d02c8a 100644 --- a/client/src/components/chat-affix/registerMessagingSocketHandlers.js +++ b/client/src/components/chat-affix/registerMessagingSocketHandlers.js @@ -22,6 +22,7 @@ export const registerMessagingHandlers = ({ socket, client }) => { query: CONVERSATION_LIST_QUERY, variables: { offset: 0 } }); + client.cache.writeQuery({ query: CONVERSATION_LIST_QUERY, variables: { offset: 0 }, @@ -49,7 +50,14 @@ export const registerMessagingHandlers = ({ socket, client }) => { id: conversationId }), fields: { - updated_at: () => new Date(), + updated_at: () => new Date().toISOString(), + archived(cached) { + // Unarchive the conversation if it was previously marked as archived + if (cached) { + return false; + } + return cached; + }, messages_aggregate(cached) { // Increment unread count only if the message is inbound if (!isoutbound) { diff --git a/client/src/graphql/conversations.queries.js b/client/src/graphql/conversations.queries.js index e05a474ab..03bc0426b 100644 --- a/client/src/graphql/conversations.queries.js +++ b/client/src/graphql/conversations.queries.js @@ -90,9 +90,25 @@ export const CONVERSATION_ID_BY_PHONE = gql` query CONVERSATION_ID_BY_PHONE($phone: String!) { conversations(where: { phone_num: { _eq: $phone } }) { id + phone_num + archived + label + unreadcnt job_conversations { jobid - id + conversationid + job { + id + ownr_fn + ownr_ln + ownr_co_nm + ro_number + } + } + messages_aggregate(where: { read: { _eq: false }, isoutbound: { _eq: false } }) { + aggregate { + count + } } } } diff --git a/server/sms/receive.js b/server/sms/receive.js index 3fb721eec..f7f41652e 100644 --- a/server/sms/receive.js +++ b/server/sms/receive.js @@ -54,7 +54,8 @@ exports.receive = async (req, res) => { newMessage.conversation = { data: { bodyshopid: bodyshop.id, - phone_num: phone(req.body.From).phoneNumber + phone_num: phone(req.body.From).phoneNumber, + archived: false } }; From e202bf9a8966626cdf050d96c94ded2ab2a17e87 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 21 Nov 2024 16:18:23 -0800 Subject: [PATCH 24/74] IO-3027 Add in bodyshop.id to logging in SFTP Signed-off-by: Allan Carr --- server/data/autohouse.js | 12 ++++++++---- server/data/claimscorp.js | 15 +++++++++++---- server/data/kaizen.js | 12 ++++++++---- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/server/data/autohouse.js b/server/data/autohouse.js index 19078c274..81679522b 100644 --- a/server/data/autohouse.js +++ b/server/data/autohouse.js @@ -125,6 +125,7 @@ async function processShopData(shopsToProcess, start, end, skipUpload, allXMLRes } const xmlObj = { + bodyshopid: bodyshop.id, imexshopid: bodyshop.imexshopid, xml: builder.create({}, autoHouseObject).end({ allowEmptyTags: true }), filename: `IM_${bodyshop.autohouseid}_${moment().format("DDMMYYYY_HHMMss")}.xml`, @@ -178,7 +179,10 @@ async function processShopData(shopsToProcess, start, end, skipUpload, allXMLRes async function uploadViaSFTP(xmlObj) { const sftp = new Client(); sftp.on("error", (errors) => - logger.log("autohouse-sftp-connection-error", "ERROR", "api", null, { error: errors.message, stack: errors.stack }) + logger.log("autohouse-sftp-connection-error", "ERROR", "api", xmlObj.bodyshopid, { + error: errors.message, + stack: errors.stack + }) ); try { //Connect to the FTP and upload all. @@ -186,13 +190,13 @@ async function uploadViaSFTP(xmlObj) { try { xmlObj.result = await sftp.put(Buffer.from(xmlObj.xml), `${xmlObj.filename}`); - logger.log("autohouse-sftp-upload", "DEBUG", "api", null, { + logger.log("autohouse-sftp-upload", "DEBUG", "api", xmlObj.bodyshopid, { imexshopid: xmlObj.imexshopid, filename: xmlObj.filename, result: xmlObj.result }); } catch (error) { - logger.log("autohouse-sftp-upload-error", "ERROR", "api", null, { + logger.log("autohouse-sftp-upload-error", "ERROR", "api", xmlObj.bodyshopid, { filename: xmlObj.filename, error: error.message, stack: error.stack @@ -200,7 +204,7 @@ async function uploadViaSFTP(xmlObj) { throw error; } } catch (error) { - logger.log("autohouse-sftp-error", "ERROR", "api", null, { error: error.message, stack: error.stack }); + logger.log("autohouse-sftp-error", "ERROR", "api", xmlObj.bodyshopid, { error: error.message, stack: error.stack }); throw error; } finally { sftp.end(); diff --git a/server/data/claimscorp.js b/server/data/claimscorp.js index 3db09cbcf..70737bd03 100644 --- a/server/data/claimscorp.js +++ b/server/data/claimscorp.js @@ -128,6 +128,7 @@ async function processShopData(shopsToProcess, start, end, skipUpload, allXMLRes } const xmlObj = { + bodyshopid: bodyshop.id, imexshopid: bodyshop.imexshopid, xml: builder.create({}, claimsCorpObject).end({ allowEmptyTags: true }), filename: `${bodyshop.claimscorpid}-${moment().format("YYYYMMDDTHHMMss")}.xml`, @@ -181,7 +182,10 @@ async function processShopData(shopsToProcess, start, end, skipUpload, allXMLRes async function uploadViaSFTP(xmlObj) { const sftp = new Client(); sftp.on("error", (errors) => - logger.log("claimscorp-sftp-connection-error", "ERROR", "api", null, { error: errors.message, stack: errors.stack }) + logger.log("claimscorp-sftp-connection-error", "ERROR", "api", xmlObj.bodyshopid, { + error: errors.message, + stack: errors.stack + }) ); try { //Connect to the FTP and upload all. @@ -189,13 +193,13 @@ async function uploadViaSFTP(xmlObj) { try { xmlObj.result = await sftp.put(Buffer.from(xmlObj.xml), `${xmlObj.filename}`); - logger.log("claimscorp-sftp-upload", "DEBUG", "api", null, { + logger.log("claimscorp-sftp-upload", "DEBUG", "api", xmlObj.bodyshopid, { imexshopid: xmlObj.imexshopid, filename: xmlObj.filename, result: xmlObj.result }); } catch (error) { - logger.log("claimscorp-sftp-upload-error", "ERROR", "api", null, { + logger.log("claimscorp-sftp-upload-error", "ERROR", "api", xmlObj.bodyshopid, { filename: xmlObj.filename, error: error.message, stack: error.stack @@ -203,7 +207,10 @@ async function uploadViaSFTP(xmlObj) { throw error; } } catch (error) { - logger.log("claimscorp-sftp-error", "ERROR", "api", null, { error: error.message, stack: error.stack }); + logger.log("claimscorp-sftp-error", "ERROR", "api", xmlObj.bodyshopid, { + error: error.message, + stack: error.stack + }); throw error; } finally { sftp.end(); diff --git a/server/data/kaizen.js b/server/data/kaizen.js index eb6abc056..a7a6aaf91 100644 --- a/server/data/kaizen.js +++ b/server/data/kaizen.js @@ -126,6 +126,7 @@ async function processShopData(shopsToProcess, start, end, skipUpload, allXMLRes } const xmlObj = { + bodyshopid: bodyshop.id, imexshopid: bodyshop.imexshopid, xml: builder.create({}, kaizenObject).end({ allowEmptyTags: true }), filename: `${bodyshop.shopname}-${moment().format("YYYYMMDDTHHMMss")}.xml`, @@ -178,7 +179,10 @@ async function processShopData(shopsToProcess, start, end, skipUpload, allXMLRes async function uploadViaSFTP(xmlObj) { const sftp = new Client(); sftp.on("error", (errors) => - logger.log("kaizen-sftp-connection-error", "ERROR", "api", null, { error: errors.message, stack: errors.stack }) + logger.log("kaizen-sftp-connection-error", "ERROR", "api", xmlObj.bodyshopid, { + error: errors.message, + stack: errors.stack + }) ); try { //Connect to the FTP and upload all. @@ -186,13 +190,13 @@ async function uploadViaSFTP(xmlObj) { try { xmlObj.result = await sftp.put(Buffer.from(xmlObj.xml), `${xmlObj.filename}`); - logger.log("kaizen-sftp-upload", "DEBUG", "api", null, { + logger.log("kaizen-sftp-upload", "DEBUG", "api", xmlObj.bodyshopid, { imexshopid: xmlObj.imexshopid, filename: xmlObj.filename, result: xmlObj.result }); } catch (error) { - logger.log("kaizen-sftp-upload-error", "ERROR", "api", null, { + logger.log("kaizen-sftp-upload-error", "ERROR", "api", xmlObj.bodyshopid, { filename: xmlObj.filename, error: error.message, stack: error.stack @@ -200,7 +204,7 @@ async function uploadViaSFTP(xmlObj) { throw error; } } catch (error) { - logger.log("kaizen-sftp-error", "ERROR", "api", null, { error: error.message, stack: error.stack }); + logger.log("kaizen-sftp-error", "ERROR", "api", xmlObj.bodyshopid, { error: error.message, stack: error.stack }); throw error; } finally { sftp.end(); From d2e1b32557a57bbe6b62124bd9dce281eed8608a Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 21 Nov 2024 17:35:04 -0800 Subject: [PATCH 25/74] feature/IO-3000-messaging-sockets-migrations2 - - Checkpoint, Signed-off-by: Dave Richer --- .../registerMessagingSocketHandlers.js | 140 ++++++++++++++---- .../chat-new-conversation.component.jsx | 7 +- .../chat-open-button.component.jsx | 7 +- .../schedule-event.component.jsx | 8 +- .../job-payments/job-payments.component.jsx | 24 ++- .../jobs-detail-header-actions.component.jsx | 10 +- .../payments-generate-link.component.jsx | 10 +- client/src/firebase/firebase.utils.js | 1 - client/src/graphql/conversations.queries.js | 22 +++ client/src/redux/messaging/messaging.sagas.js | 116 +++++++++++++-- 10 files changed, 272 insertions(+), 73 deletions(-) diff --git a/client/src/components/chat-affix/registerMessagingSocketHandlers.js b/client/src/components/chat-affix/registerMessagingSocketHandlers.js index b50d02c8a..e19c56a93 100644 --- a/client/src/components/chat-affix/registerMessagingSocketHandlers.js +++ b/client/src/components/chat-affix/registerMessagingSocketHandlers.js @@ -142,14 +142,47 @@ export const registerMessagingHandlers = ({ socket, client }) => { }); }; - const handleConversationChanged = (data) => { + const handleConversationChanged = async (data) => { if (!data) return; const { conversationId, type, job_conversations, ...fields } = data; - logLocal("handleConversationChanged", data); - // Identify the conversation in the Apollo cache + const updatedAt = new Date().toISOString(); + + const updateConversationList = (newConversation) => { + try { + const existingList = client.cache.readQuery({ + query: CONVERSATION_LIST_QUERY, + variables: { offset: 0 } + }); + + const updatedList = existingList?.conversations + ? [ + newConversation, + ...existingList.conversations.filter( + (conv) => conv.id !== newConversation.id // Prevent duplicates + ) + ] + : [newConversation]; + + client.cache.writeQuery({ + query: CONVERSATION_LIST_QUERY, + variables: { offset: 0 }, + data: { + conversations: updatedList + } + }); + } catch (error) { + console.error("Error updating conversation list in the cache:", error); + } + }; + + if (type === "conversation-created") { + updateConversationList({ ...fields, job_conversations, updated_at: updatedAt }); + return; + } + const cacheId = client.cache.identify({ __typename: "conversations", id: conversationId @@ -161,20 +194,20 @@ export const registerMessagingHandlers = ({ socket, client }) => { } if (type === "conversation-archived") { - // Remove all messages associated with this conversation - const messageRefs = client.cache.readFragment({ - id: cacheId, - fragment: gql` - fragment ConversationMessages on conversations { - messages { - id + try { + // Evict messages associated with the conversation + const messageRefs = client.cache.readFragment({ + id: cacheId, + fragment: gql` + fragment ConversationMessages on conversations { + messages { + id + } } - } - ` - }); + ` + }); - if (messageRefs?.messages) { - messageRefs.messages.forEach((message) => { + messageRefs?.messages?.forEach((message) => { const messageCacheId = client.cache.identify({ __typename: "messages", id: message.id @@ -183,39 +216,84 @@ export const registerMessagingHandlers = ({ socket, client }) => { client.cache.evict({ id: messageCacheId }); } }); + + // Evict the conversation itself + client.cache.evict({ id: cacheId }); + client.cache.gc(); // Trigger garbage collection + } catch (error) { + console.error("Error archiving conversation:", error); } - - // Evict the conversation itself - client.cache.evict({ id: cacheId }); - client.cache.gc(); // Trigger garbage collection to clean up unused entries - return; } + if (type === "conversation-unarchived") { + try { + // Fetch the conversation from the database if not already in the cache + const existingConversation = client.cache.readQuery({ + query: GET_CONVERSATION_DETAILS, + variables: { conversationId } + }); + + if (!existingConversation) { + const { data: fetchedData } = await client.query({ + query: GET_CONVERSATION_DETAILS, + variables: { conversationId }, + fetchPolicy: "network-only" + }); + + if (fetchedData?.conversations_by_pk) { + const conversationData = fetchedData.conversations_by_pk; + + // Enrich conversation data + const enrichedConversation = { + ...conversationData, + messages_aggregate: { + __typename: "messages_aggregate", + aggregate: { + __typename: "messages_aggregate_fields", + count: conversationData.messages.filter((message) => !message.read && !message.isoutbound).length + } + }, + updated_at: updatedAt + }; + + updateConversationList(enrichedConversation); + } + } + + // Mark the conversation as unarchived in the cache + client.cache.modify({ + id: cacheId, + fields: { + archived: () => false, + updated_at: () => updatedAt + } + }); + } catch (error) { + console.error("Error unarchiving conversation:", error); + } + return; + } + + // Handle other types of updates (e.g., marked read, tags added/removed) client.cache.modify({ id: cacheId, fields: { - // This is a catch-all for just sending it fields off conversation ...Object.fromEntries( - Object.entries(fields).map(([key, value]) => [ - key, - (cached) => (value !== undefined ? value : cached) // Update with new value or keep existing - ]) + Object.entries(fields).map(([key, value]) => [key, (cached) => (value !== undefined ? value : cached)]) ), ...(type === "conversation-marked-read" && { messages_aggregate: () => ({ - aggregate: { count: 0 } // Reset unread count + __typename: "messages_aggregate", + aggregate: { __typename: "messages_aggregate_fields", count: 0 } }) }), ...(type === "tag-added" && { - job_conversations: (existing = []) => { - // Merge existing job_conversations with new ones - return [...existing, ...job_conversations]; - } + job_conversations: (existing = []) => [...existing, ...job_conversations] }), ...(type === "tag-removed" && { job_conversations: (existing = [], { readField }) => - existing.filter((jobConversationRef) => readField("jobid", jobConversationRef) !== data.jobId) + existing.filter((jobRef) => readField("jobid", jobRef) !== data.jobId) }) } }); diff --git a/client/src/components/chat-new-conversation/chat-new-conversation.component.jsx b/client/src/components/chat-new-conversation/chat-new-conversation.component.jsx index b0b6054e4..e45ffb9b6 100644 --- a/client/src/components/chat-new-conversation/chat-new-conversation.component.jsx +++ b/client/src/components/chat-new-conversation/chat-new-conversation.component.jsx @@ -1,11 +1,12 @@ import { PlusCircleFilled } from "@ant-design/icons"; import { Button, Form, Popover } from "antd"; -import React from "react"; +import React, { useContext } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { openChatByPhone } from "../../redux/messaging/messaging.actions"; import PhoneFormItem, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component"; +import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser @@ -17,8 +18,10 @@ const mapDispatchToProps = (dispatch) => ({ export function ChatNewConversation({ openChatByPhone }) { const { t } = useTranslation(); const [form] = Form.useForm(); + const { socket } = useContext(SocketContext); + const handleFinish = (values) => { - openChatByPhone({ phone_num: values.phoneNumber }); + openChatByPhone({ phone_num: values.phoneNumber, socket }); form.resetFields(); }; diff --git a/client/src/components/chat-open-button/chat-open-button.component.jsx b/client/src/components/chat-open-button/chat-open-button.component.jsx index d2f0de528..dcd41c041 100644 --- a/client/src/components/chat-open-button/chat-open-button.component.jsx +++ b/client/src/components/chat-open-button/chat-open-button.component.jsx @@ -1,6 +1,6 @@ import { notification } from "antd"; import parsePhoneNumber from "libphonenumber-js"; -import React from "react"; +import React, { useContext } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { openChatByPhone } from "../../redux/messaging/messaging.actions"; @@ -9,6 +9,7 @@ import PhoneNumberFormatter from "../../utils/PhoneFormatter"; import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors"; import { searchingForConversation } from "../../redux/messaging/messaging.selectors"; +import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -21,6 +22,8 @@ const mapDispatchToProps = (dispatch) => ({ export function ChatOpenButton({ bodyshop, searchingForConversation, phone, jobid, openChatByPhone }) { const { t } = useTranslation(); + const { socket } = useContext(SocketContext); + if (!phone) return <>; if (!bodyshop.messagingservicesid) return {phone}; @@ -33,7 +36,7 @@ export function ChatOpenButton({ bodyshop, searchingForConversation, phone, jobi const p = parsePhoneNumber(phone, "CA"); if (searchingForConversation) return; //This is to prevent finding the same thing twice. if (p && p.isValid()) { - openChatByPhone({ phone_num: p.formatInternational(), jobid: jobid }); + openChatByPhone({ phone_num: p.formatInternational(), jobid: jobid, socket }); } else { notification["error"]({ message: t("messaging.error.invalidphone") }); } diff --git a/client/src/components/job-at-change/schedule-event.component.jsx b/client/src/components/job-at-change/schedule-event.component.jsx index e7f70ff65..bdf5f1a85 100644 --- a/client/src/components/job-at-change/schedule-event.component.jsx +++ b/client/src/components/job-at-change/schedule-event.component.jsx @@ -3,7 +3,7 @@ import { Button, Divider, Dropdown, Form, Input, notification, Popover, Select, import parsePhoneNumber from "libphonenumber-js"; import dayjs from "../../utils/day"; import queryString from "query-string"; -import React, { useState } from "react"; +import React, { useContext, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { Link, useLocation, useNavigate } from "react-router-dom"; @@ -24,6 +24,7 @@ import ScheduleEventNote from "./schedule-event.note.component"; import { useMutation } from "@apollo/client"; import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries"; import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component"; +import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop @@ -49,6 +50,8 @@ export function ScheduleEventComponent({ const searchParams = queryString.parse(useLocation().search); const [updateAppointment] = useMutation(UPDATE_APPOINTMENT); const [title, setTitle] = useState(event.title); + const { socket } = useContext(SocketContext); + const blockContent = ( ({ setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })), - setCardPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "cardPayment" })), - openChatByPhone: (phone) => dispatch(openChatByPhone(phone)), - setMessage: (text) => dispatch(setMessage(text)) + setCardPaymentContext: (context) => + dispatch( + setModalContext({ + context: context, + modal: "cardPayment" + }) + ) }); -export function JobPayments({ - job, - jobRO, - bodyshop, - setMessage, - openChatByPhone, - setPaymentContext, - setCardPaymentContext, - refetch -}) { +export function JobPayments({ job, jobRO, bodyshop, setPaymentContext, setCardPaymentContext, refetch }) { const { treatments: { ImEXPay } } = useSplitTreatments({ @@ -133,7 +127,7 @@ export function JobPayments({ } ]; - //Same as in RO guard. If changed, update in both. + //Same as in RO guard. If changed, update in both. const total = useMemo(() => { return ( job.payments && diff --git a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx index c9c3b8fb7..1fdeeec61 100644 --- a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx +++ b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx @@ -4,7 +4,7 @@ import { useSplitTreatments } from "@splitsoftware/splitio-react"; import { Button, Card, Dropdown, Form, Input, Modal, notification, Popconfirm, Popover, Select, Space } from "antd"; import axios from "axios"; import parsePhoneNumber from "libphonenumber-js"; -import React, { useMemo, useState } from "react"; +import React, { useContext, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { Link, useNavigate } from "react-router-dom"; @@ -30,6 +30,7 @@ import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component"; import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util"; import DuplicateJob from "./jobs-detail-header-actions.duplicate.util"; import JobsDetailHeaderActionsToggleProduction from "./jobs-detail-header-actions.toggle-production"; +import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -126,6 +127,7 @@ export function JobsDetailHeaderActions({ const [updateJob] = useMutation(UPDATE_JOB); const [voidJob] = useMutation(VOID_JOB); const [cancelAllAppointments] = useMutation(CANCEL_APPOINTMENTS_BY_JOB_ID); + const { socket } = useContext(SocketContext); const { treatments: { ImEXPay } @@ -299,7 +301,8 @@ export function JobsDetailHeaderActions({ if (p && p.isValid()) { openChatByPhone({ phone_num: p.formatInternational(), - jobid: job.id + jobid: job.id, + socket }); setMessage( `${window.location.protocol}//${window.location.host}/csi/${result.data.insert_csi.returning[0].id}` @@ -342,7 +345,8 @@ export function JobsDetailHeaderActions({ if (p && p.isValid()) { openChatByPhone({ phone_num: p.formatInternational(), - jobid: job.id + jobid: job.id, + socket }); setMessage(`${window.location.protocol}//${window.location.host}/csi/${job.csiinvites[0].id}`); } else { diff --git a/client/src/components/payments-generate-link/payments-generate-link.component.jsx b/client/src/components/payments-generate-link/payments-generate-link.component.jsx index b0f4b26b9..f9d84d10f 100644 --- a/client/src/components/payments-generate-link/payments-generate-link.component.jsx +++ b/client/src/components/payments-generate-link/payments-generate-link.component.jsx @@ -3,13 +3,14 @@ import { Button, Form, message, Popover, Space } from "antd"; import axios from "axios"; import Dinero from "dinero.js"; import { parsePhoneNumber } from "libphonenumber-js"; -import React, { useState } from "react"; +import React, { useContext, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component"; +import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -28,6 +29,7 @@ export function PaymentsGenerateLink({ bodyshop, currentUser, callback, job, ope const [open, setOpen] = useState(false); const [loading, setLoading] = useState(false); const [paymentLink, setPaymentLink] = useState(null); + const { socket } = useContext(SocketContext); const handleFinish = async ({ amount }) => { setLoading(true); @@ -50,7 +52,8 @@ export function PaymentsGenerateLink({ bodyshop, currentUser, callback, job, ope if (p) { openChatByPhone({ phone_num: p.formatInternational(), - jobid: job.id + jobid: job.id, + socket }); setMessage( t("payments.labels.smspaymentreminder", { @@ -106,7 +109,8 @@ export function PaymentsGenerateLink({ bodyshop, currentUser, callback, job, ope const p = parsePhoneNumber(job.ownr_ph1, "CA"); openChatByPhone({ phone_num: p.formatInternational(), - jobid: job.id + jobid: job.id, + socket }); setMessage( t("payments.labels.smspaymentreminder", { diff --git a/client/src/firebase/firebase.utils.js b/client/src/firebase/firebase.utils.js index 46a460525..33da2eeac 100644 --- a/client/src/firebase/firebase.utils.js +++ b/client/src/firebase/firebase.utils.js @@ -4,7 +4,6 @@ import { getAuth, updatePassword, updateProfile } from "firebase/auth"; import { getFirestore } from "firebase/firestore"; import { getMessaging, getToken, onMessage } from "firebase/messaging"; import { store } from "../redux/store"; -import axios from "axios"; const config = JSON.parse(import.meta.env.VITE_APP_FIREBASE_CONFIG); initializeApp(config); diff --git a/client/src/graphql/conversations.queries.js b/client/src/graphql/conversations.queries.js index 03bc0426b..373d70691 100644 --- a/client/src/graphql/conversations.queries.js +++ b/client/src/graphql/conversations.queries.js @@ -59,6 +59,8 @@ export const GET_CONVERSATION_DETAILS = gql` id phone_num archived + updated_at + unreadcnt label job_conversations { jobid @@ -119,6 +121,26 @@ export const CREATE_CONVERSATION = gql` insert_conversations(objects: $conversation) { returning { id + phone_num + archived + label + unreadcnt + job_conversations { + jobid + conversationid + job { + id + ownr_fn + ownr_ln + ownr_co_nm + ro_number + } + } + messages_aggregate(where: { read: { _eq: false }, isoutbound: { _eq: false } }) { + aggregate { + count + } + } } } } diff --git a/client/src/redux/messaging/messaging.sagas.js b/client/src/redux/messaging/messaging.sagas.js index 86757a427..fa17c6db5 100644 --- a/client/src/redux/messaging/messaging.sagas.js +++ b/client/src/redux/messaging/messaging.sagas.js @@ -2,7 +2,13 @@ import axios from "axios"; import parsePhoneNumber from "libphonenumber-js"; import { all, call, put, select, takeLatest } from "redux-saga/effects"; import { logImEXEvent } from "../../firebase/firebase.utils"; -import { CONVERSATION_ID_BY_PHONE, CREATE_CONVERSATION } from "../../graphql/conversations.queries"; +import { + CONVERSATION_ID_BY_PHONE, + CONVERSATION_LIST_QUERY, + CREATE_CONVERSATION, + GET_CONVERSATION_DETAILS, + TOGGLE_CONVERSATION_ARCHIVE +} from "../../graphql/conversations.queries"; import { INSERT_CONVERSATION_TAG } from "../../graphql/job-conversations.queries"; import client from "../../utils/GraphQLClient"; import { selectBodyshop } from "../user/user.selectors"; @@ -27,23 +33,24 @@ export function* onOpenChatByPhone() { export function* openChatByPhone({ payload }) { logImEXEvent("messaging_open_by_phone"); - const { phone_num, jobid } = payload; - + const { socket, phone_num, jobid } = payload; const p = parsePhoneNumber(phone_num, "CA"); const bodyshop = yield select(selectBodyshop); + try { const { data: { conversations } } = yield client.query({ query: CONVERSATION_ID_BY_PHONE, variables: { phone: p.number }, - fetchPolicy: 'no-cache' + fetchPolicy: "no-cache" }); if (conversations.length === 0) { + // No conversation exists, create a new one const { data: { - insert_conversations: { returning: newConversationsId } + insert_conversations: { returning: newConversations } } } = yield client.mutate({ mutation: CREATE_CONVERSATION, @@ -57,26 +64,107 @@ export function* openChatByPhone({ payload }) { ] } }); - yield put(setSelectedConversation(newConversationsId[0].id)); - } else if (conversations.length === 1) { - //got the ID. Open it. - yield put(setSelectedConversation(conversations[0].id)); - //Check to see if this job ID is already a child of it. If not add the tag. - if (jobid && !conversations[0].job_conversations.find((jc) => jc.jobid === jobid)) + const createdConversation = newConversations[0]; // Get the newly created conversation + + // Emit event for new conversation with full details + if (socket) { + socket.emit("conversation-modified", { + bodyshopId: bodyshop.id, + type: "conversation-created", + ...createdConversation + }); + } + + // Set the newly created conversation as selected + yield put(setSelectedConversation(createdConversation.id)); + } else if (conversations.length === 1) { + const conversation = conversations[0]; + + if (conversation.archived) { + // Conversation is archived, unarchive it in the DB + const { + data: { update_conversations_by_pk: updatedConversation } + } = yield client.mutate({ + mutation: TOGGLE_CONVERSATION_ARCHIVE, + variables: { + id: conversation.id, + archived: false + } + }); + + if (socket) { + socket.emit("conversation-modified", { + type: "conversation-unarchived", + conversationId: updatedConversation.id, + bodyshopId: bodyshop.id, + archived: false + }); + } + + // Update the conversation list in the cache + const existingConversations = client.cache.readQuery({ + query: CONVERSATION_LIST_QUERY, + variables: { offset: 0 } + }); + + client.cache.writeQuery({ + query: CONVERSATION_LIST_QUERY, + variables: { offset: 0 }, + data: { + conversations: [ + { + ...conversation, + archived: false, + updated_at: new Date().toISOString() + }, + ...(existingConversations?.conversations || []) + ] + } + }); + } + + // Check if the conversation exists in the cache + const cacheId = client.cache.identify({ + __typename: "conversations", + id: conversation.id + }); + + if (!cacheId) { + // Fetch the conversation details from the database + const { data } = yield client.query({ + query: GET_CONVERSATION_DETAILS, + variables: { conversationId: conversation.id } + }); + + // Write fetched data to the cache + client.cache.writeQuery({ + query: GET_CONVERSATION_DETAILS, + variables: { conversationId: conversation.id }, + data + }); + } + + // Open the conversation + yield put(setSelectedConversation(conversation.id)); + + // Check and add job tag if needed + if (jobid && !conversation.job_conversations.find((jc) => jc.jobid === jobid)) { yield client.mutate({ mutation: INSERT_CONVERSATION_TAG, variables: { - conversationId: conversations[0].id, + conversationId: conversation.id, jobId: jobid } }); + } } else { - console.log("ERROR: Multiple conversations found. "); + // Multiple conversations found + console.error("ERROR: Multiple conversations found."); yield put(setSelectedConversation(null)); } } catch (error) { - console.log("Error in sendMessage saga.", error); + console.error("Error in openChatByPhone saga.", error); } } From 8229e3593ca6afc6c43b5d960af520059e17e9d9 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 21 Nov 2024 17:39:05 -0800 Subject: [PATCH 26/74] feature/IO-3000-messaging-sockets-migrations2 - - remove unused query, Signed-off-by: Dave Richer --- client/src/graphql/conversations.queries.js | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/client/src/graphql/conversations.queries.js b/client/src/graphql/conversations.queries.js index 373d70691..33009ffbe 100644 --- a/client/src/graphql/conversations.queries.js +++ b/client/src/graphql/conversations.queries.js @@ -163,22 +163,3 @@ export const UPDATE_CONVERSATION_LABEL = gql` } } `; - -export const GET_CONVERSATION_MESSAGES = gql` - query GET_CONVERSATION_MESSAGES($conversationId: uuid!) { - conversation: conversations_by_pk(id: $conversationId) { - id - phone_num - updated_at - label - } - messages: messages(where: { conversationid: { _eq: $conversationId } }, order_by: { created_at: asc }) { - id - text - created_at - read - isoutbound - userid - } - } -`; From 38f13346e5efa2feee36e435dca4bb6e7d3f07ed Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 21 Nov 2024 18:44:38 -0800 Subject: [PATCH 27/74] feature/IO-3000-messaging-sockets-migrations2 - - testing and edge cases Signed-off-by: Dave Richer --- .../registerMessagingSocketHandlers.js | 201 ++++++++++---- .../chat-conversation-list.component.jsx | 11 +- .../chat-popup/chat-popup.component.jsx | 16 +- server/graphql-client/queries.js | 62 +---- server/sms/receive.js | 251 ++++++++---------- 5 files changed, 270 insertions(+), 271 deletions(-) diff --git a/client/src/components/chat-affix/registerMessagingSocketHandlers.js b/client/src/components/chat-affix/registerMessagingSocketHandlers.js index e19c56a93..49bce2ff8 100644 --- a/client/src/components/chat-affix/registerMessagingSocketHandlers.js +++ b/client/src/components/chat-affix/registerMessagingSocketHandlers.js @@ -12,61 +12,164 @@ const logLocal = (message, ...args) => { export const registerMessagingHandlers = ({ socket, client }) => { if (!(socket && client)) return; - const handleNewMessageSummary = (message) => { + const handleNewMessageSummary = async (message) => { const { conversationId, newConversation, existingConversation, isoutbound } = message; - logLocal("handleNewMessageSummary", message); - if (!existingConversation && newConversation?.phone_num) { - const queryResults = client.cache.readQuery({ - query: CONVERSATION_LIST_QUERY, - variables: { offset: 0 } - }); + const queryVariables = { offset: 0 }; - client.cache.writeQuery({ - query: CONVERSATION_LIST_QUERY, - variables: { offset: 0 }, - data: { - conversations: [ - { - ...newConversation, - updated_at: newConversation.updated_at || new Date().toISOString(), - unreadcnt: newConversation.unreadcnt || 0, - archived: newConversation.archived || false, - label: newConversation.label || null, - job_conversations: newConversation.job_conversations || [], - messages_aggregate: newConversation.messages_aggregate || { - aggregate: { count: isoutbound ? 0 : 1 } - } - }, - ...(queryResults?.conversations || []) - ] - } - }); - } else { - client.cache.modify({ - id: client.cache.identify({ - __typename: "conversations", - id: conversationId - }), - fields: { - updated_at: () => new Date().toISOString(), - archived(cached) { - // Unarchive the conversation if it was previously marked as archived - if (cached) { - return false; + // Handle new conversation + if (!existingConversation && newConversation?.phone_num) { + try { + const queryResults = client.cache.readQuery({ + query: CONVERSATION_LIST_QUERY, + variables: queryVariables + }); + + const enrichedConversation = { + ...newConversation, + updated_at: newConversation.updated_at || new Date().toISOString(), + unreadcnt: newConversation.unreadcnt || 0, + archived: newConversation.archived || false, + label: newConversation.label || null, + job_conversations: newConversation.job_conversations || [], + messages_aggregate: newConversation.messages_aggregate || { + __typename: "messages_aggregate", + aggregate: { + __typename: "messages_aggregate_fields", + count: isoutbound ? 0 : 1 } - return cached; }, - messages_aggregate(cached) { - // Increment unread count only if the message is inbound - if (!isoutbound) { - return { aggregate: { count: cached.aggregate.count + 1 } }; - } - return cached; + __typename: "conversations" + }; + + client.cache.writeQuery({ + query: CONVERSATION_LIST_QUERY, + variables: queryVariables, + data: { + conversations: [enrichedConversation, ...(queryResults?.conversations || [])] } + }); + } catch (error) { + console.error("Error updating cache for new conversation:", error); + } + return; + } + + // Handle existing conversation + if (existingConversation) { + let conversationDetails; + + // Fetch or read the conversation details + try { + conversationDetails = client.cache.readFragment({ + id: client.cache.identify({ + __typename: "conversations", + id: conversationId + }), + fragment: gql` + fragment ExistingConversation on conversations { + id + phone_num + updated_at + archived + label + unreadcnt + job_conversations { + jobid + conversationid + } + messages_aggregate { + aggregate { + count + } + } + __typename + } + ` + }); + } catch (error) { + console.warn("Conversation not found in cache, querying server..."); + } + + if (!conversationDetails) { + try { + const { data } = await client.query({ + query: GET_CONVERSATION_DETAILS, + variables: { conversationId }, + fetchPolicy: "network-only" + }); + conversationDetails = data?.conversations_by_pk; + } catch (error) { + console.error("Failed to fetch conversation details from server:", error); + return; } - }); + } + + if (!conversationDetails) { + console.error("Unable to retrieve conversation details. Skipping cache update."); + return; + } + + try { + const queryResults = client.cache.readQuery({ + query: CONVERSATION_LIST_QUERY, + variables: queryVariables + }); + + const isAlreadyInCache = queryResults?.conversations.some((conv) => conv.id === conversationId); + + if (!isAlreadyInCache) { + const enrichedConversation = { + ...conversationDetails, + archived: false, + __typename: "conversations", + messages_aggregate: { + __typename: "messages_aggregate", + aggregate: { + __typename: "messages_aggregate_fields", + count: + conversationDetails.messages?.filter( + (message) => !message.read && !message.isoutbound // Count unread, inbound messages + ).length || 0 + } + } + }; + + client.cache.writeQuery({ + query: CONVERSATION_LIST_QUERY, + variables: queryVariables, + data: { + conversations: [enrichedConversation, ...(queryResults?.conversations || [])] + } + }); + } + // Update existing conversation fields + client.cache.modify({ + id: client.cache.identify({ + __typename: "conversations", + id: conversationId + }), + fields: { + updated_at: () => new Date().toISOString(), + archived: () => false, + messages_aggregate(cached) { + if (!isoutbound) { + return { + __typename: "messages_aggregate", + aggregate: { + __typename: "messages_aggregate_fields", + count: cached.aggregate.count + 1 + } + }; + } + return cached; + } + } + }); + } catch (error) { + console.error("Error updating cache for existing conversation:", error); + } } }; @@ -300,7 +403,7 @@ export const registerMessagingHandlers = ({ socket, client }) => { }; const handleNewMessage = ({ conversationId, message }) => { - if (!conversationId || !message.id || !message.text) { + if (!conversationId || !message?.id || !message?.text) { return; } @@ -359,9 +462,9 @@ export const registerMessagingHandlers = ({ socket, client }) => { }); }; - socket.on("new-message", handleNewMessage); socket.on("new-message-summary", handleNewMessageSummary); socket.on("new-message-detailed", handleNewMessageDetailed); + socket.on("new-message", handleNewMessage); socket.on("message-changed", handleMessageChanged); socket.on("conversation-changed", handleConversationChanged); }; diff --git a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx index 38221085d..a8f95f59d 100644 --- a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx +++ b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx @@ -19,12 +19,7 @@ const mapDispatchToProps = (dispatch) => ({ setSelectedConversation: (conversationId) => dispatch(setSelectedConversation(conversationId)) }); -function ChatConversationListComponent({ - conversationList, - selectedConversation, - setSelectedConversation, - loadMoreConversations -}) { +function ChatConversationListComponent({ conversationList, selectedConversation, setSelectedConversation }) { const renderConversation = (index) => { const item = conversationList[index]; const cardContentRight = {item.updated_at}; @@ -69,13 +64,15 @@ function ChatConversationListComponent({ ); }; + // TODO: Can go back into virtuoso for additional fetch + // endReached={loadMoreConversations} // Calls loadMoreConversations when scrolled to the bottom + return (
renderConversation(index)} style={{ height: "100%", width: "100%" }} - endReached={loadMoreConversations} // Calls loadMoreConversations when scrolled to the bottom />
); diff --git a/client/src/components/chat-popup/chat-popup.component.jsx b/client/src/components/chat-popup/chat-popup.component.jsx index b2625261d..e67929d11 100644 --- a/client/src/components/chat-popup/chat-popup.component.jsx +++ b/client/src/components/chat-popup/chat-popup.component.jsx @@ -77,17 +77,6 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh }); }, [chatVisible, getConversations]); - const loadMoreConversations = useCallback(() => { - if (data) - fetchMore({ - variables: { - offset: data.conversations.length - } - }).catch((err) => { - console.error(`Error fetching more conversations: ${(err, err.message || "")}`); - }); - }, [data, fetchMore]); - const unreadCount = unreadData?.messages_aggregate?.aggregate?.count || 0; return ( @@ -114,10 +103,7 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh {loading ? ( ) : ( - + )} {selectedConversation ? : null} diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index 0a2b6a853..72224dfb6 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -1515,7 +1515,8 @@ exports.GET_JOB_BY_PK = `query GET_JOB_BY_PK($id: uuid!) { }`; //TODO:AIO The above query used to have parts order lines in it. Validate that this doesn't need it. -exports.QUERY_JOB_COSTING_DETAILS = ` query QUERY_JOB_COSTING_DETAILS($id: uuid!) { +exports.QUERY_JOB_COSTING_DETAILS = ` +query QUERY_JOB_COSTING_DETAILS($id: uuid!) { jobs_by_pk(id: $id) { ro_number clm_total @@ -2566,68 +2567,9 @@ exports.GET_JOBS_BY_PKS = `query GET_JOBS_BY_PKS($ids: [uuid!]!) { } `; -exports.GET_CONVERSATIONS = `query GET_CONVERSATIONS($bodyshopId: uuid!) { - conversations( - where: { bodyshopid: { _eq: $bodyshopId }, archived: { _eq: false } }, - order_by: { updated_at: desc }, - limit: 50 - ) { - phone_num - id - updated_at - unreadcnt - archived - label - messages_aggregate(where: { read: { _eq: false }, isoutbound: { _eq: false } }) { - aggregate { - count - } - } - job_conversations { - job { - id - ro_number - ownr_fn - ownr_ln - ownr_co_nm - } - } - } -} -`; - exports.MARK_MESSAGES_AS_READ = `mutation MARK_MESSAGES_AS_READ($conversationId: uuid!) { update_messages(where: { conversationid: { _eq: $conversationId } }, _set: { read: true }) { affected_rows } } `; - -exports.GET_CONVERSATION_DETAILS = ` - query GET_CONVERSATION_DETAILS($conversationId: uuid!) { - conversation: conversations_by_pk(id: $conversationId) { - id - phone_num - updated_at - label - job_conversations { - job { - id - ro_number - ownr_fn - ownr_ln - ownr_co_nm - } - } - } - messages: messages(where: { conversationid: { _eq: $conversationId } }, order_by: { created_at: asc }) { - id - text - created_at - read - isoutbound - userid - image_path - } - } -`; diff --git a/server/sms/receive.js b/server/sms/receive.js index f7f41652e..63c11a260 100644 --- a/server/sms/receive.js +++ b/server/sms/receive.js @@ -16,23 +16,21 @@ exports.receive = async (req, res) => { ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom } } = req; - logger.log("sms-inbound", "DEBUG", "api", null, { + const loggerData = { msid: req.body.SmsMessageSid, text: req.body.Body, image: !!req.body.MediaUrl0, image_path: generateMediaArray(req.body) - }); + }; + + logger.log("sms-inbound", "DEBUG", "api", null, loggerData); if (!req.body || !req.body.MessagingServiceSid || !req.body.SmsMessageSid) { logger.log("sms-inbound-error", "ERROR", "api", null, { - msid: req.body.SmsMessageSid, - text: req.body.Body, - image: !!req.body.MediaUrl0, - image_path: generateMediaArray(req.body), + ...loggerData, type: "malformed-request" }); - res.status(400).json({ success: false, error: "Malformed Request" }); - return; + return res.status(400).json({ success: false, error: "Malformed Request" }); } try { @@ -41,6 +39,14 @@ exports.receive = async (req, res) => { phone: phone(req.body.From).phoneNumber }); + if (!response.bodyshops[0]) { + return res.status(400).json({ success: false, error: "No matching bodyshop" }); + } + + const bodyshop = response.bodyshops[0]; + const isNewConversation = bodyshop.conversations.length === 0; + const isDuplicate = bodyshop.conversations.length > 1; + let newMessage = { msid: req.body.SmsMessageSid, text: req.body.Body, @@ -48,140 +54,105 @@ exports.receive = async (req, res) => { image_path: generateMediaArray(req.body) }; - if (response.bodyshops[0]) { - const bodyshop = response.bodyshops[0]; - if (bodyshop.conversations.length === 0) { - newMessage.conversation = { - data: { - bodyshopid: bodyshop.id, - phone_num: phone(req.body.From).phoneNumber, - archived: false - } - }; - - try { - const insertresp = await client.request(queries.RECEIVE_MESSAGE, { msg: newMessage }); - const createdConversation = insertresp?.insert_messages?.returning?.[0]?.conversation || null; - const message = insertresp?.insert_messages?.returning?.[0]; - - if (!createdConversation) { - throw new Error("Conversation data is missing from the response."); - } - - const broadcastRoom = getBodyshopRoom(createdConversation.bodyshop.id); - const conversationRoom = getBodyshopConversationRoom({ - bodyshopId: message.conversation.bodyshop.id, - conversationId: message.conversation.id - }); - - ioRedis.to(broadcastRoom).emit("new-message-summary", { - isoutbound: false, - existingConversation: false, - newConversation: createdConversation, - conversationId: createdConversation.id, - updated_at: message.updated_at, - msid: message.sid, - summary: true - }); - - ioRedis.to(conversationRoom).emit("new-message-detailed", { - newMessage: message, - isoutbound: false, - newConversation: createdConversation, - existingConversation: false, - conversationId: createdConversation.id, - summary: false - }); - - logger.log("sms-inbound-success", "DEBUG", "api", null, { - newMessage, - createdConversation - }); - - res.status(200).send(""); - return; - } catch (e) { - handleError(req, e, res, "RECEIVE_MESSAGE"); - return; - } - } else if (bodyshop.conversations.length === 1) { - newMessage.conversationid = bodyshop.conversations[0].id; - } else { - logger.log("sms-inbound-error", "ERROR", "api", null, { - msid: req.body.SmsMessageSid, - text: req.body.Body, - image: !!req.body.MediaUrl0, - image_path: generateMediaArray(req.body), - messagingServiceSid: req.body.MessagingServiceSid, - type: "duplicate-phone" - }); - res.status(400).json({ success: false, error: "Duplicate phone number" }); - return; - } - - try { - const insertresp = await client.request(queries.INSERT_MESSAGE, { - msg: newMessage, - conversationid: newMessage.conversationid - }); - - const message = insertresp.insert_messages.returning[0]; - const data = { - type: "messaging-inbound", - conversationid: message.conversationid || "", - text: message.text || "", - messageid: message.id || "", - phone_num: message.conversation.phone_num || "" - }; - - const fcmresp = await admin.messaging().send({ - topic: `${message.conversation.bodyshop.imexshopid}-messaging`, - notification: { - title: InstanceManager({ - imex: `ImEX Online Message - ${data.phone_num}`, - rome: `Rome Online Message - ${data.phone_num}`, - promanager: `ProManager Message - ${data.phone_num}` - }), - body: message.image_path ? `Image ${message.text}` : message.text - }, - data - }); - - logger.log("sms-inbound-success", "DEBUG", "api", null, { - newMessage, - fcmresp - }); - - const broadcastRoom = getBodyshopRoom(message.conversation.bodyshop.id); - const conversationRoom = getBodyshopConversationRoom({ - bodyshopId: message.conversation.bodyshop.id, - conversationId: message.conversation.id - }); - - ioRedis.to(broadcastRoom).emit("new-message-summary", { - isoutbound: false, - existingConversation: true, - conversationId: message.conversationid, - updated_at: message.updated_at, - msid: message.sid, - summary: true - }); - - ioRedis.to(conversationRoom).emit("new-message-detailed", { - newMessage: message, - isoutbound: false, - existingConversation: true, - conversationId: message.conversationid, - summary: false - }); - - res.status(200).send(""); - } catch (e) { - handleError(req, e, res, "INSERT_MESSAGE"); - } + if (isDuplicate) { + logger.log("sms-inbound-error", "ERROR", "api", null, { + ...loggerData, + messagingServiceSid: req.body.MessagingServiceSid, + type: "duplicate-phone" + }); + return res.status(400).json({ success: false, error: "Duplicate phone number" }); } + + if (isNewConversation) { + newMessage.conversation = { + data: { + bodyshopid: bodyshop.id, + phone_num: phone(req.body.From).phoneNumber, + archived: false + } + }; + } else { + const existingConversation = bodyshop.conversations[0]; + + // Update the conversation to unarchive it + if (existingConversation.archived) { + await client.request(queries.UNARCHIVE_CONVERSATION, { + id: existingConversation.id, + archived: false + }); + } + + newMessage.conversationid = existingConversation.id; + } + + const query = isNewConversation ? queries.RECEIVE_MESSAGE : queries.INSERT_MESSAGE; + const variables = isNewConversation + ? { msg: newMessage } + : { msg: newMessage, conversationid: newMessage.conversationid }; + + const insertresp = await client.request(query, variables); + const message = insertresp?.insert_messages?.returning?.[0]; + const conversation = message?.conversation || null; + + if (!conversation) { + throw new Error("Conversation data is missing from the response."); + } + + const broadcastRoom = getBodyshopRoom(conversation.bodyshop.id); + const conversationRoom = getBodyshopConversationRoom({ + bodyshopId: conversation.bodyshop.id, + conversationId: conversation.id + }); + + const commonPayload = { + isoutbound: false, + conversationId: conversation.id, + updated_at: message.updated_at, + msid: message.sid + }; + + ioRedis.to(broadcastRoom).emit("new-message-summary", { + ...commonPayload, + existingConversation: !isNewConversation, + newConversation: isNewConversation ? conversation : null, + summary: true + }); + + ioRedis.to(conversationRoom).emit("new-message-detailed", { + newMessage: message, + ...commonPayload, + newConversation: isNewConversation ? conversation : null, + existingConversation: !isNewConversation, + summary: false + }); + + const fcmresp = await admin.messaging().send({ + topic: `${message.conversation.bodyshop.imexshopid}-messaging`, + notification: { + title: InstanceManager({ + imex: `ImEX Online Message - ${message.conversation.phone_num}`, + rome: `Rome Online Message - ${message.conversation.phone_num}`, + promanager: `ProManager Message - ${message.conversation.phone_num}` + }), + body: message.image_path ? `Image ${message.text}` : message.text + }, + data: { + type: "messaging-inbound", + conversationid: message.conversationid || "", + text: message.text || "", + messageid: message.id || "", + phone_num: message.conversation.phone_num || "" + } + }); + + logger.log("sms-inbound-success", "DEBUG", "api", null, { + newMessage, + fcmresp + }); + + res.status(200).send(""); } catch (e) { - handleError(req, e, res, "FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID"); + handleError(req, e, res, "RECEIVE_MESSAGE"); } }; From 525f795ce0d500620db09b1d266d16ec65f804e4 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 21 Nov 2024 19:17:42 -0800 Subject: [PATCH 28/74] feature/IO-3000-messaging-sockets-migrations2 - - dumb down archive/unarchive Signed-off-by: Dave Richer --- .../registerMessagingSocketHandlers.js | 84 ++----------------- client/src/redux/messaging/messaging.sagas.js | 3 +- 2 files changed, 11 insertions(+), 76 deletions(-) diff --git a/client/src/components/chat-affix/registerMessagingSocketHandlers.js b/client/src/components/chat-affix/registerMessagingSocketHandlers.js index 49bce2ff8..8034353c8 100644 --- a/client/src/components/chat-affix/registerMessagingSocketHandlers.js +++ b/client/src/components/chat-affix/registerMessagingSocketHandlers.js @@ -250,6 +250,7 @@ export const registerMessagingHandlers = ({ socket, client }) => { const { conversationId, type, job_conversations, ...fields } = data; logLocal("handleConversationChanged", data); + console.log(`--------------${type}-----------------`); const updatedAt = new Date().toISOString(); @@ -296,85 +297,18 @@ export const registerMessagingHandlers = ({ socket, client }) => { return; } - if (type === "conversation-archived") { + if (type === "conversation-unarchived" || type === "conversation-archived") { try { - // Evict messages associated with the conversation - const messageRefs = client.cache.readFragment({ - id: cacheId, - fragment: gql` - fragment ConversationMessages on conversations { - messages { - id - } - } - ` - }); - - messageRefs?.messages?.forEach((message) => { - const messageCacheId = client.cache.identify({ - __typename: "messages", - id: message.id - }); - if (messageCacheId) { - client.cache.evict({ id: messageCacheId }); - } - }); - - // Evict the conversation itself - client.cache.evict({ id: cacheId }); - client.cache.gc(); // Trigger garbage collection - } catch (error) { - console.error("Error archiving conversation:", error); - } - return; - } - - if (type === "conversation-unarchived") { - try { - // Fetch the conversation from the database if not already in the cache - const existingConversation = client.cache.readQuery({ - query: GET_CONVERSATION_DETAILS, - variables: { conversationId } - }); - - if (!existingConversation) { - const { data: fetchedData } = await client.query({ - query: GET_CONVERSATION_DETAILS, - variables: { conversationId }, - fetchPolicy: "network-only" - }); - - if (fetchedData?.conversations_by_pk) { - const conversationData = fetchedData.conversations_by_pk; - - // Enrich conversation data - const enrichedConversation = { - ...conversationData, - messages_aggregate: { - __typename: "messages_aggregate", - aggregate: { - __typename: "messages_aggregate_fields", - count: conversationData.messages.filter((message) => !message.read && !message.isoutbound).length - } - }, - updated_at: updatedAt - }; - - updateConversationList(enrichedConversation); - } - } - - // Mark the conversation as unarchived in the cache - client.cache.modify({ - id: cacheId, - fields: { - archived: () => false, - updated_at: () => updatedAt - } + // Refetch the conversation list query to update the UI + const queryVariables = { offset: 0 }; + await client.refetchQueries({ + include: [CONVERSATION_LIST_QUERY], + variables: queryVariables }); } catch (error) { - console.error("Error unarchiving conversation:", error); + console.error("Error refetching conversation list after unarchiving:", error); } + return; } diff --git a/client/src/redux/messaging/messaging.sagas.js b/client/src/redux/messaging/messaging.sagas.js index fa17c6db5..2ee7a8bac 100644 --- a/client/src/redux/messaging/messaging.sagas.js +++ b/client/src/redux/messaging/messaging.sagas.js @@ -34,6 +34,7 @@ export function* onOpenChatByPhone() { export function* openChatByPhone({ payload }) { logImEXEvent("messaging_open_by_phone"); const { socket, phone_num, jobid } = payload; + if (!socket || !phone_num) return; const p = parsePhoneNumber(phone_num, "CA"); const bodyshop = yield select(selectBodyshop); @@ -67,7 +68,7 @@ export function* openChatByPhone({ payload }) { const createdConversation = newConversations[0]; // Get the newly created conversation - // Emit event for new conversation with full details + // // Emit event for new conversation with full details if (socket) { socket.emit("conversation-modified", { bodyshopId: bodyshop.id, From 12ed8d3830534aed4cdb946d5b9e0997d689ae8b Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 21 Nov 2024 19:21:51 -0800 Subject: [PATCH 29/74] feature/IO-3000-messaging-sockets-migrations2 - - dumb down archive/unarchive Signed-off-by: Dave Richer --- .../registerMessagingSocketHandlers.js | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/client/src/components/chat-affix/registerMessagingSocketHandlers.js b/client/src/components/chat-affix/registerMessagingSocketHandlers.js index 8034353c8..a20781dec 100644 --- a/client/src/components/chat-affix/registerMessagingSocketHandlers.js +++ b/client/src/components/chat-affix/registerMessagingSocketHandlers.js @@ -250,7 +250,6 @@ export const registerMessagingHandlers = ({ socket, client }) => { const { conversationId, type, job_conversations, ...fields } = data; logLocal("handleConversationChanged", data); - console.log(`--------------${type}-----------------`); const updatedAt = new Date().toISOString(); @@ -287,6 +286,23 @@ export const registerMessagingHandlers = ({ socket, client }) => { return; } + if (type === "conversation-unarchived" || type === "conversation-archived") { + try { + // Refetch the conversation list and details queries + await client.refetchQueries({ + include: [CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS], + variables: [ + { query: CONVERSATION_LIST_QUERY, variables: { offset: 0 } }, + { query: GET_CONVERSATION_DETAILS, variables: { conversationId } } + ] + }); + } catch (error) { + console.error("Error refetching queries after conversation state change:", error); + } + + return; + } + const cacheId = client.cache.identify({ __typename: "conversations", id: conversationId @@ -297,21 +313,6 @@ export const registerMessagingHandlers = ({ socket, client }) => { return; } - if (type === "conversation-unarchived" || type === "conversation-archived") { - try { - // Refetch the conversation list query to update the UI - const queryVariables = { offset: 0 }; - await client.refetchQueries({ - include: [CONVERSATION_LIST_QUERY], - variables: queryVariables - }); - } catch (error) { - console.error("Error refetching conversation list after unarchiving:", error); - } - - return; - } - // Handle other types of updates (e.g., marked read, tags added/removed) client.cache.modify({ id: cacheId, From 141deff41eb19dc2016b0be7065811d1fc228e41 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 21 Nov 2024 19:37:25 -0800 Subject: [PATCH 30/74] feature/IO-3000-messaging-sockets-migrations2 - - harden openMessageByPhone Signed-off-by: Dave Richer --- .../registerMessagingSocketHandlers.js | 38 +++++---- client/src/redux/messaging/messaging.sagas.js | 78 +++++-------------- 2 files changed, 39 insertions(+), 77 deletions(-) diff --git a/client/src/components/chat-affix/registerMessagingSocketHandlers.js b/client/src/components/chat-affix/registerMessagingSocketHandlers.js index a20781dec..76cc2c741 100644 --- a/client/src/components/chat-affix/registerMessagingSocketHandlers.js +++ b/client/src/components/chat-affix/registerMessagingSocketHandlers.js @@ -286,23 +286,6 @@ export const registerMessagingHandlers = ({ socket, client }) => { return; } - if (type === "conversation-unarchived" || type === "conversation-archived") { - try { - // Refetch the conversation list and details queries - await client.refetchQueries({ - include: [CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS], - variables: [ - { query: CONVERSATION_LIST_QUERY, variables: { offset: 0 } }, - { query: GET_CONVERSATION_DETAILS, variables: { conversationId } } - ] - }); - } catch (error) { - console.error("Error refetching queries after conversation state change:", error); - } - - return; - } - const cacheId = client.cache.identify({ __typename: "conversations", id: conversationId @@ -313,6 +296,27 @@ export const registerMessagingHandlers = ({ socket, client }) => { return; } + if (type === "conversation-unarchived" || type === "conversation-archived") { + try { + const listQueryVariables = { offset: 0 }; + const detailsQueryVariables = { conversationId }; + + // Refetch the conversation list and details queries + await client.refetchQueries({ + include: [CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS], + variables: [ + { query: CONVERSATION_LIST_QUERY, variables: listQueryVariables }, + { query: GET_CONVERSATION_DETAILS, variables: detailsQueryVariables } + ] + }); + + console.log("Refetched conversation list and details after state change."); + } catch (error) { + console.error("Error refetching queries after conversation state change:", error); + } + + return; + } // Handle other types of updates (e.g., marked read, tags added/removed) client.cache.modify({ id: cacheId, diff --git a/client/src/redux/messaging/messaging.sagas.js b/client/src/redux/messaging/messaging.sagas.js index 2ee7a8bac..d2b428e1f 100644 --- a/client/src/redux/messaging/messaging.sagas.js +++ b/client/src/redux/messaging/messaging.sagas.js @@ -68,24 +68,23 @@ export function* openChatByPhone({ payload }) { const createdConversation = newConversations[0]; // Get the newly created conversation - // // Emit event for new conversation with full details - if (socket) { - socket.emit("conversation-modified", { - bodyshopId: bodyshop.id, - type: "conversation-created", - ...createdConversation - }); - } + // Emit event for new conversation with full details + socket.emit("conversation-modified", { + bodyshopId: bodyshop.id, + type: "conversation-created", + ...createdConversation + }); // Set the newly created conversation as selected yield put(setSelectedConversation(createdConversation.id)); } else if (conversations.length === 1) { const conversation = conversations[0]; + let updatedConversation = conversation; if (conversation.archived) { // Conversation is archived, unarchive it in the DB const { - data: { update_conversations_by_pk: updatedConversation } + data: { update_conversations_by_pk: unarchivedConversation } } = yield client.mutate({ mutation: TOGGLE_CONVERSATION_ARCHIVE, variables: { @@ -94,67 +93,26 @@ export function* openChatByPhone({ payload }) { } }); - if (socket) { - socket.emit("conversation-modified", { - type: "conversation-unarchived", - conversationId: updatedConversation.id, - bodyshopId: bodyshop.id, - archived: false - }); - } + updatedConversation = unarchivedConversation; - // Update the conversation list in the cache - const existingConversations = client.cache.readQuery({ - query: CONVERSATION_LIST_QUERY, - variables: { offset: 0 } - }); - - client.cache.writeQuery({ - query: CONVERSATION_LIST_QUERY, - variables: { offset: 0 }, - data: { - conversations: [ - { - ...conversation, - archived: false, - updated_at: new Date().toISOString() - }, - ...(existingConversations?.conversations || []) - ] - } - }); - } - - // Check if the conversation exists in the cache - const cacheId = client.cache.identify({ - __typename: "conversations", - id: conversation.id - }); - - if (!cacheId) { - // Fetch the conversation details from the database - const { data } = yield client.query({ - query: GET_CONVERSATION_DETAILS, - variables: { conversationId: conversation.id } - }); - - // Write fetched data to the cache - client.cache.writeQuery({ - query: GET_CONVERSATION_DETAILS, - variables: { conversationId: conversation.id }, - data + // Emit the unarchived event only once + socket.emit("conversation-modified", { + type: "conversation-unarchived", + conversationId: unarchivedConversation.id, + bodyshopId: bodyshop.id, + archived: false }); } // Open the conversation - yield put(setSelectedConversation(conversation.id)); + yield put(setSelectedConversation(updatedConversation.id)); // Check and add job tag if needed - if (jobid && !conversation.job_conversations.find((jc) => jc.jobid === jobid)) { + if (jobid && !updatedConversation.job_conversations.find((jc) => jc.jobid === jobid)) { yield client.mutate({ mutation: INSERT_CONVERSATION_TAG, variables: { - conversationId: conversation.id, + conversationId: updatedConversation.id, jobId: jobid } }); From 6504b27eca8d5d52193e2796fa16fa56aaa57831 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 21 Nov 2024 22:14:39 -0800 Subject: [PATCH 31/74] feature/IO-3000-messaging-sockets-migrations2 - - A lot of a lot of testing.... Signed-off-by: Dave Richer --- .../registerMessagingSocketHandlers.js | 453 ++++++++++-------- .../chat-conversation.container.jsx | 114 ++++- client/src/utils/GraphQLClient.js | 16 + server/graphql-client/queries.js | 3 + server/sms/status.js | 39 +- 5 files changed, 376 insertions(+), 249 deletions(-) diff --git a/client/src/components/chat-affix/registerMessagingSocketHandlers.js b/client/src/components/chat-affix/registerMessagingSocketHandlers.js index 76cc2c741..5b4f34ea1 100644 --- a/client/src/components/chat-affix/registerMessagingSocketHandlers.js +++ b/client/src/components/chat-affix/registerMessagingSocketHandlers.js @@ -14,40 +14,46 @@ export const registerMessagingHandlers = ({ socket, client }) => { const handleNewMessageSummary = async (message) => { const { conversationId, newConversation, existingConversation, isoutbound } = message; - logLocal("handleNewMessageSummary", message); + logLocal("handleNewMessageSummary - Start", { message, isNew: !existingConversation }); const queryVariables = { offset: 0 }; + // Utility function to enrich conversation data + const enrichConversation = (conversation, isOutbound) => ({ + ...conversation, + updated_at: conversation.updated_at || new Date().toISOString(), + unreadcnt: conversation.unreadcnt || 0, + archived: conversation.archived || false, + label: conversation.label || null, + job_conversations: conversation.job_conversations || [], + messages_aggregate: conversation.messages_aggregate || { + __typename: "messages_aggregate", + aggregate: { + __typename: "messages_aggregate_fields", + count: isOutbound ? 0 : 1 + } + }, + __typename: "conversations" + }); + // Handle new conversation if (!existingConversation && newConversation?.phone_num) { + logLocal("handleNewMessageSummary - New Conversation", newConversation); + try { const queryResults = client.cache.readQuery({ query: CONVERSATION_LIST_QUERY, variables: queryVariables }); - const enrichedConversation = { - ...newConversation, - updated_at: newConversation.updated_at || new Date().toISOString(), - unreadcnt: newConversation.unreadcnt || 0, - archived: newConversation.archived || false, - label: newConversation.label || null, - job_conversations: newConversation.job_conversations || [], - messages_aggregate: newConversation.messages_aggregate || { - __typename: "messages_aggregate", - aggregate: { - __typename: "messages_aggregate_fields", - count: isoutbound ? 0 : 1 - } - }, - __typename: "conversations" - }; + const enrichedConversation = enrichConversation(newConversation, isoutbound); - client.cache.writeQuery({ - query: CONVERSATION_LIST_QUERY, - variables: queryVariables, - data: { - conversations: [enrichedConversation, ...(queryResults?.conversations || [])] + client.cache.modify({ + id: "ROOT_QUERY", + fields: { + conversations(existingConversations = []) { + return [enrichedConversation, ...existingConversations]; + } } }); } catch (error) { @@ -60,13 +66,10 @@ export const registerMessagingHandlers = ({ socket, client }) => { if (existingConversation) { let conversationDetails; - // Fetch or read the conversation details + // Attempt to read existing conversation details from cache try { conversationDetails = client.cache.readFragment({ - id: client.cache.identify({ - __typename: "conversations", - id: conversationId - }), + id: client.cache.identify({ __typename: "conversations", id: conversationId }), fragment: gql` fragment ExistingConversation on conversations { id @@ -89,9 +92,10 @@ export const registerMessagingHandlers = ({ socket, client }) => { ` }); } catch (error) { - console.warn("Conversation not found in cache, querying server..."); + logLocal("handleNewMessageSummary - Cache miss for conversation, fetching from server", { conversationId }); } + // Fetch conversation details from server if not in cache if (!conversationDetails) { try { const { data } = await client.query({ @@ -106,12 +110,14 @@ export const registerMessagingHandlers = ({ socket, client }) => { } } + // Validate that conversation details were retrieved if (!conversationDetails) { console.error("Unable to retrieve conversation details. Skipping cache update."); return; } try { + // Check if the conversation is already in the cache const queryResults = client.cache.readQuery({ query: CONVERSATION_LIST_QUERY, variables: queryVariables @@ -120,46 +126,32 @@ export const registerMessagingHandlers = ({ socket, client }) => { const isAlreadyInCache = queryResults?.conversations.some((conv) => conv.id === conversationId); if (!isAlreadyInCache) { - const enrichedConversation = { - ...conversationDetails, - archived: false, - __typename: "conversations", - messages_aggregate: { - __typename: "messages_aggregate", - aggregate: { - __typename: "messages_aggregate_fields", - count: - conversationDetails.messages?.filter( - (message) => !message.read && !message.isoutbound // Count unread, inbound messages - ).length || 0 - } - } - }; + const enrichedConversation = enrichConversation(conversationDetails, isoutbound); - client.cache.writeQuery({ - query: CONVERSATION_LIST_QUERY, - variables: queryVariables, - data: { - conversations: [enrichedConversation, ...(queryResults?.conversations || [])] + client.cache.modify({ + id: "ROOT_QUERY", + fields: { + conversations(existingConversations = []) { + return [enrichedConversation, ...existingConversations]; + } } }); } - // Update existing conversation fields + + // Update fields for the existing conversation in the cache client.cache.modify({ - id: client.cache.identify({ - __typename: "conversations", - id: conversationId - }), + id: client.cache.identify({ __typename: "conversations", id: conversationId }), fields: { updated_at: () => new Date().toISOString(), archived: () => false, - messages_aggregate(cached) { + messages_aggregate(cached = { aggregate: { count: 0 } }) { + const currentCount = cached.aggregate?.count || 0; if (!isoutbound) { return { __typename: "messages_aggregate", aggregate: { __typename: "messages_aggregate_fields", - count: cached.aggregate.count + 1 + count: currentCount + 1 } }; } @@ -176,80 +168,107 @@ export const registerMessagingHandlers = ({ socket, client }) => { const handleNewMessageDetailed = (message) => { const { conversationId, newMessage } = message; - logLocal("handleNewMessageDetailed", message); + logLocal("handleNewMessageDetailed - Start", message); - // Append the new message to the conversation's message list - const queryResults = client.cache.readQuery({ - query: GET_CONVERSATION_DETAILS, - variables: { conversationId } - }); - - if (queryResults) { - client.cache.writeQuery({ + try { + // Check if the conversation exists in the cache + const queryResults = client.cache.readQuery({ query: GET_CONVERSATION_DETAILS, - variables: { conversationId }, - data: { - ...queryResults, - conversations_by_pk: { - ...queryResults.conversations_by_pk, - messages: [...queryResults.conversations_by_pk.messages, newMessage] + variables: { conversationId } + }); + + if (!queryResults?.conversations_by_pk) { + console.warn("Conversation not found in cache:", { conversationId }); + return; + } + + // Append the new message to the conversation's message list using cache.modify + client.cache.modify({ + id: client.cache.identify({ __typename: "conversations", id: conversationId }), + fields: { + messages(existingMessages = []) { + return [...existingMessages, newMessage]; } } }); + + logLocal("handleNewMessageDetailed - Message appended successfully", { conversationId, newMessage }); + } catch (error) { + console.error("Error updating conversation messages in cache:", error); } }; const handleMessageChanged = (message) => { - if (!message) return; + if (!message) { + logLocal("handleMessageChanged - No message provided", message); + return; + } - logLocal("handleMessageChanged", message); + logLocal("handleMessageChanged - Start", message); - client.cache.modify({ - id: client.cache.identify({ __typename: "conversations", id: message.conversationid }), - fields: { - ...(message.type === "status-changed" && { - messages(existing = [], { readField }) { - return existing.map((messageRef) => { - // Match the message by ID + try { + client.cache.modify({ + id: client.cache.identify({ __typename: "conversations", id: message.conversationid }), + fields: { + messages(existingMessages = [], { readField }) { + return existingMessages.map((messageRef) => { + // Check if this is the message to update if (readField("id", messageRef) === message.id) { const currentStatus = readField("status", messageRef); - // Prevent overwriting if the current status is already "delivered" - if (currentStatus === "delivered") { - return messageRef; - } - - // Update the existing message fields - return client.cache.writeFragment({ - id: messageRef.__ref, - fragment: gql` - fragment UpdatedMessage on messages { - id - status - conversationid - __typename + // Handle known types of message changes + switch (message.type) { + case "status-changed": + // Prevent overwriting if the current status is already "delivered" + if (currentStatus === "delivered") { + logLocal("handleMessageChanged - Status already delivered, skipping update", { + messageId: message.id + }); + return messageRef; } - `, - data: { - __typename: "messages", - ...message // Only update the fields provided in the message object - } - }); + + // Update the status field + return { + ...messageRef, + status: message.status + }; + + case "text-updated": + // Handle changes to the message text + return { + ...messageRef, + text: message.text + }; + + // Add cases for other known message types as needed + + default: + // Log a warning for unhandled message types + logLocal("handleMessageChanged - Unhandled message type", { type: message.type }); + return messageRef; + } } return messageRef; // Keep other messages unchanged }); } - }) - } - }); + } + }); + + logLocal("handleMessageChanged - Message updated successfully", { messageId: message.id, type: message.type }); + } catch (error) { + console.error("handleMessageChanged - Error modifying cache:", error); + } }; const handleConversationChanged = async (data) => { - if (!data) return; + if (!data) { + logLocal("handleConversationChanged - No data provided", data); + return; + } - const { conversationId, type, job_conversations, ...fields } = data; - logLocal("handleConversationChanged", data); + const { conversationId, type, job_conversations, messageIds, ...fields } = data; + logLocal("handleConversationChanged - Start", data); const updatedAt = new Date().toISOString(); @@ -263,9 +282,7 @@ export const registerMessagingHandlers = ({ socket, client }) => { const updatedList = existingList?.conversations ? [ newConversation, - ...existingList.conversations.filter( - (conv) => conv.id !== newConversation.id // Prevent duplicates - ) + ...existingList.conversations.filter((conv) => conv.id !== newConversation.id) // Prevent duplicates ] : [newConversation]; @@ -276,129 +293,149 @@ export const registerMessagingHandlers = ({ socket, client }) => { conversations: updatedList } }); + + logLocal("handleConversationChanged - Conversation list updated successfully", newConversation); } catch (error) { console.error("Error updating conversation list in the cache:", error); } }; - if (type === "conversation-created") { - updateConversationList({ ...fields, job_conversations, updated_at: updatedAt }); - return; - } + // Handle specific types + try { + switch (type) { + case "conversation-marked-read": + if (conversationId && messageIds?.length > 0) { + client.cache.modify({ + id: client.cache.identify({ __typename: "conversations", id: conversationId }), + fields: { + messages(existingMessages = [], { readField }) { + return existingMessages.map((message) => { + if (messageIds.includes(readField("id", message))) { + return { ...message, read: true }; + } + return message; + }); + }, + messages_aggregate: () => ({ + __typename: "messages_aggregate", + aggregate: { __typename: "messages_aggregate_fields", count: 0 } + }) + } + }); + } + break; - const cacheId = client.cache.identify({ - __typename: "conversations", - id: conversationId - }); + case "conversation-created": + updateConversationList({ ...fields, job_conversations, updated_at: updatedAt }); + break; - if (!cacheId) { - console.error(`Could not find conversation with id: ${conversationId}`); - return; - } + case "conversation-unarchived": + case "conversation-archived": + // Would like to someday figure out how to get this working without refetch queries, + // But I have but a solid 4 hours into it, and there are just too many weird occurrences + try { + const listQueryVariables = { offset: 0 }; + const detailsQueryVariables = { conversationId }; - if (type === "conversation-unarchived" || type === "conversation-archived") { - try { - const listQueryVariables = { offset: 0 }; - const detailsQueryVariables = { conversationId }; + // Refetch conversation list and details + await client.refetchQueries({ + include: [CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS], + variables: [ + { query: CONVERSATION_LIST_QUERY, variables: listQueryVariables }, + { query: GET_CONVERSATION_DETAILS, variables: detailsQueryVariables } + ] + }); - // Refetch the conversation list and details queries - await client.refetchQueries({ - include: [CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS], - variables: [ - { query: CONVERSATION_LIST_QUERY, variables: listQueryVariables }, - { query: GET_CONVERSATION_DETAILS, variables: detailsQueryVariables } - ] - }); + logLocal("handleConversationChanged - Refetched queries after state change", { conversationId, type }); + } catch (error) { + console.error("Error refetching queries after conversation state change:", error); + } + break; - console.log("Refetched conversation list and details after state change."); - } catch (error) { - console.error("Error refetching queries after conversation state change:", error); + case "tag-added": + client.cache.modify({ + id: client.cache.identify({ __typename: "conversations", id: conversationId }), + fields: { + job_conversations: (existing = []) => [...existing, ...job_conversations] + } + }); + break; + + case "tag-removed": + client.cache.modify({ + id: client.cache.identify({ __typename: "conversations", id: conversationId }), + fields: { + job_conversations: (existing = [], { readField }) => + existing.filter((jobRef) => readField("jobid", jobRef) !== fields.jobId) + } + }); + break; + + default: + logLocal("handleConversationChanged - Unhandled type", { type }); + client.cache.modify({ + id: client.cache.identify({ __typename: "conversations", id: conversationId }), + fields: { + ...Object.fromEntries( + Object.entries(fields).map(([key, value]) => [key, (cached) => (value !== undefined ? value : cached)]) + ) + } + }); } - - return; + } catch (error) { + console.error("Error handling conversation changes:", { type, error }); } - // Handle other types of updates (e.g., marked read, tags added/removed) - client.cache.modify({ - id: cacheId, - fields: { - ...Object.fromEntries( - Object.entries(fields).map(([key, value]) => [key, (cached) => (value !== undefined ? value : cached)]) - ), - ...(type === "conversation-marked-read" && { - messages_aggregate: () => ({ - __typename: "messages_aggregate", - aggregate: { __typename: "messages_aggregate_fields", count: 0 } - }) - }), - ...(type === "tag-added" && { - job_conversations: (existing = []) => [...existing, ...job_conversations] - }), - ...(type === "tag-removed" && { - job_conversations: (existing = [], { readField }) => - existing.filter((jobRef) => readField("jobid", jobRef) !== data.jobId) - }) - } - }); }; const handleNewMessage = ({ conversationId, message }) => { if (!conversationId || !message?.id || !message?.text) { + logLocal("handleNewMessage - Missing conversationId or message details", { conversationId, message }); return; } - logLocal("handleNewMessage", { conversationId, message }); + logLocal("handleNewMessage - Start", { conversationId, message }); - client.cache.modify({ - id: client.cache.identify({ __typename: "conversations", id: conversationId }), - fields: { - messages(existing = []) { - // Ensure that the `message` object matches the schema - const newMessageRef = client.cache.writeFragment({ - data: { - __typename: "messages", - id: message.id, - body: message.text, - selectedMedia: message.image_path || [], - imexshopid: message.userid, - status: message.status, - created_at: message.created_at, - read: message.read - }, - fragment: gql` - fragment NewMessage on messages { - id - body - selectedMedia - imexshopid - status - created_at - read - } - ` - }); + try { + // Add the new message to the cache + client.cache.modify({ + id: client.cache.identify({ __typename: "conversations", id: conversationId }), + fields: { + messages(existing = []) { + // Write the new message to the cache + const newMessageRef = client.cache.writeFragment({ + data: { + __typename: "messages", + id: message.id, + text: message.text, + selectedMedia: message.image_path || [], + imexshopid: message.userid, + status: message.status, + created_at: message.created_at, + read: message.read + }, + fragment: gql` + fragment NewMessage on messages { + id + text + selectedMedia + imexshopid + status + created_at + read + } + ` + }); - // Prevent duplicates by checking if the message already exists - const isDuplicate = existing.some( - (msgRef) => - client.cache.readFragment({ - id: msgRef.__ref, - fragment: gql` - fragment CheckMessage on messages { - id - } - ` - })?.id === message.id - ); - - // We already have it, so return the existing list - if (isDuplicate) { - return existing; + // The merge function defined in the cache will handle deduplication + return [...existing, newMessageRef]; } - - return [...existing, newMessageRef]; // Add the new message reference } - } - }); + }); + + logLocal("handleNewMessage - Message added to cache", { conversationId, messageId: message.id }); + } catch (error) { + console.error("handleNewMessage - Error modifying cache:", error); + } }; socket.on("new-message-summary", handleNewMessageSummary); diff --git a/client/src/components/chat-conversation/chat-conversation.container.jsx b/client/src/components/chat-conversation/chat-conversation.container.jsx index 613d96288..3b7f8385b 100644 --- a/client/src/components/chat-conversation/chat-conversation.container.jsx +++ b/client/src/components/chat-conversation/chat-conversation.container.jsx @@ -1,4 +1,4 @@ -import { useQuery } from "@apollo/client"; +import { useApolloClient, useQuery } from "@apollo/client"; import axios from "axios"; import React, { useContext, useEffect, useState } from "react"; import { connect } from "react-redux"; @@ -17,19 +17,73 @@ const mapStateToProps = createStructuredSelector({ export default connect(mapStateToProps, null)(ChatConversationContainer); export function ChatConversationContainer({ bodyshop, selectedConversation }) { + const client = useApolloClient(); + const { socket } = useContext(SocketContext); + const [markingAsReadInProgress, setMarkingAsReadInProgress] = useState(false); + const { loading: convoLoading, error: convoError, data: convoData } = useQuery(GET_CONVERSATION_DETAILS, { variables: { conversationId: selectedConversation }, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only" + fetchPolicy: "network-only" }); - const { socket } = useContext(SocketContext); + // Utility to update Apollo cache + const updateCacheWithReadMessages = (conversationId, messageIds) => { + if (!conversationId || !messageIds || messageIds.length === 0) return; + + messageIds.forEach((messageId) => { + client.cache.modify({ + id: client.cache.identify({ __typename: "messages", id: messageId }), + fields: { + read() { + return true; // Mark message as read + } + } + }); + }); + + // Optionally update aggregate unread count + client.cache.modify({ + id: client.cache.identify({ __typename: "conversations", id: conversationId }), + fields: { + messages_aggregate(existingAggregate) { + const updatedAggregate = { + ...existingAggregate, + aggregate: { + ...existingAggregate.aggregate, + count: 0 // No unread messages remaining + } + }; + return updatedAggregate; + } + } + }); + }; + + // Handle WebSocket events + useEffect(() => { + if (!socket || !socket.connected) return; + + const handleConversationChange = (data) => { + if (data.type === "conversation-marked-read") { + const { conversationId, messageIds } = data; + console.log("Conversation change received:", data); + updateCacheWithReadMessages(conversationId, messageIds); + } + }; + + socket.on("conversation-changed", handleConversationChange); + + return () => { + socket.off("conversation-changed", handleConversationChange); + }; + }, [socket, client]); + + // Handle joining/leaving conversation useEffect(() => { - // Early gate, we have no socket, bail. if (!socket || !socket.connected) return; socket.emit("join-bodyshop-conversation", { @@ -45,25 +99,41 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) { }; }, [selectedConversation, bodyshop, socket]); - const [markingAsReadInProgress, setMarkingAsReadInProgress] = useState(false); - - const unreadCount = - convoData && - convoData.conversations_by_pk && - convoData.conversations_by_pk.messages && - convoData.conversations_by_pk.messages.reduce((acc, val) => { - return !val.read && !val.isoutbound ? acc + 1 : acc; - }, 0); - + // Handle marking conversation as read const handleMarkConversationAsRead = async () => { - if (unreadCount > 0 && !!selectedConversation && !markingAsReadInProgress) { + if (!convoData || !selectedConversation || markingAsReadInProgress) return; + + const conversation = convoData.conversations_by_pk; + if (!conversation) { + console.warn(`No data found for conversation ID: ${selectedConversation}`); + return; + } + + const unreadMessageIds = conversation.messages + ?.filter((message) => !message.read && !message.isoutbound) + .map((message) => message.id); + + if (unreadMessageIds?.length > 0) { setMarkingAsReadInProgress(true); - await axios.post("/sms/markConversationRead", { - conversationid: selectedConversation, - imexshopid: bodyshop.imexshopid, - bodyshopid: bodyshop.id - }); - setMarkingAsReadInProgress(false); + + try { + const payload = { + conversation, + imexshopid: bodyshop?.imexshopid, + bodyshopid: bodyshop?.id + }; + + console.log("Marking conversation as read:", payload); + + await axios.post("/sms/markConversationRead", payload); + + // Update local cache + updateCacheWithReadMessages(selectedConversation, unreadMessageIds); + } catch (error) { + console.error("Error marking conversation as read:", error.response?.data || error.message); + } finally { + setMarkingAsReadInProgress(false); + } } }; diff --git a/client/src/utils/GraphQLClient.js b/client/src/utils/GraphQLClient.js index e0d409bd7..423c38e54 100644 --- a/client/src/utils/GraphQLClient.js +++ b/client/src/utils/GraphQLClient.js @@ -162,6 +162,22 @@ const cache = new InMemoryCache({ (incomingItem) => !existing.some((existingItem) => existingItem.__ref === incomingItem.__ref) ) ]; + return merged; + } + }, + messages: { + keyArgs: false, // Ignore arguments when determining uniqueness (like `order_by`). + merge(existing = [], incoming = [], { readField }) { + const existingIds = new Set(existing.map((message) => readField("id", message))); + + // Merge incoming messages, avoiding duplicates + const merged = [...existing]; + incoming.forEach((message) => { + if (!existingIds.has(readField("id", message))) { + merged.push(message); + } + }); + return merged; } } diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index 72224dfb6..e70144775 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -2569,6 +2569,9 @@ exports.GET_JOBS_BY_PKS = `query GET_JOBS_BY_PKS($ids: [uuid!]!) { exports.MARK_MESSAGES_AS_READ = `mutation MARK_MESSAGES_AS_READ($conversationId: uuid!) { update_messages(where: { conversationid: { _eq: $conversationId } }, _set: { read: true }) { + returning { + id + } affected_rows } } diff --git a/server/sms/status.js b/server/sms/status.js index 51d2f7ecb..0e29bbf8f 100644 --- a/server/sms/status.js +++ b/server/sms/status.js @@ -58,48 +58,49 @@ exports.status = async (req, res) => { logger.log("sms-status-update-error", "ERROR", "api", null, { msid: SmsSid, fields: { status: SmsStatus }, - error + stack: error.stack, + message: error.message }); res.status(500).json({ error: "Failed to update message status." }); } }; exports.markConversationRead = async (req, res) => { - const { conversationid, imexshopid, bodyshopid } = req.body; const { ioRedis, ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom } } = req; + const { conversation, imexshopid, bodyshopid } = req.body; + + // Alternatively, support both payload formats + const conversationId = conversation?.id || req.body.conversationId; + + if (!conversationId || !imexshopid || !bodyshopid) { + return res.status(400).json({ error: "Invalid conversation data provided." }); + } try { - // Mark messages in the conversation as read const response = await client.request(queries.MARK_MESSAGES_AS_READ, { - conversationId: conversationid + conversationId }); - const updatedMessages = response.update_messages.affected_rows; - - logger.log("conversation-mark-read", "DEBUG", "api", null, { - conversationid, - imexshopid, - bodyshopid, - updatedMessages - }); + const updatedMessageIds = response.update_messages.returning.map((message) => message.id); const broadcastRoom = getBodyshopRoom(bodyshopid); ioRedis.to(broadcastRoom).emit("conversation-changed", { type: "conversation-marked-read", - conversationId: conversationid + conversationId, + affectedMessages: response.update_messages.affected_rows, + messageIds: updatedMessageIds }); - res.status(200).json({ success: true, message: "Conversation marked as read." }); - } catch (error) { - logger.log("conversation-mark-read-error", "ERROR", "api", null, { - conversationid, - imexshopid, - error + res.status(200).json({ + success: true, + message: "Conversation marked as read." }); + } catch (error) { + console.error("Error marking conversation as read:", error); res.status(500).json({ error: "Failed to mark conversation as read." }); } }; From 3ab471e6297626aaf658cd63bd32b8bba5f9a3d9 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 22 Nov 2024 08:23:24 -0800 Subject: [PATCH 32/74] feature/IO-3000-messaging-sockets-migrations2 - - Final fix of unread messagages Signed-off-by: Dave Richer --- .../chat-popup/chat-popup.component.jsx | 37 ++++++++++++++----- client/src/redux/messaging/messaging.sagas.js | 2 - 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/client/src/components/chat-popup/chat-popup.component.jsx b/client/src/components/chat-popup/chat-popup.component.jsx index e67929d11..f99738cb1 100644 --- a/client/src/components/chat-popup/chat-popup.component.jsx +++ b/client/src/components/chat-popup/chat-popup.component.jsx @@ -1,11 +1,11 @@ import { InfoCircleOutlined, MessageOutlined, ShrinkOutlined, SyncOutlined } from "@ant-design/icons"; -import { useLazyQuery, useQuery } from "@apollo/client"; +import { useApolloClient, useLazyQuery } from "@apollo/client"; import { Badge, Card, Col, Row, Space, Tag, Tooltip, Typography } from "antd"; -import React, { useCallback, useContext, useEffect, useState } from "react"; +import React, { useContext, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; -import { CONVERSATION_LIST_QUERY, UNREAD_CONVERSATION_COUNT } from "../../graphql/conversations.queries"; +import { CONVERSATION_LIST_QUERY } from "../../graphql/conversations.queries"; import { toggleChatVisible } from "../../redux/messaging/messaging.actions"; import { selectChatVisible, selectSelectedConversation } from "../../redux/messaging/messaging.selectors"; import ChatConversationListComponent from "../chat-conversation-list/chat-conversation-list.component"; @@ -28,13 +28,9 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh const { t } = useTranslation(); const [pollInterval, setPollInterval] = useState(0); const { socket } = useContext(SocketContext); + const client = useApolloClient(); // Apollo Client instance for cache operations - const { data: unreadData } = useQuery(UNREAD_CONVERSATION_COUNT, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - ...(pollInterval > 0 ? { pollInterval } : {}) - }); - + // Lazy query for conversations const [getConversations, { loading, data, refetch, fetchMore }] = useLazyQuery(CONVERSATION_LIST_QUERY, { fetchPolicy: "network-only", nextFetchPolicy: "network-only", @@ -42,6 +38,7 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh ...(pollInterval > 0 ? { pollInterval } : {}) }); + // Socket connection status useEffect(() => { const handleSocketStatus = () => { if (socket?.connected) { @@ -66,6 +63,7 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh }; }, [socket]); + // Fetch conversations when chat becomes visible useEffect(() => { if (chatVisible) getConversations({ @@ -77,7 +75,26 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh }); }, [chatVisible, getConversations]); - const unreadCount = unreadData?.messages_aggregate?.aggregate?.count || 0; + // Get unread count from the cache + const unreadCount = (() => { + try { + const cachedData = client.readQuery({ + query: CONVERSATION_LIST_QUERY, + variables: { offset: 0 } + }); + + if (!cachedData?.conversations) return 0; + + // Aggregate unread message count + return cachedData.conversations.reduce((total, conversation) => { + const unread = conversation.messages_aggregate?.aggregate?.count || 0; + return total + unread; + }, 0); + } catch (error) { + console.warn("Unread count not found in cache:", error); + return 0; // Fallback if not in cache + } + })(); return ( diff --git a/client/src/redux/messaging/messaging.sagas.js b/client/src/redux/messaging/messaging.sagas.js index d2b428e1f..012c05618 100644 --- a/client/src/redux/messaging/messaging.sagas.js +++ b/client/src/redux/messaging/messaging.sagas.js @@ -4,9 +4,7 @@ import { all, call, put, select, takeLatest } from "redux-saga/effects"; import { logImEXEvent } from "../../firebase/firebase.utils"; import { CONVERSATION_ID_BY_PHONE, - CONVERSATION_LIST_QUERY, CREATE_CONVERSATION, - GET_CONVERSATION_DETAILS, TOGGLE_CONVERSATION_ARCHIVE } from "../../graphql/conversations.queries"; import { INSERT_CONVERSATION_TAG } from "../../graphql/job-conversations.queries"; From 11ab7cd67effde073eda08811a0a77fdc341516b Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Fri, 22 Nov 2024 09:18:09 -0800 Subject: [PATCH 33/74] IO-3001 Null Coalesce for some items for better handling. --- server/accounting/qb-receivables-lines.js | 18 +++++++++--------- server/job/job-costing.js | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/server/accounting/qb-receivables-lines.js b/server/accounting/qb-receivables-lines.js index c257cd944..bed94f1d7 100644 --- a/server/accounting/qb-receivables-lines.js +++ b/server/accounting/qb-receivables-lines.js @@ -548,7 +548,7 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes } } - if (jobs_by_pk.job_totals.totals.ttl_adjustment) { + if (jobs_by_pk.job_totals.totals?.ttl_adjustment) { // Do not need to check for ImEX or Rome because ImEX uses a different totals calculation that will never set this field. if (qbo) { const taxAccountCode = findTaxCode( @@ -571,11 +571,11 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes InvoiceLineAdd.push({ DetailType: "SalesItemLineDetail", - Amount: Dinero(jobs_by_pk.job_totals.totals.ttl_adjustment).toFormat(DineroQbFormat), + Amount: Dinero(jobs_by_pk.job_totals.totals?.ttl_adjustment).toFormat(DineroQbFormat), SalesItemLineDetail: { ...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}), ItemRef: { - value: items[responsibilityCenters.ttl_adjustment.accountitem] + value: items[responsibilityCenters.ttl_adjustment?.accountitem] }, TaxCodeRef: { value: QboTaxId @@ -586,11 +586,11 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes } else { InvoiceLineAdd.push({ ItemRef: { - FullName: responsibilityCenters.ttl_adjustment.accountitem + FullName: responsibilityCenters.ttl_adjustment?.accountitem }, Desc: "Adjustment", Quantity: 1, - Amount: Dinero(jobs_by_pk.job_totals.totals.ttl_adjustment).toFormat(DineroQbFormat), + Amount: Dinero(jobs_by_pk.job_totals.totals?.ttl_adjustment).toFormat(DineroQbFormat), SalesTaxCodeRef: InstanceManager({ imex: { FullName: "E" @@ -902,11 +902,11 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes InvoiceLineAdd.push({ DetailType: "SalesItemLineDetail", - Amount: Dinero(jobs_by_pk.job_totals.totals.ttl_tax_adjustment).toFormat(DineroQbFormat), + Amount: Dinero(jobs_by_pk.job_totals.totals?.ttl_tax_adjustment).toFormat(DineroQbFormat), SalesItemLineDetail: { ...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}), ItemRef: { - value: items[responsibilityCenters.ttl_tax_adjustment.accountitem] + value: items[responsibilityCenters.ttl_tax_adjustment?.accountitem] }, TaxCodeRef: { value: QboTaxId @@ -917,11 +917,11 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes } else { InvoiceLineAdd.push({ ItemRef: { - FullName: responsibilityCenters.ttl_tax_adjustment.accountitem + FullName: responsibilityCenters.ttl_tax_adjustment?.accountitem }, Desc: "Tax Adjustment", Quantity: 1, - Amount: Dinero(jobs_by_pk.job_totals.totals.ttl_tax_adjustment).toFormat(DineroQbFormat), + Amount: Dinero(jobs_by_pk.job_totals.totals?.ttl_tax_adjustment).toFormat(DineroQbFormat), SalesTaxCodeRef: InstanceManager({ imex: { FullName: "E" diff --git a/server/job/job-costing.js b/server/job/job-costing.js index 6c107ce8d..4e7af7f86 100644 --- a/server/job/job-costing.js +++ b/server/job/job-costing.js @@ -850,7 +850,7 @@ function GenerateCostingData(job) { }); } //Push adjustments to bottom line. - if (job.job_totals.totals.ttl_adjustment) { + if (job.job_totals?.totals?.ttl_adjustment) { //Add to totals. const Adjustment = Dinero(job.job_totals.totals.ttl_adjustment); //Need to invert, since this is being assigned as a cost. summaryData.totalAdditionalSales = summaryData.totalAdditionalSales.add(Adjustment); From 49044e566998cf28b1b277925e6176a2117e6db6 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 22 Nov 2024 10:03:41 -0800 Subject: [PATCH 34/74] feature/IO-3000-messaging-sockets-migrations2 - - Missed a check Signed-off-by: Dave Richer --- client/src/pages/manage/manage.page.component.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/pages/manage/manage.page.component.jsx b/client/src/pages/manage/manage.page.component.jsx index c1a8e561c..b39ee69c4 100644 --- a/client/src/pages/manage/manage.page.component.jsx +++ b/client/src/pages/manage/manage.page.component.jsx @@ -647,7 +647,7 @@ export function Manage({ conflict, bodyshop, alerts, setAlerts }) { return ( <> - {true && } + From 91de31135195aad9d0e9b5e3d8d10324aa3ca348 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Fri, 22 Nov 2024 10:55:19 -0800 Subject: [PATCH 35/74] IO-2921 Chatter Final mods and Cron Trigger Signed-off-by: Allan Carr --- hasura/metadata/cron_triggers.yaml | 9 +++++++++ server/data/chatter.js | 3 ++- server/graphql-client/queries.js | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/hasura/metadata/cron_triggers.yaml b/hasura/metadata/cron_triggers.yaml index ff7aef8d1..95157adaa 100644 --- a/hasura/metadata/cron_triggers.yaml +++ b/hasura/metadata/cron_triggers.yaml @@ -6,6 +6,15 @@ headers: - name: x-imex-auth value_from_env: DATAPUMP_AUTH +- name: Chatter Data Pump + webhook: '{{HASURA_API_URL}}/data/chatter' + schedule: 45 5 * * * + include_in_metadata: true + payload: {} + headers: + - name: x-imex-auth + value_from_env: DATAPUMP_AUTH + comment: "" - name: Claimscorp Data Pump webhook: '{{HASURA_API_URL}}/data/cc' schedule: 30 6 * * * diff --git a/server/data/chatter.js b/server/data/chatter.js index 8c3f8e634..993707c41 100644 --- a/server/data/chatter.js +++ b/server/data/chatter.js @@ -109,7 +109,8 @@ async function processBatch(shopsToProcess, start, end, allChatterObjects, allEr lastname: j.ownr_co_nm ? j.ownr_co_nm : j.ownr_ln, transaction_id: j.ro_number, email: j.ownr_ea, - phone_number: j.ownr_ph1 + phone_number: j.ownr_ph1, + transaction_time: (j.actual_delivery && moment(j.actual_delivery).tz(bodyshop.timezone).format("YYYYMMDD-HHmm")) || "" }; }); allChatterObjects.push(...chatterObject); diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index 99e37bebe..66acc6baa 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -840,6 +840,7 @@ exports.CHATTER_QUERY = `query CHATTER_EXPORT($start: timestamptz, $bodyshopid: timezone } jobs(where: {_and: [{converted: {_eq: true}}, {actual_delivery: {_gt: $start}}, {actual_delivery: {_lte: $end}}, {shopid: {_eq: $bodyshopid}}, {_or: [{ownr_ph1: {_is_null: false}}, {ownr_ea: {_is_null: false}}]}]}) { + actual_delivery id created_at ro_number From cbc8665636e04243d821b3135fb6e58ef0cb5d63 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 25 Nov 2024 08:36:33 -0800 Subject: [PATCH 36/74] feature/IO-3000-messaging-sockets-migration2 - Handle some of the PR notes Signed-off-by: Dave Richer --- .../chat-archive-button.component.jsx | 13 +++- .../chat-conversation-list.component.jsx | 2 +- ...chat-conversation-title-tags.component.jsx | 13 +++- .../chat-conversation-title.component.jsx | 24 ++++--- .../chat-conversation.component.jsx | 13 +++- .../chat-conversation.container.jsx | 63 ++++++++++--------- .../chat-label/chat-label.component.jsx | 13 +++- .../chat-tag-ro/chat-tag-ro.container.jsx | 13 +++- 8 files changed, 109 insertions(+), 45 deletions(-) diff --git a/client/src/components/chat-archive-button/chat-archive-button.component.jsx b/client/src/components/chat-archive-button/chat-archive-button.component.jsx index 2b8bcd054..6572cbefc 100644 --- a/client/src/components/chat-archive-button/chat-archive-button.component.jsx +++ b/client/src/components/chat-archive-button/chat-archive-button.component.jsx @@ -4,8 +4,17 @@ import React, { useContext, useState } from "react"; import { useTranslation } from "react-i18next"; import { TOGGLE_CONVERSATION_ARCHIVE } from "../../graphql/conversations.queries"; import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors.js"; +import { connect } from "react-redux"; -export default function ChatArchiveButton({ conversation, bodyshop }) { +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop +}); + +const mapDispatchToProps = () => ({}); + +export function ChatArchiveButton({ conversation, bodyshop }) { const [loading, setLoading] = useState(false); const { t } = useTranslation(); const [updateConversation] = useMutation(TOGGLE_CONVERSATION_ARCHIVE); @@ -36,3 +45,5 @@ export default function ChatArchiveButton({ conversation, bodyshop }) { ); } + +export default connect(mapStateToProps, mapDispatchToProps)(ChatArchiveButton); diff --git a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx index a8f95f59d..fe71ee46e 100644 --- a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx +++ b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx @@ -64,7 +64,7 @@ function ChatConversationListComponent({ conversationList, selectedConversation, ); }; - // TODO: Can go back into virtuoso for additional fetch + // CAN DO: Can go back into virtuoso for additional fetch // endReached={loadMoreConversations} // Calls loadMoreConversations when scrolled to the bottom return ( diff --git a/client/src/components/chat-conversation-title-tags/chat-conversation-title-tags.component.jsx b/client/src/components/chat-conversation-title-tags/chat-conversation-title-tags.component.jsx index 944ade8b0..821ad1603 100644 --- a/client/src/components/chat-conversation-title-tags/chat-conversation-title-tags.component.jsx +++ b/client/src/components/chat-conversation-title-tags/chat-conversation-title-tags.component.jsx @@ -6,8 +6,17 @@ import { logImEXEvent } from "../../firebase/firebase.utils"; import { REMOVE_CONVERSATION_TAG } from "../../graphql/job-conversations.queries"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors.js"; +import { connect } from "react-redux"; -export default function ChatConversationTitleTags({ jobConversations, bodyshop }) { +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop +}); + +const mapDispatchToProps = () => ({}); + +export function ChatConversationTitleTags({ jobConversations, bodyshop }) { const [removeJobConversation] = useMutation(REMOVE_CONVERSATION_TAG); const { socket } = useContext(SocketContext); @@ -66,3 +75,5 @@ export default function ChatConversationTitleTags({ jobConversations, bodyshop }
); } + +export default connect(mapStateToProps, mapDispatchToProps)(ChatConversationTitleTags); diff --git a/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx b/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx index 7754ea347..7e5b045f5 100644 --- a/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx +++ b/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx @@ -6,19 +6,27 @@ import ChatConversationTitleTags from "../chat-conversation-title-tags/chat-conv import ChatLabelComponent from "../chat-label/chat-label.component"; import ChatPrintButton from "../chat-print-button/chat-print-button.component"; import ChatTagRoContainer from "../chat-tag-ro/chat-tag-ro.container"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors.js"; +import { connect } from "react-redux"; -export default function ChatConversationTitle({ conversation, bodyshop }) { +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop +}); + +const mapDispatchToProps = () => ({}); + +export function ChatConversationTitle({ conversation }) { return ( {conversation && conversation.phone_num} - + - - - + + + ); } + +export default connect(mapStateToProps, mapDispatchToProps)(ChatConversationTitle); diff --git a/client/src/components/chat-conversation/chat-conversation.component.jsx b/client/src/components/chat-conversation/chat-conversation.component.jsx index 4188fbabe..3334b5cbd 100644 --- a/client/src/components/chat-conversation/chat-conversation.component.jsx +++ b/client/src/components/chat-conversation/chat-conversation.component.jsx @@ -5,8 +5,17 @@ import ChatMessageListComponent from "../chat-messages-list/chat-message-list.co import ChatSendMessage from "../chat-send-message/chat-send-message.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx"; import "./chat-conversation.styles.scss"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors.js"; +import { connect } from "react-redux"; -export default function ChatConversationComponent({ +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop +}); + +const mapDispatchToProps = () => ({}); + +export function ChatConversationComponent({ subState, conversation, messages, @@ -31,3 +40,5 @@ export default function ChatConversationComponent({
); } + +export default connect(mapStateToProps, mapDispatchToProps)(ChatConversationComponent); diff --git a/client/src/components/chat-conversation/chat-conversation.container.jsx b/client/src/components/chat-conversation/chat-conversation.container.jsx index 3b7f8385b..e2f7c6ae9 100644 --- a/client/src/components/chat-conversation/chat-conversation.container.jsx +++ b/client/src/components/chat-conversation/chat-conversation.container.jsx @@ -1,6 +1,6 @@ import { useApolloClient, useQuery } from "@apollo/client"; import axios from "axios"; -import React, { useContext, useEffect, useState } from "react"; +import React, { useCallback, useContext, useEffect, useState } from "react"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import SocketContext from "../../contexts/SocketIO/socketContext"; @@ -14,8 +14,6 @@ const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); -export default connect(mapStateToProps, null)(ChatConversationContainer); - export function ChatConversationContainer({ bodyshop, selectedConversation }) { const client = useApolloClient(); const { socket } = useContext(SocketContext); @@ -30,38 +28,40 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) { fetchPolicy: "network-only" }); - // Utility to update Apollo cache - const updateCacheWithReadMessages = (conversationId, messageIds) => { - if (!conversationId || !messageIds || messageIds.length === 0) return; + const updateCacheWithReadMessages = useCallback( + (conversationId, messageIds) => { + if (!conversationId || !messageIds || messageIds.length === 0) return; - messageIds.forEach((messageId) => { + // Mark individual messages as read + messageIds.forEach((messageId) => { + client.cache.modify({ + id: client.cache.identify({ __typename: "messages", id: messageId }), + fields: { + read() { + return true; // Mark message as read + } + } + }); + }); + + // Update aggregate unread count for the conversation client.cache.modify({ - id: client.cache.identify({ __typename: "messages", id: messageId }), + id: client.cache.identify({ __typename: "conversations", id: conversationId }), fields: { - read() { - return true; // Mark message as read + messages_aggregate(existingAggregate) { + return { + ...existingAggregate, + aggregate: { + ...existingAggregate.aggregate, + count: 0 // No unread messages remaining + } + }; } } }); - }); - - // Optionally update aggregate unread count - client.cache.modify({ - id: client.cache.identify({ __typename: "conversations", id: conversationId }), - fields: { - messages_aggregate(existingAggregate) { - const updatedAggregate = { - ...existingAggregate, - aggregate: { - ...existingAggregate.aggregate, - count: 0 // No unread messages remaining - } - }; - return updatedAggregate; - } - } - }); - }; + }, + [client.cache] + ); // Handle WebSocket events useEffect(() => { @@ -80,7 +80,7 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) { return () => { socket.off("conversation-changed", handleConversationChange); }; - }, [socket, client]); + }, [socket, client, updateCacheWithReadMessages]); // Handle joining/leaving conversation useEffect(() => { @@ -143,7 +143,8 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) { conversation={convoData ? convoData.conversations_by_pk : {}} messages={convoData ? convoData.conversations_by_pk.messages : []} handleMarkConversationAsRead={handleMarkConversationAsRead} - bodyshop={bodyshop} /> ); } + +export default connect(mapStateToProps, null)(ChatConversationContainer); diff --git a/client/src/components/chat-label/chat-label.component.jsx b/client/src/components/chat-label/chat-label.component.jsx index e577bbf23..0764869ee 100644 --- a/client/src/components/chat-label/chat-label.component.jsx +++ b/client/src/components/chat-label/chat-label.component.jsx @@ -5,8 +5,17 @@ import React, { useContext, useState } from "react"; import { useTranslation } from "react-i18next"; import { UPDATE_CONVERSATION_LABEL } from "../../graphql/conversations.queries"; import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors.js"; +import { connect } from "react-redux"; -export default function ChatLabel({ conversation, bodyshop }) { +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop +}); + +const mapDispatchToProps = (dispatch) => ({}); + +export function ChatLabel({ conversation, bodyshop }) { const [loading, setLoading] = useState(false); const [editing, setEditing] = useState(false); const [value, setValue] = useState(conversation.label); @@ -67,3 +76,5 @@ export default function ChatLabel({ conversation, bodyshop }) { ); } } + +export default connect(mapStateToProps, mapDispatchToProps)(ChatLabel); diff --git a/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx b/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx index 6e01ea5ed..8d3476c7d 100644 --- a/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx +++ b/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx @@ -9,8 +9,17 @@ import { INSERT_CONVERSATION_TAG } from "../../graphql/job-conversations.queries import { SEARCH_FOR_JOBS } from "../../graphql/jobs.queries"; import ChatTagRo from "./chat-tag-ro.component"; import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors.js"; +import { connect } from "react-redux"; -export default function ChatTagRoContainer({ conversation, bodyshop }) { +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop +}); + +const mapDispatchToProps = () => ({}); + +export function ChatTagRoContainer({ conversation, bodyshop }) { const { t } = useTranslation(); const [open, setOpen] = useState(false); const { socket } = useContext(SocketContext); @@ -86,3 +95,5 @@ export default function ChatTagRoContainer({ conversation, bodyshop }) { ); } + +export default connect(mapStateToProps, mapDispatchToProps)(ChatTagRoContainer); From 5e2c0f9c4a00639566f99b4299932d2f67d058bf Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 25 Nov 2024 08:38:07 -0800 Subject: [PATCH 37/74] feature/IO-3000-messaging-sockets-migration2 - Handle some of the PR notes Signed-off-by: Dave Richer --- client/src/components/chat-popup/chat-popup.component.jsx | 2 +- client/src/redux/messaging/messaging.sagas.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/chat-popup/chat-popup.component.jsx b/client/src/components/chat-popup/chat-popup.component.jsx index f99738cb1..7c0711fbc 100644 --- a/client/src/components/chat-popup/chat-popup.component.jsx +++ b/client/src/components/chat-popup/chat-popup.component.jsx @@ -31,7 +31,7 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh const client = useApolloClient(); // Apollo Client instance for cache operations // Lazy query for conversations - const [getConversations, { loading, data, refetch, fetchMore }] = useLazyQuery(CONVERSATION_LIST_QUERY, { + const [getConversations, { loading, data, refetch }] = useLazyQuery(CONVERSATION_LIST_QUERY, { fetchPolicy: "network-only", nextFetchPolicy: "network-only", skip: !chatVisible, diff --git a/client/src/redux/messaging/messaging.sagas.js b/client/src/redux/messaging/messaging.sagas.js index 012c05618..7194ea4dc 100644 --- a/client/src/redux/messaging/messaging.sagas.js +++ b/client/src/redux/messaging/messaging.sagas.js @@ -42,7 +42,7 @@ export function* openChatByPhone({ payload }) { } = yield client.query({ query: CONVERSATION_ID_BY_PHONE, variables: { phone: p.number }, - fetchPolicy: "no-cache" + fetchPolicy: "network-only" }); if (conversations.length === 0) { From 457a3b2d7adec8a00a3a95ae491913bd71c52314 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 25 Nov 2024 08:42:02 -0800 Subject: [PATCH 38/74] feature/IO-3000-messaging-sockets-migration2 - Handle some of the PR notes Signed-off-by: Dave Richer --- .../chat-conversation-title.component.jsx | 5 +---- .../chat-conversation/chat-conversation.container.jsx | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx b/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx index 7e5b045f5..07b48716f 100644 --- a/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx +++ b/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx @@ -7,12 +7,9 @@ import ChatLabelComponent from "../chat-label/chat-label.component"; import ChatPrintButton from "../chat-print-button/chat-print-button.component"; import ChatTagRoContainer from "../chat-tag-ro/chat-tag-ro.container"; import { createStructuredSelector } from "reselect"; -import { selectBodyshop } from "../../redux/user/user.selectors.js"; import { connect } from "react-redux"; -const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop -}); +const mapStateToProps = createStructuredSelector({}); const mapDispatchToProps = () => ({}); diff --git a/client/src/components/chat-conversation/chat-conversation.container.jsx b/client/src/components/chat-conversation/chat-conversation.container.jsx index e2f7c6ae9..b477b436e 100644 --- a/client/src/components/chat-conversation/chat-conversation.container.jsx +++ b/client/src/components/chat-conversation/chat-conversation.container.jsx @@ -25,7 +25,8 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) { data: convoData } = useQuery(GET_CONVERSATION_DETAILS, { variables: { conversationId: selectedConversation }, - fetchPolicy: "network-only" + fetchPolicy: "network-only", + nextFetchPolicy: "network-only" }); const updateCacheWithReadMessages = useCallback( From 239c1502f90a5d077e736df348f0078dbb983931 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 25 Nov 2024 09:57:18 -0800 Subject: [PATCH 39/74] feature/IO-3000-messaging-sockets-migration2 - Fix console warn in archive/unarchive if one query is not existent Signed-off-by: Dave Richer --- .../chat-affix/registerMessagingSocketHandlers.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/client/src/components/chat-affix/registerMessagingSocketHandlers.js b/client/src/components/chat-affix/registerMessagingSocketHandlers.js index 5b4f34ea1..d0ab4baa5 100644 --- a/client/src/components/chat-affix/registerMessagingSocketHandlers.js +++ b/client/src/components/chat-affix/registerMessagingSocketHandlers.js @@ -337,12 +337,18 @@ export const registerMessagingHandlers = ({ socket, client }) => { const listQueryVariables = { offset: 0 }; const detailsQueryVariables = { conversationId }; - // Refetch conversation list and details + // Check if conversation details exist in the cache + const detailsExist = !!client.cache.readQuery({ + query: GET_CONVERSATION_DETAILS, + variables: detailsQueryVariables + }); + + // Refetch conversation list await client.refetchQueries({ - include: [CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS], + include: [CONVERSATION_LIST_QUERY, ...(detailsExist ? [GET_CONVERSATION_DETAILS] : [])], variables: [ { query: CONVERSATION_LIST_QUERY, variables: listQueryVariables }, - { query: GET_CONVERSATION_DETAILS, variables: detailsQueryVariables } + ...(detailsExist ? [{ query: GET_CONVERSATION_DETAILS, variables: detailsQueryVariables }] : []) ] }); From 268b1ba9c186485fc4bb92b6e1ee2a169721cabc Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Mon, 25 Nov 2024 10:51:48 -0800 Subject: [PATCH 40/74] IO-2920 Refactor fee discounting to use API to check. --- .../job-totals.table.totals.component.jsx | 11 +--- ...totals.cash-discount-display.component.jsx | 57 +++++++++++++++++++ server/intellipay/intellipay.js | 46 +++++++++++++++ server/routes/intellipayRoutes.js | 3 +- 4 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 client/src/components/job-totals-table/jobs-totals.cash-discount-display.component.jsx diff --git a/client/src/components/job-totals-table/job-totals.table.totals.component.jsx b/client/src/components/job-totals-table/job-totals.table.totals.component.jsx index 301fec91f..eef6dfaf5 100644 --- a/client/src/components/job-totals-table/job-totals.table.totals.component.jsx +++ b/client/src/components/job-totals-table/job-totals.table.totals.component.jsx @@ -7,6 +7,7 @@ import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; +import JobTotalsCashDiscount from "./jobs-totals.cash-discount-display.component"; const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser @@ -169,13 +170,7 @@ export function JobTotalsTableTotals({ bodyshop, job }) { }, { key: t("jobs.labels.total_cust_payable"), - total: Dinero(job.job_totals.totals.custPayable.total) - .add( - Dinero(job.job_totals.totals.custPayable.total).percentage( - bodyshop.intellipay_config?.cash_discount_percentage || 0 - ) - ) - .toJSON(), + render: , bold: true } ] @@ -211,7 +206,7 @@ export function JobTotalsTableTotals({ bodyshop, job }) { dataIndex: "total", key: "total", align: "right", - render: (text, record) => Dinero(record.total).toFormat(), + render: (text, record) => (record.render ? record.render : Dinero(record.total).toFormat()), width: "20%", onCell: (record, rowIndex) => { return { style: { fontWeight: record.bold && "bold" } }; diff --git a/client/src/components/job-totals-table/jobs-totals.cash-discount-display.component.jsx b/client/src/components/job-totals-table/jobs-totals.cash-discount-display.component.jsx new file mode 100644 index 000000000..9a529866f --- /dev/null +++ b/client/src/components/job-totals-table/jobs-totals.cash-discount-display.component.jsx @@ -0,0 +1,57 @@ +import { notification, Spin } from "antd"; +import axios from "axios"; +import Dinero from "dinero.js"; +import React, { useCallback, useEffect, useState } from "react"; + +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop +}); +const mapDispatchToProps = (dispatch) => ({ +}); +export default connect(mapStateToProps, mapDispatchToProps)(JobTotalsCashDiscount); + +export function JobTotalsCashDiscount({ bodyshop, amountDinero }) { + const [loading, setLoading] = useState(true); + const [fee, setFee] = useState(0); + + const fetchData = useCallback(async () => { + if (amountDinero && bodyshop) { + setLoading(true); + let response; + try { + response = await axios.post("/intellipay/checkfee", { + bodyshop: { id: bodyshop.id, imexshopid: bodyshop.imexshopid }, + amount: Dinero(amountDinero).toFormat("0.00") + }); + + if (response?.data?.error) { + notification.open({ + type: "error", + message: response.data?.error || "Error encountered contacting IntelliPay service." + }); + } else { + setFee(response.data?.fee || 0); + } + } catch (error) { + notification.open({ + type: "error", + message: error.response?.data?.error || "Error encountered contacting IntelliPay service." + }); + } finally { + setLoading(false); + } + } + }, [amountDinero, bodyshop]); + + useEffect(() => { + fetchData(); + }, [fetchData, bodyshop, amountDinero]); + + if (loading) return ; + return Dinero(amountDinero) + .add(Dinero({ amount: Math.round(fee * 100) })) + .toFormat(); +} diff --git a/server/intellipay/intellipay.js b/server/intellipay/intellipay.js index 080deec90..43758f97e 100644 --- a/server/intellipay/intellipay.js +++ b/server/intellipay/intellipay.js @@ -149,6 +149,52 @@ exports.generate_payment_url = async (req, res) => { } }; +//Reference: https://intellipay.com/dist/webapi26.html#operation/fee +exports.checkfee = async (req, res) => { + logger.log("intellipay-fee-check", "DEBUG", req.user?.email, null, null); + + //If there's no amount, there can't be a fee. Skip the call. + if (!req.body.amount || req.body.amount <= 0) { + res.json({ fee: 0 }); + return; + } + + const shopCredentials = await getShopCredentials(req.body.bodyshop); + + try { + const options = { + method: "POST", + headers: { "content-type": "application/x-www-form-urlencoded" }, + //TODO: Move these to environment variables/database. + data: qs.stringify( + { + method: "fee", + ...shopCredentials, + amount: req.body.amount, + paymenttype: `"CC"` + }, + { sort: false } //ColdFusion Query Strings depend on order. This preserves it. + ), + url: `https://${domain}.cpteller.com/api/26/webapi.cfc` + }; + + const response = await axios(options); + if (response.data?.error) { + res.status(400).json({ error: response.data.error }); + } else if (response.data < 0) { + res.json({ error: "Fee amount negative. Check API credentials & account configuration." }); + } else { + res.json({ fee: response.data }); + } + } catch (error) { + //console.log(error); + logger.log("intellipay-fee-check-error", "ERROR", req.user?.email, null, { + error: error.message + }); + res.status(400).json({ error }); + } +}; + exports.postback = async (req, res) => { try { logger.log("intellipay-postback", "DEBUG", req.user?.email, null, req.body); diff --git a/server/routes/intellipayRoutes.js b/server/routes/intellipayRoutes.js index 47f09ffb0..ad0b87323 100644 --- a/server/routes/intellipayRoutes.js +++ b/server/routes/intellipayRoutes.js @@ -1,11 +1,12 @@ const express = require("express"); const router = express.Router(); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); -const { lightbox_credentials, payment_refund, generate_payment_url, postback } = require("../intellipay/intellipay"); +const { lightbox_credentials, payment_refund, generate_payment_url, postback, checkfee } = require("../intellipay/intellipay"); router.post("/lightbox_credentials", validateFirebaseIdTokenMiddleware, lightbox_credentials); router.post("/payment_refund", validateFirebaseIdTokenMiddleware, payment_refund); router.post("/generate_payment_url", validateFirebaseIdTokenMiddleware, generate_payment_url); +router.post("/checkfee", validateFirebaseIdTokenMiddleware, checkfee); router.post("/postback", postback); module.exports = router; From 62dd3d7e8ec823f5285d7bd1fff329784cf70005 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 25 Nov 2024 11:38:59 -0800 Subject: [PATCH 41/74] feature/IO-3000-messaging-sockets-migration2 - Final fixes around sync / archive / receive Signed-off-by: Dave Richer --- client/src/redux/messaging/messaging.sagas.js | 20 +++-- server/graphql-client/queries.js | 31 ++++++++ server/sms/receive.js | 73 ++++++++++--------- 3 files changed, 77 insertions(+), 47 deletions(-) diff --git a/client/src/redux/messaging/messaging.sagas.js b/client/src/redux/messaging/messaging.sagas.js index 7194ea4dc..fb519bcca 100644 --- a/client/src/redux/messaging/messaging.sagas.js +++ b/client/src/redux/messaging/messaging.sagas.js @@ -42,10 +42,13 @@ export function* openChatByPhone({ payload }) { } = yield client.query({ query: CONVERSATION_ID_BY_PHONE, variables: { phone: p.number }, - fetchPolicy: "network-only" + // THIS NEEDS TO REMAIN NO CACHE, IT CHECKS FOR NEW MESSAGES FOR SYNC + fetchPolicy: "no-cache" }); - if (conversations.length === 0) { + const existingConversation = conversations?.find((c) => c.phone_num === phone_num); + + if (!existingConversation) { // No conversation exists, create a new one const { data: { @@ -75,18 +78,17 @@ export function* openChatByPhone({ payload }) { // Set the newly created conversation as selected yield put(setSelectedConversation(createdConversation.id)); - } else if (conversations.length === 1) { - const conversation = conversations[0]; - let updatedConversation = conversation; + } else { + let updatedConversation = existingConversation; - if (conversation.archived) { + if (existingConversation.archived) { // Conversation is archived, unarchive it in the DB const { data: { update_conversations_by_pk: unarchivedConversation } } = yield client.mutate({ mutation: TOGGLE_CONVERSATION_ARCHIVE, variables: { - id: conversation.id, + id: existingConversation.id, archived: false } }); @@ -115,10 +117,6 @@ export function* openChatByPhone({ payload }) { } }); } - } else { - // Multiple conversations found - console.error("ERROR: Multiple conversations found."); - yield put(setSelectedConversation(null)); } } catch (error) { console.error("Error in openChatByPhone saga.", error); diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index 4d82f0025..cf5e2b016 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -2577,3 +2577,34 @@ exports.MARK_MESSAGES_AS_READ = `mutation MARK_MESSAGES_AS_READ($conversationId: } } `; + +exports.CREATE_CONVERSATION = `mutation CREATE_CONVERSATION($conversation: [conversations_insert_input!]!) { + insert_conversations(objects: $conversation) { + returning { + id + phone_num + archived + label + unreadcnt + job_conversations { + jobid + conversationid + job { + id + ro_number + ownr_fn + ownr_ln + ownr_co_nm + } + } + messages_aggregate(where: { read: { _eq: false }, isoutbound: { _eq: false } }) { + aggregate { + count + } + } + created_at + updated_at + } + } +} +`; diff --git a/server/sms/receive.js b/server/sms/receive.js index 63c11a260..fef921912 100644 --- a/server/sms/receive.js +++ b/server/sms/receive.js @@ -34,6 +34,7 @@ exports.receive = async (req, res) => { } try { + // Step 1: Find the bodyshop and existing conversation const response = await client.request(queries.FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID, { mssid: req.body.MessagingServiceSid, phone: phone(req.body.From).phoneNumber @@ -44,53 +45,51 @@ exports.receive = async (req, res) => { } const bodyshop = response.bodyshops[0]; - const isNewConversation = bodyshop.conversations.length === 0; - const isDuplicate = bodyshop.conversations.length > 1; + const existingConversation = bodyshop.conversations[0]; // Expect only one conversation per phone number per bodyshop + let conversationid; let newMessage = { msid: req.body.SmsMessageSid, text: req.body.Body, image: !!req.body.MediaUrl0, - image_path: generateMediaArray(req.body) + image_path: generateMediaArray(req.body), + isoutbound: false, + userid: null // Add additional fields as necessary }; - if (isDuplicate) { - logger.log("sms-inbound-error", "ERROR", "api", null, { - ...loggerData, - messagingServiceSid: req.body.MessagingServiceSid, - type: "duplicate-phone" - }); - return res.status(400).json({ success: false, error: "Duplicate phone number" }); - } + if (existingConversation) { + // Use the existing conversation + conversationid = existingConversation.id; - if (isNewConversation) { - newMessage.conversation = { - data: { + // Unarchive the conversation if necessary + if (existingConversation.archived) { + await client.request(queries.UNARCHIVE_CONVERSATION, { + id: conversationid, + archived: false + }); + } + } else { + // Create a new conversation + const newConversationResponse = await client.request(queries.CREATE_CONVERSATION, { + conversation: { bodyshopid: bodyshop.id, phone_num: phone(req.body.From).phoneNumber, archived: false } - }; - } else { - const existingConversation = bodyshop.conversations[0]; - - // Update the conversation to unarchive it - if (existingConversation.archived) { - await client.request(queries.UNARCHIVE_CONVERSATION, { - id: existingConversation.id, - archived: false - }); - } - - newMessage.conversationid = existingConversation.id; + }); + const createdConversation = newConversationResponse.insert_conversations.returning[0]; + conversationid = createdConversation.id; } - const query = isNewConversation ? queries.RECEIVE_MESSAGE : queries.INSERT_MESSAGE; - const variables = isNewConversation - ? { msg: newMessage } - : { msg: newMessage, conversationid: newMessage.conversationid }; + // Ensure `conversationid` is added to the message + newMessage.conversationid = conversationid; + + // Step 3: Insert the message into the conversation + const insertresp = await client.request(queries.INSERT_MESSAGE, { + msg: newMessage, + conversationid: conversationid + }); - const insertresp = await client.request(query, variables); const message = insertresp?.insert_messages?.returning?.[0]; const conversation = message?.conversation || null; @@ -98,6 +97,7 @@ exports.receive = async (req, res) => { throw new Error("Conversation data is missing from the response."); } + // Step 4: Notify clients through Redis const broadcastRoom = getBodyshopRoom(conversation.bodyshop.id); const conversationRoom = getBodyshopConversationRoom({ bodyshopId: conversation.bodyshop.id, @@ -113,19 +113,20 @@ exports.receive = async (req, res) => { ioRedis.to(broadcastRoom).emit("new-message-summary", { ...commonPayload, - existingConversation: !isNewConversation, - newConversation: isNewConversation ? conversation : null, + existingConversation: !!existingConversation, + newConversation: !existingConversation ? conversation : null, summary: true }); ioRedis.to(conversationRoom).emit("new-message-detailed", { newMessage: message, ...commonPayload, - newConversation: isNewConversation ? conversation : null, - existingConversation: !isNewConversation, + newConversation: !existingConversation ? conversation : null, + existingConversation: !!existingConversation, summary: false }); + // Step 5: Send FCM notification const fcmresp = await admin.messaging().send({ topic: `${message.conversation.bodyshop.imexshopid}-messaging`, notification: { From c3c66f96465afaefd60e239b4e007d435217da22 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 25 Nov 2024 12:46:10 -0800 Subject: [PATCH 42/74] feature/IO-3000-messaging-sockets-migration2 - Final Modifications Signed-off-by: Dave Richer --- .../registerMessagingSocketHandlers.js | 94 ++++++------------- .../chat-send-message.component.jsx | 8 -- client/src/redux/messaging/messaging.sagas.js | 91 +++++++++--------- server/web-sockets/redisSocketEvents.js | 13 --- 4 files changed, 77 insertions(+), 129 deletions(-) diff --git a/client/src/components/chat-affix/registerMessagingSocketHandlers.js b/client/src/components/chat-affix/registerMessagingSocketHandlers.js index d0ab4baa5..09b405861 100644 --- a/client/src/components/chat-affix/registerMessagingSocketHandlers.js +++ b/client/src/components/chat-affix/registerMessagingSocketHandlers.js @@ -46,16 +46,20 @@ export const registerMessagingHandlers = ({ socket, client }) => { variables: queryVariables }); + const existingConversations = queryResults?.conversations || []; const enrichedConversation = enrichConversation(newConversation, isoutbound); - client.cache.modify({ - id: "ROOT_QUERY", - fields: { - conversations(existingConversations = []) { - return [enrichedConversation, ...existingConversations]; + // Avoid adding duplicate conversations + if (!existingConversations.some((conv) => conv.id === enrichedConversation.id)) { + client.cache.modify({ + id: "ROOT_QUERY", + fields: { + conversations(existingConversations = []) { + return [enrichedConversation, ...existingConversations]; + } } - } - }); + }); + } } catch (error) { console.error("Error updating cache for new conversation:", error); } @@ -192,7 +196,10 @@ export const registerMessagingHandlers = ({ socket, client }) => { } }); - logLocal("handleNewMessageDetailed - Message appended successfully", { conversationId, newMessage }); + logLocal("handleNewMessageDetailed - Message appended successfully", { + conversationId, + newMessage + }); } catch (error) { console.error("Error updating conversation messages in cache:", error); } @@ -255,7 +262,10 @@ export const registerMessagingHandlers = ({ socket, client }) => { } }); - logLocal("handleMessageChanged - Message updated successfully", { messageId: message.id, type: message.type }); + logLocal("handleMessageChanged - Message updated successfully", { + messageId: message.id, + type: message.type + }); } catch (error) { console.error("handleMessageChanged - Error modifying cache:", error); } @@ -348,11 +358,21 @@ export const registerMessagingHandlers = ({ socket, client }) => { include: [CONVERSATION_LIST_QUERY, ...(detailsExist ? [GET_CONVERSATION_DETAILS] : [])], variables: [ { query: CONVERSATION_LIST_QUERY, variables: listQueryVariables }, - ...(detailsExist ? [{ query: GET_CONVERSATION_DETAILS, variables: detailsQueryVariables }] : []) + ...(detailsExist + ? [ + { + query: GET_CONVERSATION_DETAILS, + variables: detailsQueryVariables + } + ] + : []) ] }); - logLocal("handleConversationChanged - Refetched queries after state change", { conversationId, type }); + logLocal("handleConversationChanged - Refetched queries after state change", { + conversationId, + type + }); } catch (error) { console.error("Error refetching queries after conversation state change:", error); } @@ -393,60 +413,8 @@ export const registerMessagingHandlers = ({ socket, client }) => { } }; - const handleNewMessage = ({ conversationId, message }) => { - if (!conversationId || !message?.id || !message?.text) { - logLocal("handleNewMessage - Missing conversationId or message details", { conversationId, message }); - return; - } - - logLocal("handleNewMessage - Start", { conversationId, message }); - - try { - // Add the new message to the cache - client.cache.modify({ - id: client.cache.identify({ __typename: "conversations", id: conversationId }), - fields: { - messages(existing = []) { - // Write the new message to the cache - const newMessageRef = client.cache.writeFragment({ - data: { - __typename: "messages", - id: message.id, - text: message.text, - selectedMedia: message.image_path || [], - imexshopid: message.userid, - status: message.status, - created_at: message.created_at, - read: message.read - }, - fragment: gql` - fragment NewMessage on messages { - id - text - selectedMedia - imexshopid - status - created_at - read - } - ` - }); - - // The merge function defined in the cache will handle deduplication - return [...existing, newMessageRef]; - } - } - }); - - logLocal("handleNewMessage - Message added to cache", { conversationId, messageId: message.id }); - } catch (error) { - console.error("handleNewMessage - Error modifying cache:", error); - } - }; - socket.on("new-message-summary", handleNewMessageSummary); socket.on("new-message-detailed", handleNewMessageDetailed); - socket.on("new-message", handleNewMessage); socket.on("message-changed", handleMessageChanged); socket.on("conversation-changed", handleConversationChanged); }; diff --git a/client/src/components/chat-send-message/chat-send-message.component.jsx b/client/src/components/chat-send-message/chat-send-message.component.jsx index e86531ee4..7aa91c8e6 100644 --- a/client/src/components/chat-send-message/chat-send-message.component.jsx +++ b/client/src/components/chat-send-message/chat-send-message.component.jsx @@ -49,14 +49,6 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi imexshopid: bodyshop.imexshopid }; sendMessage(newMessage); - if (socket) { - const lastMessage = conversation.messages?.[conversation.messages.length - 1]; // Get the last message - socket.emit("message-added", { - conversationId: conversation.id, - bodyshopId: bodyshop.id, - message: lastMessage - }); - } setSelectedMedia( selectedMedia.map((i) => { return { ...i, isSelected: false }; diff --git a/client/src/redux/messaging/messaging.sagas.js b/client/src/redux/messaging/messaging.sagas.js index fb519bcca..e2cbb3bc8 100644 --- a/client/src/redux/messaging/messaging.sagas.js +++ b/client/src/redux/messaging/messaging.sagas.js @@ -33,22 +33,62 @@ export function* openChatByPhone({ payload }) { logImEXEvent("messaging_open_by_phone"); const { socket, phone_num, jobid } = payload; if (!socket || !phone_num) return; + const p = parsePhoneNumber(phone_num, "CA"); const bodyshop = yield select(selectBodyshop); try { + // Fetch conversations including archived ones const { data: { conversations } } = yield client.query({ query: CONVERSATION_ID_BY_PHONE, variables: { phone: p.number }, - // THIS NEEDS TO REMAIN NO CACHE, IT CHECKS FOR NEW MESSAGES FOR SYNC - fetchPolicy: "no-cache" + fetchPolicy: "no-cache" // Ensure the query always gets the latest data }); - const existingConversation = conversations?.find((c) => c.phone_num === phone_num); + const existingConversation = conversations?.find((c) => c.phone_num === p.number); - if (!existingConversation) { + if (existingConversation) { + let updatedConversation = existingConversation; + + if (existingConversation.archived) { + // If the conversation is archived, unarchive it + const { + data: { update_conversations_by_pk: unarchivedConversation } + } = yield client.mutate({ + mutation: TOGGLE_CONVERSATION_ARCHIVE, + variables: { + id: existingConversation.id, + archived: false + } + }); + + updatedConversation = unarchivedConversation; + + // Emit an event indicating the conversation was unarchived + socket.emit("conversation-modified", { + type: "conversation-unarchived", + conversationId: unarchivedConversation.id, + bodyshopId: bodyshop.id, + archived: false + }); + } + + // Set the unarchived or already active conversation as selected + yield put(setSelectedConversation(updatedConversation.id)); + + // Add job tag if needed + if (jobid && !updatedConversation.job_conversations.find((jc) => jc.jobid === jobid)) { + yield client.mutate({ + mutation: INSERT_CONVERSATION_TAG, + variables: { + conversationId: updatedConversation.id, + jobId: jobid + } + }); + } + } else { // No conversation exists, create a new one const { data: { @@ -67,9 +107,9 @@ export function* openChatByPhone({ payload }) { } }); - const createdConversation = newConversations[0]; // Get the newly created conversation + const createdConversation = newConversations[0]; - // Emit event for new conversation with full details + // Emit event for the new conversation with full details socket.emit("conversation-modified", { bodyshopId: bodyshop.id, type: "conversation-created", @@ -78,45 +118,6 @@ export function* openChatByPhone({ payload }) { // Set the newly created conversation as selected yield put(setSelectedConversation(createdConversation.id)); - } else { - let updatedConversation = existingConversation; - - if (existingConversation.archived) { - // Conversation is archived, unarchive it in the DB - const { - data: { update_conversations_by_pk: unarchivedConversation } - } = yield client.mutate({ - mutation: TOGGLE_CONVERSATION_ARCHIVE, - variables: { - id: existingConversation.id, - archived: false - } - }); - - updatedConversation = unarchivedConversation; - - // Emit the unarchived event only once - socket.emit("conversation-modified", { - type: "conversation-unarchived", - conversationId: unarchivedConversation.id, - bodyshopId: bodyshop.id, - archived: false - }); - } - - // Open the conversation - yield put(setSelectedConversation(updatedConversation.id)); - - // Check and add job tag if needed - if (jobid && !updatedConversation.job_conversations.find((jc) => jc.jobid === jobid)) { - yield client.mutate({ - mutation: INSERT_CONVERSATION_TAG, - variables: { - conversationId: updatedConversation.id, - jobId: jobid - } - }); - } } } catch (error) { console.error("Error in openChatByPhone saga.", error); diff --git a/server/web-sockets/redisSocketEvents.js b/server/web-sockets/redisSocketEvents.js index 366461adc..9fd7e5a93 100644 --- a/server/web-sockets/redisSocketEvents.js +++ b/server/web-sockets/redisSocketEvents.js @@ -188,19 +188,6 @@ const redisSocketEvents = ({ } }; - const messageAdded = ({ bodyshopId, conversationId, message }) => { - try { - const room = getBodyshopConversationRoom({ bodyshopId, conversationId }); - io.to(room).emit("new-message", { message, conversationId }); - } catch (error) { - logger.log("Failed to handle new message", "error", "io-redis", null, { - error: error.message, - stack: error.stack - }); - } - }; - - socket.on("message-added", messageAdded); socket.on("conversation-modified", conversationModified); socket.on("join-bodyshop-conversation", joinConversationRoom); socket.on("leave-bodyshop-conversation", leaveConversationRoom); From 5c30f33dac9e1877b3ee994df678d269555178b5 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 25 Nov 2024 13:45:09 -0800 Subject: [PATCH 43/74] feature/IO-3000-messaging-sockets-migration2 - Additional logging Signed-off-by: Dave Richer --- server/web-sockets/redisSocketEvents.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/web-sockets/redisSocketEvents.js b/server/web-sockets/redisSocketEvents.js index 9fd7e5a93..d96fcb7d7 100644 --- a/server/web-sockets/redisSocketEvents.js +++ b/server/web-sockets/redisSocketEvents.js @@ -182,6 +182,9 @@ const redisSocketEvents = ({ }); } catch (error) { logger.log("Failed to handle conversation modification", "error", "io-redis", null, { + bodyshopId, + conversationId, + fields, error: error.message, stack: error.stack }); From 225b57fd588b7755b9e42203cfaf513abd30837b Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Mon, 25 Nov 2024 14:08:38 -0800 Subject: [PATCH 44/74] IO-2920 Update checkfee method to get CC amount based on new information. --- bodyshop_translations.babel | 107 +- .../job-totals.table.totals.component.jsx | 26 +- ...totals.cash-discount-display.component.jsx | 13 +- client/src/translations/en_us/common.json | 7207 +++++++++-------- client/src/translations/es/common.json | 7207 +++++++++-------- client/src/translations/fr/common.json | 7207 +++++++++-------- server/intellipay/intellipay.js | 10 +- 7 files changed, 10955 insertions(+), 10822 deletions(-) diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 236f677e0..96f83b437 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -1,4 +1,4 @@ - +