From e8b7e2f0b9c6c00052396a07baa0de936f0a269c Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 26 May 2021 09:06:05 -0700 Subject: [PATCH 01/10] IO-1152 Add in part mark up for BC Economical claims. --- bodyshop_translations.babel | 21 +++++ .../job-detail-lines/job-lines.component.jsx | 9 +- .../job-totals.table.parts.component.jsx | 31 ++++--- client/src/graphql/jobs.queries.js | 1 + client/src/translations/en_us/common.json | 1 + client/src/translations/es/common.json | 1 + client/src/translations/fr/common.json | 1 + server/accounting/qbxml/qbxml-receivables.js | 13 ++- server/graphql-client/queries.js | 1 + server/job/job-costing.js | 84 ++++++++++++------- server/job/job-totals.js | 34 ++++++-- 11 files changed, 147 insertions(+), 50 deletions(-) diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 446075c77..0faad2b83 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -22179,6 +22179,27 @@ + + prt_dsmk_total + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + rates false diff --git a/client/src/components/job-detail-lines/job-lines.component.jsx b/client/src/components/job-detail-lines/job-lines.component.jsx index 02c73382a..cd88da672 100644 --- a/client/src/components/job-detail-lines/job-lines.component.jsx +++ b/client/src/components/job-detail-lines/job-lines.component.jsx @@ -155,7 +155,14 @@ export function JobLinesComponent({ state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order, ellipsis: true, render: (text, record) => ( - {record.act_price} + <> + {record.act_price} + {record.prt_dsmk_p !== 0 && ( + {`(${record.prt_dsmk_p}%)`} + )} + ), }, { diff --git a/client/src/components/job-totals-table/job-totals.table.parts.component.jsx b/client/src/components/job-totals-table/job-totals.table.parts.component.jsx index 7f1b884d6..93ed6eea6 100644 --- a/client/src/components/job-totals-table/job-totals.table.parts.component.jsx +++ b/client/src/components/job-totals-table/job-totals.table.parts.component.jsx @@ -69,17 +69,28 @@ export default function JobTotalsTableParts({ job }) { x: true, }} summary={() => ( - - - {t("jobs.labels.partstotal")} - + <> + + + {t("jobs.labels.prt_dsmk_total")} + - - - {Dinero(job.job_totals.parts.parts.total).toFormat()} - - - + + {Dinero(job.job_totals.parts.parts.prt_dsmk_total).toFormat()} + + + + + {t("jobs.labels.partstotal")} + + + + + {Dinero(job.job_totals.parts.parts.total).toFormat()} + + + + )} /> ); diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index 0f1d23141..9d1dbea66 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -528,6 +528,7 @@ export const GET_JOB_BY_PK = gql` tax_part db_ref manual_line + prt_dsmk_p billlines(limit: 1, order_by: { bill: { date: desc } }) { id quantity diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 97730f5e4..86345398c 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -1316,6 +1316,7 @@ "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 amount of returns created for this job." }, + "prt_dsmk_total": "Line Item Markup", "rates": "Rates", "rates_subtotal": "All Rates Subtotal", "reconciliation": { diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index ccbcc56ad..ac60ee51a 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -1316,6 +1316,7 @@ "partstotal": "", "totalreturns": "" }, + "prt_dsmk_total": "", "rates": "Tarifas", "rates_subtotal": "", "reconciliation": { diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index b34210452..7dc82bfce 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -1316,6 +1316,7 @@ "partstotal": "", "totalreturns": "" }, + "prt_dsmk_total": "", "rates": "Les taux", "rates_subtotal": "", "reconciliation": { diff --git a/server/accounting/qbxml/qbxml-receivables.js b/server/accounting/qbxml/qbxml-receivables.js index ac24f4408..49f67f397 100644 --- a/server/accounting/qbxml/qbxml-receivables.js +++ b/server/accounting/qbxml/qbxml-receivables.js @@ -203,7 +203,8 @@ const generateInvoiceQbxml = ( //Create the invoice lines mapping. jobs_by_pk.joblines.map((jobline) => { //Parts Lines - if (jobline.db_ref === "936008") { //If either of these DB REFs change, they also need to change in job-totals calculations. + if (jobline.db_ref === "936008") { + //If either of these DB REFs change, they also need to change in job-totals calculations. hasMapaLine = true; } if (jobline.db_ref === "936007") { @@ -213,7 +214,15 @@ const generateInvoiceQbxml = ( if (jobline.profitcenter_part && jobline.act_price) { const DineroAmount = Dinero({ amount: Math.round(jobline.act_price * 100), - }).multiply(jobline.part_qty || 1); + }) + .multiply(jobline.part_qty || 1) + .add( + Dinero({ + amount: Math.round((jobline.act_price || 0) * 100), + }) + .multiply(jobline.part_qty || 0) + .percentage(jobline.prt_dsmk_p) + ); const account = responsibilityCenters.profits.find( (i) => jobline.profitcenter_part.toLowerCase() === i.name.toLowerCase() ); diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index c9d7f3173..fd3a07228 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -566,6 +566,7 @@ exports.GET_JOB_BY_PK = ` query GET_JOB_BY_PK($id: uuid!) { tax_part db_ref manual_line + prt_dsmk_p parts_order_lines { id parts_order { diff --git a/server/job/job-costing.js b/server/job/job-costing.js index 7a15b4f29..7b98acf1e 100644 --- a/server/job/job-costing.js +++ b/server/job/job-costing.js @@ -122,27 +122,34 @@ async function JobCostingMulti(req, res) { }); //Add all summary data. - multiSummary.summaryData.totalPartsSales = multiSummary.summaryData.totalPartsSales.add( - costingData.summaryData.totalPartsSales - ); - multiSummary.summaryData.totalSales = multiSummary.summaryData.totalSales.add( - costingData.summaryData.totalSales - ); - multiSummary.summaryData.totalLaborCost = multiSummary.summaryData.totalLaborCost.add( - costingData.summaryData.totalLaborCost - ); - multiSummary.summaryData.totalLaborSales = multiSummary.summaryData.totalLaborSales.add( - costingData.summaryData.totalLaborSales - ); - multiSummary.summaryData.totalPartsCost = multiSummary.summaryData.totalPartsCost.add( - costingData.summaryData.totalPartsCost - ); - multiSummary.summaryData.totalCost = multiSummary.summaryData.totalCost.add( - costingData.summaryData.totalCost - ); - multiSummary.summaryData.gpdollars = multiSummary.summaryData.gpdollars.add( - costingData.summaryData.gpdollars - ); + multiSummary.summaryData.totalPartsSales = + multiSummary.summaryData.totalPartsSales.add( + costingData.summaryData.totalPartsSales + ); + multiSummary.summaryData.totalSales = + multiSummary.summaryData.totalSales.add( + costingData.summaryData.totalSales + ); + multiSummary.summaryData.totalLaborCost = + multiSummary.summaryData.totalLaborCost.add( + costingData.summaryData.totalLaborCost + ); + multiSummary.summaryData.totalLaborSales = + multiSummary.summaryData.totalLaborSales.add( + costingData.summaryData.totalLaborSales + ); + multiSummary.summaryData.totalPartsCost = + multiSummary.summaryData.totalPartsCost.add( + costingData.summaryData.totalPartsCost + ); + multiSummary.summaryData.totalCost = + multiSummary.summaryData.totalCost.add( + costingData.summaryData.totalCost + ); + multiSummary.summaryData.gpdollars = + multiSummary.summaryData.gpdollars.add( + costingData.summaryData.gpdollars + ); console.timeEnd(`SummaryOfCostingData-${job.id}`); //Take the summary data & add it to total summary data. }); @@ -220,9 +227,8 @@ function GenerateCostingData(job) { }).multiply(val.mod_lb_hrs || 0); if (!acc.labor[laborProfitCenter]) acc.labor[laborProfitCenter] = Dinero(); - acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter].add( - laborAmount - ); + acc.labor[laborProfitCenter] = + acc.labor[laborProfitCenter].add(laborAmount); if (val.mod_lbr_ty === "LAR") { if (!acc.labor[defaultProfits["MAPA"]]) @@ -265,12 +271,19 @@ function GenerateCostingData(job) { ); const partsAmount = Dinero({ amount: Math.round((val.act_price || 0) * 100), - }).multiply(val.part_qty || 1); + }) + .multiply(val.part_qty || 1) + .add( + Dinero({ + amount: Math.round((val.act_price || 0) * 100), + }) + .multiply(val.part_qty || 0) + .percentage(val.prt_dsmk_p) + ); if (!acc.parts[partsProfitCenter]) acc.parts[partsProfitCenter] = Dinero(); - acc.parts[partsProfitCenter] = acc.parts[partsProfitCenter].add( - partsAmount - ); + acc.parts[partsProfitCenter] = + acc.parts[partsProfitCenter].add(partsAmount); } //To deal with additional costs. @@ -287,7 +300,15 @@ function GenerateCostingData(job) { } else { const partsAmount = Dinero({ amount: Math.round((val.act_price || 0) * 100), - }).multiply(val.part_qty || 1); + }) + .multiply(val.part_qty || 1) + .add( + Dinero({ + amount: Math.round((val.act_price || 0) * 100), + }) + .multiply(val.part_qty || 0) + .percentage(val.prt_dsmk_p) + ); console.log( `*** partsAmount`, val.line_desc, @@ -296,9 +317,8 @@ function GenerateCostingData(job) { ); if (!acc.parts[partsProfitCenter]) acc.parts[partsProfitCenter] = Dinero(); - acc.parts[partsProfitCenter] = acc.parts[partsProfitCenter].add( - partsAmount - ); + acc.parts[partsProfitCenter] = + acc.parts[partsProfitCenter].add(partsAmount); } } diff --git a/server/job/job-totals.js b/server/job/job-totals.js index b0798c62d..dea522a89 100644 --- a/server/job/job-totals.js +++ b/server/job/job-totals.js @@ -86,6 +86,7 @@ async function Totals(req, res) { res.status(400).send(JSON.stringify(error)); } } + function CalculateRatesTotals(ratesList) { const jobLines = ratesList.joblines.filter((jl) => !jl.removed); @@ -211,6 +212,13 @@ function CalculatePartsTotals(jobLines) { ...acc, parts: { ...acc.parts, + prt_dsmk_total: acc.parts.prt_dsmk_total.add( + Dinero({ + amount: Math.round((value.act_price || 0) * 100), + }) + .multiply(value.part_qty || 0) + .percentage(value.prt_dsmk_p) + ), list: { ...acc.parts.list, [value.part_type]: @@ -229,11 +237,19 @@ function CalculatePartsTotals(jobLines) { }).multiply(value.part_qty || 0), }, }, - subtotal: acc.parts.subtotal.add( - Dinero({ - amount: Math.round(value.act_price * 100), - }).multiply(value.part_qty || 0) - ), + subtotal: acc.parts.subtotal + .add( + Dinero({ + amount: Math.round(value.act_price * 100), + }).multiply(value.part_qty || 0) + ) + .add( + Dinero({ + amount: Math.round((value.act_price || 0) * 100), + }) + .multiply(value.part_qty || 0) + .percentage(value.prt_dsmk_p) + ), }, }; } @@ -241,6 +257,7 @@ function CalculatePartsTotals(jobLines) { { parts: { list: {}, + prt_dsmk_total: Dinero(), subtotal: Dinero({ amount: 0 }), total: Dinero({ amount: 0 }), }, @@ -360,6 +377,13 @@ function CalculateTaxesTotals(job, otherTotals) { statePartsTax = statePartsTax.add( Dinero({ amount: Math.round((val.act_price || 0) * 100) }) .multiply(val.part_qty || 1) + .add( + Dinero({ + amount: Math.round((val.act_price || 0) * 100), + }) + .multiply(val.part_qty || 0) + .percentage(val.prt_dsmk_p) + ) .percentage( ((job.parts_tax_rates && job.parts_tax_rates[val.part_type] && From b5b4a3a4f93754c8920073d821cab99113d37a3f Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 26 May 2021 14:16:17 -0700 Subject: [PATCH 02/10] IO-1080 Standard audit note language. --- .../jobs-admin-unvoid/jobs-admin-unvoid.component.jsx | 2 +- .../jobs-available-table.container.jsx | 10 ++-------- .../jobs-detail-header-actions.component.jsx | 5 +---- client/src/translations/en_us/common.json | 10 +++++----- 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx b/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx index d467fe8c1..4f36cd850 100644 --- a/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx +++ b/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx @@ -32,7 +32,7 @@ mutation UNVOID_JOB($jobId: uuid!) { } insert_notes(objects: {jobid: $jobId, audit: true, created_by: "${ currentUser.email - }", text: "${t("jobs.labels.unvoidnote", { email: currentUser.email })}"}) { + }", text: "${t("jobs.labels.unvoidnote")}"}) { returning { id } 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 345981c08..1f68838c3 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 @@ -124,10 +124,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) { data: { created_by: currentUser.email, audit: true, - text: t("jobs.labels.importnote", { - date: moment().format("MM/DD/yyy"), - time: moment().format("hh:mm a"), - }), + text: t("jobs.labels.importnote"), }, }, queued_for_parts: true, @@ -278,10 +275,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) { jobid: selectedJob, created_by: currentUser.email, audit: true, - text: t("jobs.labels.supplementnote", { - date: moment().format("MM/DD/yyy"), - time: moment().format("hh:mm a"), - }), + text: t("jobs.labels.supplementnote"), }, ], }, 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 16bcc94ff..9c9dd4533 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 @@ -385,10 +385,7 @@ export function JobsDetailHeaderActions({ jobid: job.id, created_by: currentUser.email, audit: true, - text: t("jobs.labels.voidnote", { - date: moment().format("MM/DD/yyy"), - time: moment().format("hh:mm a"), - }), + text: t("jobs.labels.voidnote"), }, ], }, diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 86345398c..19bd55384 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -555,7 +555,7 @@ "driverinformation": "Driver's Information", "findcontract": "Find Contract", "findermodal": "Contract Finder", - "noteconvertedfrom": "R.O. created from converted Courtesy Car Contract {{agreementnumber}}.", + "noteconvertedfrom": "R.O. created from converted Courtesy Car Contract {{agreementnumber}}.", "populatefromjob": "Populate from Job", "rates": "Contract Rates", "time": "Time", @@ -1281,7 +1281,7 @@ "gppercent": "% G.P.", "hrs_claimed": "Hours Claimed", "hrs_total": "Hours Total", - "importnote": "The job was initially imported on {{date}} at {{time}}.", + "importnote": "The job was initially imported.", "inproduction": "In Production", "intakechecklist": "Intake Checklist", "job": "Job Details", @@ -1342,19 +1342,19 @@ "state_tax_amt": "Provincial/State Taxes", "subletstotal": "Sublets Total", "subtotal": "Subtotal", - "supplementnote": "The job had a supplement imported on {{date}} at {{time}}.", + "supplementnote": "The job had a supplement imported.", "suspense": "Suspense", "total_cost": "Total Cost", "total_cust_payable": "Total Customer Amount Payable", "total_repairs": "Total Repairs", "total_sales": "Total Sales", "totals": "Totals", - "unvoidnote": "This job was unvoided by {{email}}.", + "unvoidnote": "This job was unvoided.", "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 repair order was voided on {{date}} at {{time}}." + "voidnote": "This job was voided." }, "successes": { "addedtoproduction": "Job added to production board.", From 48ecfe0d982cdacc8f4d71fb9b709fc563b6b27a Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 26 May 2021 16:45:59 -0700 Subject: [PATCH 03/10] Media File improvements + separate DEV testing instance for cloudinary. --- bodyshop_translations.babel | 21 +++++++++++++++++++ .../documents-upload.utility.js | 4 ++-- .../jobs-detail-header-actions.component.jsx | 1 - .../jobs-documents-gallery.component.jsx | 18 ++++++++++------ client/src/graphql/documents.queries.js | 1 + client/src/translations/en_us/common.json | 3 ++- client/src/translations/es/common.json | 3 ++- client/src/translations/fr/common.json | 3 ++- 8 files changed, 42 insertions(+), 12 deletions(-) diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 0faad2b83..a4c57daea 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -10757,6 +10757,27 @@
+ + updating + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + diff --git a/client/src/components/documents-upload/documents-upload.utility.js b/client/src/components/documents-upload/documents-upload.utility.js index ad5083e95..8812aefd3 100644 --- a/client/src/components/documents-upload/documents-upload.utility.js +++ b/client/src/components/documents-upload/documents-upload.utility.js @@ -128,7 +128,7 @@ export const uploadToCloudinary = async ( let takenat; if (fileType.includes("image")) { const exif = await exifr.parse(file); - console.log(`exif`, exif); + takenat = exif && exif.DateTimeOriginal; } const documentInsert = await client.mutate({ @@ -141,7 +141,7 @@ export const uploadToCloudinary = async ( uploaded_by: uploaded_by, key: key, type: fileType, - extension: extension, + extension: cloudinaryUploadResponse.data.format || extension, bodyshopid: bodyshop.id, size: cloudinaryUploadResponse.data.bytes || file.size, takenat, 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 9c9dd4533..1b31a210d 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 @@ -1,7 +1,6 @@ import { DownCircleFilled } from "@ant-design/icons"; import { useApolloClient, useMutation } from "@apollo/client"; import { Button, Dropdown, Menu, notification, Popconfirm } from "antd"; -import moment from "moment"; import React, { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; diff --git a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx index 2c50c9672..e4c3d0eba 100644 --- a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx +++ b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx @@ -31,7 +31,9 @@ function JobsDocumentsComponent({ acc.images.push({ src: `${ process.env.REACT_APP_CLOUDINARY_ENDPOINT - }/${DetermineFileType(value.type)}/upload/${value.key}`, + }/${DetermineFileType(value.type)}/upload/${value.key}${ + value.extension ? `.${value.extension}` : "" + }`, thumbnail: `${ process.env.REACT_APP_CLOUDINARY_ENDPOINT }/${DetermineFileType(value.type)}/upload/${ @@ -51,13 +53,17 @@ function JobsDocumentsComponent({ let thumb; switch (fileType) { case "video": - thumb = `${process.env.REACT_APP_CLOUDINARY_ENDPOINT}/${fileType}/upload/c_fill,f_png,h_250,w_250/${value.key}`; + thumb = `${process.env.REACT_APP_CLOUDINARY_ENDPOINT}/${fileType}/upload/${process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS}/${value.key}`; break; case "raw": thumb = `${window.location.origin}/file.png`; break; default: - thumb = `${process.env.REACT_APP_CLOUDINARY_ENDPOINT}/${fileType}/upload/${process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS}/${value.key}`; + thumb = `${ + process.env.REACT_APP_CLOUDINARY_ENDPOINT + }/${fileType}/upload/${ + process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS + }/${value.key}${value.extension ? `.${value.extension}` : ""}`; break; } @@ -65,9 +71,9 @@ function JobsDocumentsComponent({ acc.other.push({ src: `${ process.env.REACT_APP_CLOUDINARY_ENDPOINT - }/${fileType}/upload/${fileType === "video" ? "q_auto/" : ""}${ - value.key - }${fileType === "raw" ? `.${value.extension}` : ""}`, + }/${fileType}/upload/${value.key}${ + value.extension ? `.${value.extension}` : "" + }`, thumbnail: thumb, tags: [ { diff --git a/client/src/graphql/documents.queries.js b/client/src/graphql/documents.queries.js index 8959e49a9..d0c11f914 100644 --- a/client/src/graphql/documents.queries.js +++ b/client/src/graphql/documents.queries.js @@ -20,6 +20,7 @@ export const GET_DOCUMENTS_BY_JOB = gql` type size takenat + extension bill { id invoice_number diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 19bd55384..4dc554b3a 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -683,7 +683,8 @@ "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." + "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.", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index ac60ee51a..0e50e4a16 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -683,7 +683,8 @@ "deleting_cloudinary": "", "getpresignurl": "Error al obtener la URL prescrita para el documento. {{message}}", "insert": "Incapaz de cargar el archivo. {{message}}", - "nodocuments": "No hay documentos" + "nodocuments": "No hay documentos", + "updating": "" }, "labels": { "confirmdelete": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 7dc82bfce..4529a34e9 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -683,7 +683,8 @@ "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." + "nodocuments": "Il n'y a pas de documents.", + "updating": "" }, "labels": { "confirmdelete": "", From 1b0e37be45cd37f50e070a78d99bafa852fb2186 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Thu, 27 May 2021 08:49:43 -0700 Subject: [PATCH 04/10] IO-998 Resolve media rename issues for non images. --- .../documents-upload/documents-upload.utility.js | 1 + server/media/media.js | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/client/src/components/documents-upload/documents-upload.utility.js b/client/src/components/documents-upload/documents-upload.utility.js index 8812aefd3..5856d985e 100644 --- a/client/src/components/documents-upload/documents-upload.utility.js +++ b/client/src/components/documents-upload/documents-upload.utility.js @@ -176,6 +176,7 @@ export const uploadToCloudinary = async ( } }; +//Also needs to be updated in media JS and mobile app. export function DetermineFileType(filetype) { if (!filetype) return "auto"; else if (filetype.startsWith("image")) return "image"; diff --git a/server/media/media.js b/server/media/media.js index 9aa60663e..e02beeac5 100644 --- a/server/media/media.js +++ b/server/media/media.js @@ -40,7 +40,9 @@ exports.renameKeys = async (req, res) => { try { const res = { id: d.id, - ...(await cloudinary.uploader.rename(d.from, d.to)), + ...(await cloudinary.uploader.rename(d.from, d.to, { + resource_type: DetermineFileType(d.type), + })), }; return res; } catch (error) { @@ -56,3 +58,14 @@ exports.renameKeys = async (req, res) => { res.send(result); }; + +//Also needs to be updated in upload utility and mobile app. +function DetermineFileType(filetype) { + if (!filetype) return "auto"; + else if (filetype.startsWith("image")) return "image"; + else if (filetype.startsWith("video")) return "video"; + else if (filetype.startsWith("application/pdf")) return "image"; + else if (filetype.startsWith("application")) return "raw"; + + return "auto"; +} From a5628188d80c7855bdc9ad25f1a8f4aabb2e7771 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Thu, 27 May 2021 11:35:58 -0700 Subject: [PATCH 05/10] IO-1171 Null vehicle link on detail page. --- .../jobs-detail-header.component.jsx | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx b/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx index 363fc889b..8beebc60d 100644 --- a/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx +++ b/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx @@ -151,17 +151,21 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) { - {`${job.v_model_yr || ""} ${job.v_color || ""} + job.vehicle ? ( + + {`${job.v_model_yr || ""} ${job.v_color || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`} - + + ) : ( + + ) } >
From 842cb548674ff6521e7712925e1f15702773da21 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Thu, 27 May 2021 13:15:58 -0700 Subject: [PATCH 06/10] IO-1153 Phonebook Validation --- bodyshop_translations.babel | 42 +++++++++++++++++++ .../phonebook-form.component.jsx | 26 +++++++++++- client/src/translations/en_us/common.json | 2 + client/src/translations/es/common.json | 2 + client/src/translations/fr/common.json | 2 + 5 files changed, 73 insertions(+), 1 deletion(-) diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index a4c57daea..36244a534 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -27901,6 +27901,48 @@ labels + + noneselected + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + onenamerequired + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + vendorcategory false diff --git a/client/src/components/phonebook-form/phonebook-form.component.jsx b/client/src/components/phonebook-form/phonebook-form.component.jsx index e962a34c6..e40096ce9 100644 --- a/client/src/components/phonebook-form/phonebook-form.component.jsx +++ b/client/src/components/phonebook-form/phonebook-form.component.jsx @@ -82,7 +82,31 @@ export function PhonebookFormComponent({ - + ({ + validator(rule, value) { + const { firstname, lastname, company } = getFieldsValue([ + "firstname", + "lastname", + "company", + ]); + + if ( + (firstname && firstname.trim() !== "") || + (lastname && lastname.trim() !== "") || + (company && company.trim() !== "") + ) { + return Promise.resolve(); + } + return Promise.reject(t("phonebook.labels.onenamerequired")); + }, + }), + ]} + > diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 4dc554b3a..b978a4ac3 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -1666,6 +1666,8 @@ "state": "Province/State" }, "labels": { + "noneselected": "No phone book entry selected. ", + "onenamerequired": "At least one name related field is required.", "vendorcategory": "Vendor" }, "successes": { diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 0e50e4a16..9ebe3517d 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -1666,6 +1666,8 @@ "state": "" }, "labels": { + "noneselected": "", + "onenamerequired": "", "vendorcategory": "" }, "successes": { diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 4529a34e9..05d7f3ccf 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -1666,6 +1666,8 @@ "state": "" }, "labels": { + "noneselected": "", + "onenamerequired": "", "vendorcategory": "" }, "successes": { From 0b21b8d97609875a41fda10bb5ffb88dd97d86fe Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Thu, 27 May 2021 13:32:57 -0700 Subject: [PATCH 07/10] IO-1138 Offline detection. --- bodyshop_translations.babel | 42 +++++++++++++++++++ client/src/App/App.container.jsx | 1 + client/src/App/App.jsx | 35 +++++++++++++++- .../redux/application/application.actions.js | 5 +++ .../redux/application/application.reducer.js | 6 +++ .../application/application.selectors.js | 4 ++ .../redux/application/application.types.js | 1 + client/src/translations/en_us/common.json | 2 + client/src/translations/es/common.json | 2 + client/src/translations/fr/common.json | 2 + 10 files changed, 99 insertions(+), 1 deletion(-) diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 36244a534..1f7b2a5c9 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -12968,6 +12968,48 @@ + + nointernet + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + nointernet_sub + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + none false diff --git a/client/src/App/App.container.jsx b/client/src/App/App.container.jsx index c8e619c05..1f5298da2 100644 --- a/client/src/App/App.container.jsx +++ b/client/src/App/App.container.jsx @@ -14,6 +14,7 @@ if (process.env.NODE_ENV === "production") LogRocket.init("gvfvfw/bodyshopapp"); export default function AppContainer() { const { t } = useTranslation(); + return ( const mapStateToProps = createStructuredSelector({ currentUser: selectCurrentUser, + online: selectOnline, }); const mapDispatchToProps = (dispatch) => ({ checkUserSession: () => dispatch(checkUserSession()), + setOnline: (isOnline) => dispatch(setOnline(isOnline)), }); -export function App({ checkUserSession, currentUser }) { +export function App({ checkUserSession, currentUser, online, setOnline }) { useEffect(() => { checkUserSession(); }, [checkUserSession]); @@ -42,10 +47,38 @@ export function App({ checkUserSession, currentUser }) { const { t } = useTranslation(); + window.addEventListener("offline", function (e) { + console.log("Internet connection lost."); + setOnline(false); + }); + + window.addEventListener("online", function (e) { + setOnline(true); + }); + if (currentUser.authorized === null) { return ; } + if (!online) + return ( + { + window.location.reload(); + }} + > + {t("general.actions.refresh")} + + } + /> + ); + return ( }> diff --git a/client/src/redux/application/application.actions.js b/client/src/redux/application/application.actions.js index cffcaa4fc..3f3fb4012 100644 --- a/client/src/redux/application/application.actions.js +++ b/client/src/redux/application/application.actions.js @@ -48,3 +48,8 @@ export const setPartnerVersion = (version) => ({ type: ApplicationActionTypes.SET_PARTNER_VERSION, payload: version, }); + +export const setOnline = (isOnline) => ({ + type: ApplicationActionTypes.SET_ONLINE_STATUS, + payload: isOnline, +}); diff --git a/client/src/redux/application/application.reducer.js b/client/src/redux/application/application.reducer.js index 40fece7d7..60685e2cc 100644 --- a/client/src/redux/application/application.reducer.js +++ b/client/src/redux/application/application.reducer.js @@ -2,6 +2,7 @@ import ApplicationActionTypes from "./application.types"; const INITIAL_STATE = { loading: false, + online: true, breadcrumbs: [], recentItems: [], selectedHeader: "home", @@ -21,6 +22,11 @@ const applicationReducer = (state = INITIAL_STATE, action) => { ...state, selectedHeader: action.payload, }; + case ApplicationActionTypes.SET_ONLINE_STATUS: + return { + ...state, + online: action.payload, + }; case ApplicationActionTypes.ADD_RECENT_ITEM: return { ...state, diff --git a/client/src/redux/application/application.selectors.js b/client/src/redux/application/application.selectors.js index 2dba7b8ea..5c48a17d1 100644 --- a/client/src/redux/application/application.selectors.js +++ b/client/src/redux/application/application.selectors.js @@ -40,3 +40,7 @@ export const selectJobReadOnly = createSelector( [selectApplication], (application) => application.jobReadOnly ); +export const selectOnline = createSelector( + [selectApplication], + (application) => application.online +); diff --git a/client/src/redux/application/application.types.js b/client/src/redux/application/application.types.js index 2de24c05d..8714692cb 100644 --- a/client/src/redux/application/application.types.js +++ b/client/src/redux/application/application.types.js @@ -9,5 +9,6 @@ const ApplicationActionTypes = { SET_SELECTED_HEADER: "SET_SELECTED_HEADER", SET_JOB_READONLY: "SET_JOB_READONLY", SET_PARTNER_VERSION: "SET_PARTNER_VERSION", + SET_ONLINE_STATUS: "SET_ONLINE_STATUS", }; export default ApplicationActionTypes; diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index b978a4ac3..9dfef4704 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -821,6 +821,8 @@ "monday": "Monday", "na": "N/A", "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", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 9ebe3517d..5777c3745 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -821,6 +821,8 @@ "monday": "", "na": "N / A", "no": "", + "nointernet": "", + "nointernet_sub": "", "none": "", "out": "Afuera", "password": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 05d7f3ccf..1bf35e380 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -821,6 +821,8 @@ "monday": "", "na": "N / A", "no": "", + "nointernet": "", + "nointernet_sub": "", "none": "", "out": "En dehors", "password": "", From d91a83a13762aa364424ba6e75388cecf59e3671 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Thu, 27 May 2021 14:04:52 -0700 Subject: [PATCH 08/10] IO-1157 Add mapa/mash costs to costing. --- bodyshop_translations.babel | 47 +++++++++++ .../shop-info/shop-info.general.component.jsx | 14 +++- client/src/graphql/bodyshop.queries.js | 2 + client/src/translations/en_us/common.json | 4 + client/src/translations/es/common.json | 4 + client/src/translations/fr/common.json | 4 + .../down.yaml | 6 ++ .../up.yaml | 5 ++ .../down.yaml | 5 ++ .../up.yaml | 6 ++ .../down.yaml | 83 ++++++++++++++++++ .../up.yaml | 84 +++++++++++++++++++ .../down.yaml | 77 +++++++++++++++++ .../up.yaml | 78 +++++++++++++++++ hasura/migrations/metadata.yaml | 2 + server/graphql-client/queries.js | 2 + server/job/job-costing.js | 60 +++++++++++-- 17 files changed, 474 insertions(+), 9 deletions(-) create mode 100644 hasura/migrations/1622147830428_alter_table_public_bodyshops_alter_column_autohouseid/down.yaml create mode 100644 hasura/migrations/1622147830428_alter_table_public_bodyshops_alter_column_autohouseid/up.yaml create mode 100644 hasura/migrations/1622147892235_alter_table_public_bodyshops_add_column_jc_hourly_rates/down.yaml create mode 100644 hasura/migrations/1622147892235_alter_table_public_bodyshops_add_column_jc_hourly_rates/up.yaml create mode 100644 hasura/migrations/1622147902848_update_permission_user_public_table_bodyshops/down.yaml create mode 100644 hasura/migrations/1622147902848_update_permission_user_public_table_bodyshops/up.yaml create mode 100644 hasura/migrations/1622147914381_update_permission_user_public_table_bodyshops/down.yaml create mode 100644 hasura/migrations/1622147914381_update_permission_user_public_table_bodyshops/up.yaml diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 1f7b2a5c9..a6258dd82 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -3339,6 +3339,53 @@ + + jc_hourly_rates + + + mapa + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + mash + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + lastnumberworkingdays false diff --git a/client/src/components/shop-info/shop-info.general.component.jsx b/client/src/components/shop-info/shop-info.general.component.jsx index 00c6225a6..74170bf3c 100644 --- a/client/src/components/shop-info/shop-info.general.component.jsx +++ b/client/src/components/shop-info/shop-info.general.component.jsx @@ -16,7 +16,7 @@ import PhoneFormItem, { } from "../form-items-formatted/phone-form-item.component"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; - +import CurrencyInput from "../form-items-formatted/currency-form-item.component"; export default function ShopInfoGeneral({ form }) { const { t } = useTranslation(); return ( @@ -425,6 +425,18 @@ export default function ShopInfoGeneral({ form }) { > + + + + + + diff --git a/client/src/graphql/bodyshop.queries.js b/client/src/graphql/bodyshop.queries.js index 5f456e88a..5bbea1ef5 100644 --- a/client/src/graphql/bodyshop.queries.js +++ b/client/src/graphql/bodyshop.queries.js @@ -87,6 +87,7 @@ export const QUERY_BODYSHOP = gql` md_ccc_rates enforce_referral website + jc_hourly_rates employees { id active @@ -171,6 +172,7 @@ export const UPDATE_SHOP = gql` md_ccc_rates enforce_referral website + jc_hourly_rates employees { id first_name diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 9dfef4704..7c5106e75 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -218,6 +218,10 @@ "invoice_federal_tax_rate": "Invoices - Federal Tax Rate", "invoice_local_tax_rate": "Invoices - Local Tax Rate", "invoice_state_tax_rate": "Invoices - Provincial/State Tax Rate", + "jc_hourly_rates": { + "mapa": "Job Costing - Paint Materials Hourly Cost Rate", + "mash": "Job Costing - Shop Materials Hourly Cost Rate" + }, "lastnumberworkingdays": "Scoreboard - Last Number of Working Days", "logo_img_path": "Shop Logo", "logo_img_path_height": "Logo Image Height", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 5777c3745..d4b8d2c1e 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -218,6 +218,10 @@ "invoice_federal_tax_rate": "", "invoice_local_tax_rate": "", "invoice_state_tax_rate": "", + "jc_hourly_rates": { + "mapa": "", + "mash": "" + }, "lastnumberworkingdays": "", "logo_img_path": "", "logo_img_path_height": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 1bf35e380..6f7f00692 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -218,6 +218,10 @@ "invoice_federal_tax_rate": "", "invoice_local_tax_rate": "", "invoice_state_tax_rate": "", + "jc_hourly_rates": { + "mapa": "", + "mash": "" + }, "lastnumberworkingdays": "", "logo_img_path": "", "logo_img_path_height": "", diff --git a/hasura/migrations/1622147830428_alter_table_public_bodyshops_alter_column_autohouseid/down.yaml b/hasura/migrations/1622147830428_alter_table_public_bodyshops_alter_column_autohouseid/down.yaml new file mode 100644 index 000000000..6393d9f29 --- /dev/null +++ b/hasura/migrations/1622147830428_alter_table_public_bodyshops_alter_column_autohouseid/down.yaml @@ -0,0 +1,6 @@ +- args: + cascade: false + read_only: false + sql: ALTER TABLE "public"."bodyshops" ADD CONSTRAINT "bodyshops_autohouseid_key" + UNIQUE ("autohouseid"); + type: run_sql diff --git a/hasura/migrations/1622147830428_alter_table_public_bodyshops_alter_column_autohouseid/up.yaml b/hasura/migrations/1622147830428_alter_table_public_bodyshops_alter_column_autohouseid/up.yaml new file mode 100644 index 000000000..13be53f1d --- /dev/null +++ b/hasura/migrations/1622147830428_alter_table_public_bodyshops_alter_column_autohouseid/up.yaml @@ -0,0 +1,5 @@ +- args: + cascade: false + read_only: false + sql: ALTER TABLE "public"."bodyshops" DROP CONSTRAINT "bodyshops_autohouseid_key"; + type: run_sql diff --git a/hasura/migrations/1622147892235_alter_table_public_bodyshops_add_column_jc_hourly_rates/down.yaml b/hasura/migrations/1622147892235_alter_table_public_bodyshops_add_column_jc_hourly_rates/down.yaml new file mode 100644 index 000000000..40520bed4 --- /dev/null +++ b/hasura/migrations/1622147892235_alter_table_public_bodyshops_add_column_jc_hourly_rates/down.yaml @@ -0,0 +1,5 @@ +- args: + cascade: false + read_only: false + sql: ALTER TABLE "public"."bodyshops" DROP COLUMN "jc_hourly_rates"; + type: run_sql diff --git a/hasura/migrations/1622147892235_alter_table_public_bodyshops_add_column_jc_hourly_rates/up.yaml b/hasura/migrations/1622147892235_alter_table_public_bodyshops_add_column_jc_hourly_rates/up.yaml new file mode 100644 index 000000000..b943423ad --- /dev/null +++ b/hasura/migrations/1622147892235_alter_table_public_bodyshops_add_column_jc_hourly_rates/up.yaml @@ -0,0 +1,6 @@ +- args: + cascade: false + read_only: false + sql: ALTER TABLE "public"."bodyshops" ADD COLUMN "jc_hourly_rates" jsonb NULL + DEFAULT jsonb_build_object(); + type: run_sql diff --git a/hasura/migrations/1622147902848_update_permission_user_public_table_bodyshops/down.yaml b/hasura/migrations/1622147902848_update_permission_user_public_table_bodyshops/down.yaml new file mode 100644 index 000000000..f17e43eb2 --- /dev/null +++ b/hasura/migrations/1622147902848_update_permission_user_public_table_bodyshops/down.yaml @@ -0,0 +1,83 @@ +- args: + role: user + table: + name: bodyshops + schema: public + type: drop_select_permission +- args: + permission: + allow_aggregations: false + columns: + - accountingconfig + - address1 + - address2 + - appt_alt_transport + - appt_colors + - appt_length + - bill_tax_rates + - city + - country + - created_at + - default_adjustment_rate + - deliverchecklist + - email + - enforce_class + - enforce_referral + - federal_tax_id + - id + - imexshopid + - inhousevendorid + - insurance_vendor_id + - intakechecklist + - jobsizelimit + - logo_img_path + - md_categories + - md_ccc_rates + - md_classes + - md_hour_split + - md_ins_cos + - md_labor_rates + - md_messaging_presets + - md_notes_presets + - md_order_statuses + - md_parts_locations + - md_payment_types + - md_rbac + - md_referral_sources + - md_responsibility_centers + - md_ro_statuses + - messagingservicesid + - phone + - prodtargethrs + - production_config + - region_config + - schedule_end_time + - schedule_start_time + - scoreboard_target + - shopname + - shoprates + - speedprint + - ssbuckets + - state + - state_tax_id + - stripe_acct_id + - sub_status + - target_touchtime + - template_header + - textid + - updated_at + - use_fippa + - website + - workingdays + - zip_post + computed_fields: [] + filter: + associations: + user: + authid: + _eq: X-Hasura-User-Id + role: user + table: + name: bodyshops + schema: public + type: create_select_permission diff --git a/hasura/migrations/1622147902848_update_permission_user_public_table_bodyshops/up.yaml b/hasura/migrations/1622147902848_update_permission_user_public_table_bodyshops/up.yaml new file mode 100644 index 000000000..a1885496e --- /dev/null +++ b/hasura/migrations/1622147902848_update_permission_user_public_table_bodyshops/up.yaml @@ -0,0 +1,84 @@ +- args: + role: user + table: + name: bodyshops + schema: public + type: drop_select_permission +- args: + permission: + allow_aggregations: false + columns: + - accountingconfig + - address1 + - address2 + - appt_alt_transport + - appt_colors + - appt_length + - bill_tax_rates + - city + - country + - created_at + - default_adjustment_rate + - deliverchecklist + - email + - enforce_class + - enforce_referral + - federal_tax_id + - id + - imexshopid + - inhousevendorid + - insurance_vendor_id + - intakechecklist + - jc_hourly_rates + - jobsizelimit + - logo_img_path + - md_categories + - md_ccc_rates + - md_classes + - md_hour_split + - md_ins_cos + - md_labor_rates + - md_messaging_presets + - md_notes_presets + - md_order_statuses + - md_parts_locations + - md_payment_types + - md_rbac + - md_referral_sources + - md_responsibility_centers + - md_ro_statuses + - messagingservicesid + - phone + - prodtargethrs + - production_config + - region_config + - schedule_end_time + - schedule_start_time + - scoreboard_target + - shopname + - shoprates + - speedprint + - ssbuckets + - state + - state_tax_id + - stripe_acct_id + - sub_status + - target_touchtime + - template_header + - textid + - updated_at + - use_fippa + - website + - workingdays + - zip_post + computed_fields: [] + filter: + associations: + user: + authid: + _eq: X-Hasura-User-Id + role: user + table: + name: bodyshops + schema: public + type: create_select_permission diff --git a/hasura/migrations/1622147914381_update_permission_user_public_table_bodyshops/down.yaml b/hasura/migrations/1622147914381_update_permission_user_public_table_bodyshops/down.yaml new file mode 100644 index 000000000..cf88f05a1 --- /dev/null +++ b/hasura/migrations/1622147914381_update_permission_user_public_table_bodyshops/down.yaml @@ -0,0 +1,77 @@ +- args: + role: user + table: + name: bodyshops + schema: public + type: drop_update_permission +- args: + permission: + columns: + - accountingconfig + - address1 + - address2 + - appt_alt_transport + - appt_colors + - appt_length + - bill_tax_rates + - city + - country + - created_at + - default_adjustment_rate + - deliverchecklist + - email + - enforce_class + - enforce_referral + - federal_tax_id + - id + - inhousevendorid + - insurance_vendor_id + - intakechecklist + - logo_img_path + - md_categories + - md_ccc_rates + - md_classes + - md_hour_split + - md_ins_cos + - md_labor_rates + - md_messaging_presets + - md_notes_presets + - md_order_statuses + - md_parts_locations + - md_payment_types + - md_rbac + - md_referral_sources + - md_responsibility_centers + - md_ro_statuses + - phone + - prodtargethrs + - production_config + - schedule_end_time + - schedule_start_time + - scoreboard_target + - shopname + - shoprates + - speedprint + - ssbuckets + - state + - state_tax_id + - target_touchtime + - updated_at + - use_fippa + - website + - workingdays + - zip_post + filter: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + set: {} + role: user + table: + name: bodyshops + schema: public + type: create_update_permission diff --git a/hasura/migrations/1622147914381_update_permission_user_public_table_bodyshops/up.yaml b/hasura/migrations/1622147914381_update_permission_user_public_table_bodyshops/up.yaml new file mode 100644 index 000000000..e8583e4ca --- /dev/null +++ b/hasura/migrations/1622147914381_update_permission_user_public_table_bodyshops/up.yaml @@ -0,0 +1,78 @@ +- args: + role: user + table: + name: bodyshops + schema: public + type: drop_update_permission +- args: + permission: + columns: + - accountingconfig + - address1 + - address2 + - appt_alt_transport + - appt_colors + - appt_length + - bill_tax_rates + - city + - country + - created_at + - default_adjustment_rate + - deliverchecklist + - email + - enforce_class + - enforce_referral + - federal_tax_id + - id + - inhousevendorid + - insurance_vendor_id + - intakechecklist + - jc_hourly_rates + - logo_img_path + - md_categories + - md_ccc_rates + - md_classes + - md_hour_split + - md_ins_cos + - md_labor_rates + - md_messaging_presets + - md_notes_presets + - md_order_statuses + - md_parts_locations + - md_payment_types + - md_rbac + - md_referral_sources + - md_responsibility_centers + - md_ro_statuses + - phone + - prodtargethrs + - production_config + - schedule_end_time + - schedule_start_time + - scoreboard_target + - shopname + - shoprates + - speedprint + - ssbuckets + - state + - state_tax_id + - target_touchtime + - updated_at + - use_fippa + - website + - workingdays + - zip_post + filter: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + set: {} + role: user + table: + name: bodyshops + schema: public + type: create_update_permission diff --git a/hasura/migrations/metadata.yaml b/hasura/migrations/metadata.yaml index 3aef96e54..02291ea5a 100644 --- a/hasura/migrations/metadata.yaml +++ b/hasura/migrations/metadata.yaml @@ -771,6 +771,7 @@ tables: - inhousevendorid - insurance_vendor_id - intakechecklist + - jc_hourly_rates - jobsizelimit - logo_img_path - md_categories @@ -841,6 +842,7 @@ tables: - inhousevendorid - insurance_vendor_id - intakechecklist + - jc_hourly_rates - logo_img_path - md_categories - md_ccc_rates diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index fd3a07228..9b7b3677c 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -680,6 +680,7 @@ exports.QUERY_JOB_COSTING_DETAILS = ` query QUERY_JOB_COSTING_DETAILS($id: uuid! bodyshop{ id md_responsibility_centers + jc_hourly_rates } } }`; @@ -780,6 +781,7 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT bodyshop { id md_responsibility_centers + jc_hourly_rates } } } diff --git a/server/job/job-costing.js b/server/job/job-costing.js index 7b98acf1e..24d1d6382 100644 --- a/server/job/job-costing.js +++ b/server/job/job-costing.js @@ -10,8 +10,7 @@ Dinero.globalRoundingMode = "HALF_EVEN"; async function JobCosting(req, res) { const { jobid } = req.body; - console.log("🚀 ~ file: job-costing.js ~ line 13 ~ jobid", jobid); - console.time("querydata"); + console.time("Query for Data"); const BearerToken = req.headers.authorization; const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { @@ -209,6 +208,8 @@ function GenerateCostingData(job) { job.bodyshop.md_responsibility_centers.costs.map((p) => p.name) ); + const materialsHours = { mapaHrs: 0, mashHrs: 0 }; + //Massage the data. const jobLineTotalsByProfitCenter = job && @@ -234,6 +235,7 @@ function GenerateCostingData(job) { if (!acc.labor[defaultProfits["MAPA"]]) acc.labor[defaultProfits["MAPA"]] = Dinero(); + materialsHours.mapaHrs += val.mod_lb_hrs || 0; acc.labor[defaultProfits["MAPA"]] = acc.labor[ defaultProfits["MAPA"] ].add( @@ -253,6 +255,7 @@ function GenerateCostingData(job) { amount: Math.round((job.rate_mash || 0) * 100), }).multiply(val.mod_lb_hrs || 0) ); + materialsHours.mashHrs += val.mod_lb_hrs || 0; } //If labor line, add to paint and shop materials. } @@ -309,12 +312,7 @@ function GenerateCostingData(job) { .multiply(val.part_qty || 0) .percentage(val.prt_dsmk_p) ); - console.log( - `*** partsAmount`, - val.line_desc, - partsProfitCenter, - partsAmount.toJSON() - ); + if (!acc.parts[partsProfitCenter]) acc.parts[partsProfitCenter] = Dinero(); acc.parts[partsProfitCenter] = @@ -348,6 +346,52 @@ function GenerateCostingData(job) { return bill_acc; }, {}); + //If the hourly rates for job costing are set, add them in. + if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mapa) { + if ( + !billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MAPA + ] + ) + billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MAPA + ] = Dinero(); + billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MAPA + ] = billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MAPA + ].add( + Dinero({ + amount: + (job.bodyshop.jc_hourly_rates && + job.bodyshop.jc_hourly_rates.mapa * 100) || + 0, + }).multiply(materialsHours.mapaHrs) + ); + } + if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mash) { + if ( + !billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MASH + ] + ) + billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MASH + ] = Dinero(); + billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MASH + ] = billTotalsByCostCenters[ + job.bodyshop.md_responsibility_centers.defaults.costs.MASH + ].add( + Dinero({ + amount: + (job.bodyshop.jc_hourly_rates && + job.bodyshop.jc_hourly_rates.mash * 100) || + 0, + }).multiply(materialsHours.mashHrs) + ); + } + const ticketTotalsByCostCenter = job.timetickets.reduce( (ticket_acc, ticket_val) => { //At the invoice level. From 79f2c7dd3d67c0bbae41c6c99ac8b05f479fe953 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Thu, 27 May 2021 14:11:16 -0700 Subject: [PATCH 09/10] IO-1086 confirmation on profile update. --- bodyshop_translations.babel | 26 +++++++++++++++++++++++ client/src/redux/user/user.sagas.js | 6 ++++++ client/src/translations/en_us/common.json | 3 +++ client/src/translations/es/common.json | 3 +++ client/src/translations/fr/common.json | 3 +++ 5 files changed, 41 insertions(+) diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index a6258dd82..abc4f8118 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -30465,6 +30465,32 @@ + + successes + + + updated + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + diff --git a/client/src/redux/user/user.sagas.js b/client/src/redux/user/user.sagas.js index a70964387..cbeba03af 100644 --- a/client/src/redux/user/user.sagas.js +++ b/client/src/redux/user/user.sagas.js @@ -1,4 +1,6 @@ import Fingerprint2 from "@fingerprintjs/fingerprintjs"; +import { notification } from "antd"; +import i18next from "i18next"; import LogRocket from "logrocket"; import { all, call, delay, put, select, takeLatest } from "redux-saga/effects"; import { @@ -100,6 +102,10 @@ export function* updateUserDetails(userDetails) { try { yield updateCurrentUser(userDetails.payload); yield put(updateUserDetailsSuccess(userDetails.payload)); + notification.open({ + type: "success", + message: i18next.t("profile.successes.updated"), + }); } catch (error) { //yield put(signOutFailure(error.message)); } diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 7c5106e75..164bdd128 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -1825,6 +1825,9 @@ }, "labels": { "activeshop": "Active Shop" + }, + "successes": { + "updated": "Profile updated successfully." } }, "reportcenter": { diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index d4b8d2c1e..4fae2a134 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -1825,6 +1825,9 @@ }, "labels": { "activeshop": "" + }, + "successes": { + "updated": "" } }, "reportcenter": { diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 6f7f00692..63fde29b6 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -1825,6 +1825,9 @@ }, "labels": { "activeshop": "" + }, + "successes": { + "updated": "" } }, "reportcenter": { From c8506387f6500964b12eb5d9b2d7a40acac93670 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Thu, 27 May 2021 14:58:23 -0700 Subject: [PATCH 10/10] IO-1131 - Remove version #. --- 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 9563b5bfd..364520735 100644 --- a/client/src/pages/manage/manage.page.component.jsx +++ b/client/src/pages/manage/manage.page.component.jsx @@ -385,7 +385,7 @@ export function Manage({ match, conflict, bodyshop }) {